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