mail-autoexpunge.c revision 27129cce9e2205639739e1877af152e7caa1fc82
1N/A/* Copyright (c) 2015-2017 Dovecot authors, see the included COPYING file */
1N/A
1N/A#include "lib.h"
1N/A#include "ioloop.h"
1N/A#include "file-create-locked.h"
1N/A#include "mailbox-list-iter.h"
1N/A#include "mail-storage-private.h"
1N/A#include "mail-namespace.h"
1N/A#include "mail-user.h"
1N/A#include "mail-autoexpunge.h"
1N/A
1N/A#define AUTOEXPUNGE_LOCK_FNAME "dovecot.autoexpunge.lock"
1N/A
1N/Astruct mailbox_autoexpunge_lock {
1N/A const char *path;
1N/A struct file_lock *lock;
1N/A int fd;
1N/A};
1N/A
1N/Astatic void mailbox_autoexpunge_lock(struct mail_user *user,
1N/A struct mailbox_autoexpunge_lock *lock)
1N/A{
1N/A struct file_create_settings lock_set;
1N/A bool created;
1N/A const char *home, *error;
1N/A int ret;
1N/A
1N/A /* Try to lock the autoexpunging. If the lock already exists, another
1N/A process is already busy with expunging, so we don't have to do it.
1N/A The easiest place where to store the lock file to is the home
1N/A directory, but allow autoexpunging to work even if we can't get
1N/A it. The lock isn't really required; it 1) improves performance
1N/A so that multiple processes won't do the same work unnecessarily,
1N/A and 2) it helps to avoid duplicates mails being added with
1N/A lazy_expunge. */
1N/A if ((ret = mail_user_get_home(user, &home)) > 0) {
1N/A const struct mail_storage_settings *mail_set =
1N/A mail_user_set_get_storage_set(user);
1N/A i_zero(&lock_set);
1N/A lock_set.lock_method = mail_set->parsed_lock_method,
1N/A lock->path = t_strdup_printf("%s/"AUTOEXPUNGE_LOCK_FNAME, home);
1N/A lock->fd = file_create_locked(lock->path, &lock_set,
1N/A &lock->lock, &created, &error);
1N/A if (lock->fd == -1) {
1N/A if (errno != EAGAIN && errno != ENOENT)
1N/A i_error("autoexpunge: Couldn't lock %s: %s", lock->path, error);
1N/A }
1N/A } else if (ret == 0) {
1N/A i_warning("autoexpunge: User has no home directory, can't lock");
1N/A }
1N/A}
1N/A
1N/Astatic int
1N/Amailbox_autoexpunge(struct mailbox *box, unsigned int interval_time,
1N/A unsigned int max_mails)
1N/A{
1N/A struct mailbox_transaction_context *t;
1N/A struct mail *mail;
1N/A struct mailbox_metadata metadata;
1N/A const struct mail_index_header *hdr;
1N/A struct mailbox_status status;
1N/A uint32_t seq;
1N/A time_t timestamp, expire_time, last_rename_stamp = 0;
1N/A const void *data;
1N/A size_t size;
1N/A int ret = 0;
1N/A
1N/A if ((unsigned int)ioloop_time < interval_time)
1N/A expire_time = 0;
1N/A else
1N/A expire_time = ioloop_time - interval_time;
1N/A
1N/A /* first try to check quickly from mailbox list index if we should
1N/A bother opening this mailbox. */
1N/A if (mailbox_get_status(box, STATUS_MESSAGES, &status) < 0) {
1N/A if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
1N/A /* autocreated mailbox doesn't exist yet */
1N/A return 0;
1N/A }
1N/A return -1;
1N/A }
1N/A if (interval_time == 0 && status.messages <= max_mails)
1N/A return 0;
1N/A
1N/A if (max_mails == 0 || status.messages <= max_mails) {
1N/A if (mailbox_get_metadata(box, MAILBOX_METADATA_FIRST_SAVE_DATE,
1N/A &metadata) < 0)
1N/A return -1;
1N/A if (metadata.first_save_date == (time_t)-1 ||
1N/A metadata.first_save_date > expire_time)
1N/A return 0;
1N/A }
1N/A
1N/A if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0)
1N/A return -1;
1N/A
1N/A mail_index_get_header_ext(box->view, box->box_last_rename_stamp_ext_id,
1N/A &data, &size);
1N/A
1N/A if (size >= sizeof(uint32_t))
1N/A last_rename_stamp = *(const uint32_t*)data;
1N/A
1N/A t = mailbox_transaction_begin(box, 0);
1N/A mail = mail_alloc(t, 0, NULL);
1N/A
1N/A hdr = mail_index_get_header(box->view);
1N/A for (seq = 1; seq <= hdr->messages_count; seq++) {
1N/A mail_set_seq(mail, seq);
1N/A if (max_mails > 0 && hdr->messages_count - seq + 1 > max_mails) {
1N/A /* max_mails is still being reached -> expunge.
1N/A don't even check saved-dates before we're
1N/A below max_mails. */
1N/A mail_expunge(mail);
1N/A } else if (interval_time == 0) {
1N/A /* only max_mails is used. nothing further to do. */
1N/A break;
1N/A } else if (mail_get_save_date(mail, &timestamp) == 0) {
1N/A if (I_MAX(last_rename_stamp, timestamp) > expire_time)
1N/A break;
1N/A mail_expunge(mail);
1N/A } else if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXPUNGED) {
1N/A /* already expunged */
1N/A } else {
1N/A /* failed */
1N/A ret = -1;
1N/A break;
1N/A }
1N/A }
1N/A mail_free(&mail);
1N/A if (mailbox_transaction_commit(&t) < 0)
1N/A ret = -1;
1N/A return ret;
1N/A}
1N/A
1N/Astatic void
1N/Amailbox_autoexpunge_set(struct mail_namespace *ns, const char *vname,
1N/A unsigned int autoexpunge,
1N/A unsigned int autoexpunge_max_mails)
1N/A{
1N/A struct mailbox *box;
1N/A
1N/A /* autoexpunge is configured by admin, so we can safely ignore
1N/A any ACLs the user might normally have against expunging in
1N/A the mailbox. */
1N/A box = mailbox_alloc(ns->list, vname, MAILBOX_FLAG_IGNORE_ACLS);
1N/A if (mailbox_autoexpunge(box, autoexpunge, autoexpunge_max_mails) < 0) {
1N/A i_error("Failed to autoexpunge mailbox '%s': %s",
1N/A mailbox_get_vname(box),
1N/A mailbox_get_last_error(box, NULL));
1N/A }
1N/A mailbox_free(&box);
1N/A}
1N/A
1N/Astatic void
1N/Amailbox_autoexpunge_wildcards(struct mail_namespace *ns,
1N/A const struct mailbox_settings *set)
1N/A{
1N/A struct mailbox_list_iterate_context *iter;
1N/A const struct mailbox_info *info;
1N/A const char *iter_name;
1N/A
1N/A iter_name = t_strconcat(ns->prefix, set->name, NULL);
1N/A iter = mailbox_list_iter_init(ns->list, iter_name,
1N/A MAILBOX_LIST_ITER_NO_AUTO_BOXES |
1N/A MAILBOX_LIST_ITER_SKIP_ALIASES |
1N/A MAILBOX_LIST_ITER_RETURN_NO_FLAGS);
1N/A while ((info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
1N/A mailbox_autoexpunge_set(ns, info->vname, set->autoexpunge,
1N/A set->autoexpunge_max_mails);
1N/A } T_END;
1N/A if (mailbox_list_iter_deinit(&iter) < 0) {
1N/A i_error("Failed to iterate autoexpunge mailboxes '%s': %s",
1N/A iter_name, mailbox_list_get_last_error(ns->list, NULL));
1N/A }
1N/A}
static void
mail_namespace_autoexpunge(struct mail_namespace *ns,
struct mailbox_autoexpunge_lock *lock)
{
struct mailbox_settings *const *box_set;
const char *vname;
if (!array_is_created(&ns->set->mailboxes))
return;
array_foreach(&ns->set->mailboxes, box_set) {
if ((*box_set)->autoexpunge == 0 &&
(*box_set)->autoexpunge_max_mails == 0)
continue;
mailbox_autoexpunge_lock(ns->user, lock);
if (strpbrk((*box_set)->name, "*?") != NULL)
mailbox_autoexpunge_wildcards(ns, *box_set);
else {
if ((*box_set)->name[0] == '\0' && ns->prefix_len > 0 &&
ns->prefix[ns->prefix_len-1] == mail_namespace_get_sep(ns))
vname = t_strndup(ns->prefix, ns->prefix_len - 1);
else
vname = t_strconcat(ns->prefix, (*box_set)->name, NULL);
mailbox_autoexpunge_set(ns, vname, (*box_set)->autoexpunge,
(*box_set)->autoexpunge_max_mails);
}
}
}
void mail_user_autoexpunge(struct mail_user *user)
{
struct mailbox_autoexpunge_lock lock = { .fd = -1 };
struct mail_namespace *ns;
for (ns = user->namespaces; ns != NULL; ns = ns->next) {
if (ns->alias_for == NULL)
mail_namespace_autoexpunge(ns, &lock);
}
if (lock.fd != -1) {
i_unlink(lock.path);
i_close_fd(&lock.fd);
file_lock_free(&lock.lock);
}
}