-pkglibexecdir = $(libexecdir)/dovecot
+doveadm_moduledir = $(moduledir)/doveadm
AM_CPPFLAGS = \
-I$(top_srcdir)/src/lib \
-I$(top_srcdir)/src/lib-imap \
-I$(top_srcdir)/src/lib-index \
-I$(top_srcdir)/src/lib-storage \
- -I$(top_srcdir)/src/lib-storage/index
+ -I$(top_srcdir)/src/lib-storage/index \
+ -I$(top_srcdir)/src/doveadm
+lib10_doveadm_expire_plugin_la_LDFLAGS = -module -avoid-version
lib20_expire_plugin_la_LDFLAGS = -module -avoid-version
module_LTLIBRARIES = \
lib20_expire_plugin.la
lib20_expire_plugin_la_SOURCES = \
- expire-env.c \
+ expire-set.c \
expire-plugin.c
noinst_HEADERS = \
- expire-env.h \
+ expire-set.h \
expire-plugin.h
-pkglibexec_PROGRAMS = expire-tool
+doveadm_module_LTLIBRARIES = \
+ lib10_doveadm_expire_plugin.la
-expire_tool_SOURCES = \
- expire-tool.c
-
-if !BUILD_SHARED_LIBS
-unused_objects = \
- $(top_builddir)/src/lib/mountpoint.o
-endif
-
-libs = \
- expire-env.lo \
- $(LIBDOVECOT_STORAGE) \
- $(unused_objects)
-
-expire_tool_LDADD = $(libs) $(LIBDOVECOT) $(MODULE_LIBS)
-expire_tool_DEPENDENCIES = $(libs) $(LIBDOVECOT_DEPS)
+lib10_doveadm_expire_plugin_la_SOURCES = \
+ doveadm-expire.c
--- /dev/null
+/* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "module-dir.h"
+#include "str.h"
+#include "hash.h"
+#include "dict.h"
+#include "imap-match.h"
+#include "expire-set.h"
+#include "mail-search.h"
+#include "doveadm-mail.h"
+
+#define DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, doveadm_expire_mail_cmd_module)
+
+struct expire_query {
+ const char *mailbox;
+ struct imap_match_glob *glob;
+ time_t before_time;
+};
+
+struct doveadm_expire_mail_cmd_context {
+ union doveadm_mail_cmd_module_context module_ctx;
+
+ struct dict *dict;
+ struct dict_transaction_context *trans;
+ struct dict_iterate_context *iter;
+
+ struct hash_table *seen_users;
+ ARRAY_DEFINE(queries, struct expire_query);
+ time_t oldest_before_time;
+};
+
+const char *doveadm_expire_plugin_version = DOVECOT_VERSION;
+
+void doveadm_expire_plugin_init(struct module *module);
+void doveadm_expire_plugin_deinit(void);
+
+static MODULE_CONTEXT_DEFINE_INIT(doveadm_expire_mail_cmd_module,
+ &doveadm_mail_cmd_module_register);
+static void (*next_hook_doveadm_mail_init)(struct doveadm_mail_cmd_context *ctx);
+
+static bool
+doveadm_expire_mail_match_mailbox(struct doveadm_expire_mail_cmd_context *ectx,
+ const char *mailbox, time_t oldest_savedate)
+{
+ const struct expire_query *query;
+
+ array_foreach(&ectx->queries, query) {
+ if (oldest_savedate >= query->before_time)
+ continue;
+
+ if (query->glob == NULL) {
+ if (strcmp(query->mailbox, mailbox) == 0)
+ return TRUE;
+ } else {
+ if (imap_match(query->glob, mailbox) == IMAP_MATCH_YES)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static bool
+doveadm_expire_mail_want(struct doveadm_mail_cmd_context *ctx,
+ const char *key, time_t stamp, const char **username_r)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+ const char *username, *mailbox;
+ char *username_dup;
+
+ /* key = DICT_EXPIRE_PREFIX<user>/<mailbox> */
+ username = key + strlen(DICT_EXPIRE_PREFIX);
+ mailbox = strchr(username, '/');
+ if (mailbox == NULL) {
+ /* invalid record, ignore */
+ return FALSE;
+ }
+ username = t_strdup_until(username, mailbox++);
+
+ if (hash_table_lookup(ectx->seen_users, username) != NULL) {
+ /* seen this user already, skip the record */
+ return FALSE;
+ }
+
+ if (!doveadm_expire_mail_match_mailbox(ectx, mailbox, stamp)) {
+ /* this mailbox doesn't have any matching messages */
+ return FALSE;
+ }
+ username_dup = p_strdup(ctx->pool, username);
+ hash_table_insert(ectx->seen_users, username_dup, username_dup);
+
+ *username_r = username_dup;
+ return TRUE;
+}
+
+static int
+doveadm_expire_mail_cmd_get_next_user(struct doveadm_mail_cmd_context *ctx,
+ const char **username_r)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+ const char *key, *value;
+ unsigned long stamp;
+ bool ret;
+
+ while (dict_iterate(ectx->iter, &key, &value)) {
+ if (str_to_ulong(value, &stamp) < 0) {
+ /* invalid record */
+ continue;
+ }
+ if ((time_t)stamp > ectx->oldest_before_time)
+ break;
+
+ T_BEGIN {
+ ret = doveadm_expire_mail_want(ctx, key, stamp,
+ username_r);
+ } T_END;
+ if (ret)
+ return TRUE;
+ }
+
+ /* finished */
+ if (dict_iterate_deinit(&ectx->iter) < 0) {
+ i_error("Dictionary iteration failed");
+ return -1;
+ }
+ return 0;
+}
+
+static const char *const *doveadm_expire_get_patterns(void)
+{
+ ARRAY_TYPE(const_string) patterns;
+ const char *str;
+ char set_name[20];
+ unsigned int i;
+
+ t_array_init(&patterns, 16);
+ str = doveadm_plugin_getenv("expire");
+ for (i = 2; str != NULL; i++) {
+ array_append(&patterns, &str, 1);
+
+ i_snprintf(set_name, sizeof(set_name), "expire%u", i);
+ str = doveadm_plugin_getenv(set_name);
+ }
+ (void)array_append_space(&patterns);
+ return array_idx(&patterns, 0);
+}
+
+static bool
+doveadm_expire_get_or_mailboxes(struct doveadm_mail_cmd_context *ctx,
+ const struct mail_search_arg *args,
+ struct expire_query query)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+ const struct mail_search_arg *arg;
+ unsigned int query_count;
+
+ query.mailbox = NULL;
+ query_count = array_count(&ectx->queries);
+ for (arg = args; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_MAILBOX_GLOB:
+ query.glob = imap_match_init(ctx->pool, arg->value.str,
+ TRUE, '/');
+ /* fall through */
+ case SEARCH_MAILBOX:
+ /* require mailbox to be in expire patterns */
+ query.mailbox = p_strdup(ctx->pool, arg->value.str);
+ array_append(&ectx->queries, &query, 1);
+ break;
+ default:
+ /* there are something else besides mailboxes,
+ can't optimize this. */
+ array_delete(&ectx->queries, query_count,
+ array_count(&ectx->queries) - query_count);
+ return FALSE;
+ }
+ }
+ return query.mailbox != NULL;
+}
+
+static bool
+doveadm_expire_analyze_and_query(struct doveadm_mail_cmd_context *ctx,
+ const struct mail_search_arg *args)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+ const struct mail_search_arg *arg;
+ struct expire_query query;
+ bool have_or = FALSE;
+
+ memset(&query, 0, sizeof(query));
+ query.before_time = (time_t)-1;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_OR:
+ have_or = TRUE;
+ break;
+ case SEARCH_MAILBOX_GLOB:
+ query.glob = imap_match_init(ctx->pool, arg->value.str,
+ TRUE, '/');
+ /* fall through */
+ case SEARCH_MAILBOX:
+ /* require mailbox to be in expire patterns */
+ query.mailbox = p_strdup(ctx->pool, arg->value.str);
+ break;
+ case SEARCH_BEFORE:
+ if (arg->value.date_type != MAIL_SEARCH_DATE_TYPE_SAVED)
+ break;
+ if ((arg->value.search_flags &
+ MAIL_SEARCH_ARG_FLAG_USE_TZ) == 0)
+ break;
+ query.before_time = arg->value.time;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (query.before_time == (time_t)-1) {
+ /* no SAVEDBEFORE, can't optimize */
+ return FALSE;
+ }
+
+ if (query.mailbox != NULL) {
+ /* one mailbox */
+ array_append(&ectx->queries, &query, 1);
+ return TRUE;
+ }
+
+ /* no MAILBOX, but check if one of the ORs lists mailboxes */
+ if (!have_or)
+ return FALSE;
+
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (arg->type == SEARCH_OR &&
+ doveadm_expire_get_or_mailboxes(ctx, arg->value.subargs,
+ query))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static time_t
+doveadm_expire_analyze_or_query(struct doveadm_mail_cmd_context *ctx,
+ const struct mail_search_arg *args)
+{
+ const struct mail_search_arg *arg;
+
+ /* all of the subqueries must have mailbox and savedbefore */
+ for (arg = args; arg != NULL; arg = arg->next) {
+ if (arg->type != SEARCH_SUB)
+ return FALSE;
+
+ if (!doveadm_expire_analyze_and_query(ctx, arg->value.subargs))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool doveadm_expire_analyze_query(struct doveadm_mail_cmd_context *ctx)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+ struct mail_search_arg *args = ctx->search_args->args;
+ struct expire_set *set;
+ const struct expire_query *queries;
+ unsigned int i, count;
+
+ /* we support two kinds of queries:
+
+ 1) mailbox-pattern savedbefore <stamp> ...
+ 2) or 2*(mailbox-pattern savedbefore <stamp> ...)
+
+ mailbox-pattern can be:
+
+ a) mailbox <name>
+ b) or 2*(mailbox <name>)
+ */
+ p_array_init(&ectx->queries, ctx->pool, 8);
+ if (!doveadm_expire_analyze_and_query(ctx, args) &&
+ (args->type != SEARCH_OR || args->next != NULL ||
+ !doveadm_expire_analyze_or_query(ctx, args->value.subargs))) {
+ if (doveadm_debug)
+ i_debug("expire: Couldn't optimize search query");
+ return FALSE;
+ }
+
+ /* make sure all mailboxes match expire patterns */
+ set = expire_set_init(doveadm_expire_get_patterns());
+ queries = array_get(&ectx->queries, &count);
+ for (i = 0; i < count; i++) {
+ if (!expire_set_lookup(set, queries[i].mailbox)) {
+ if (doveadm_debug) {
+ i_debug("expire: Couldn't optimize search query: "
+ "mailbox %s not in expire database",
+ queries[i].mailbox);
+ }
+ break;
+ }
+ }
+ expire_set_deinit(&set);
+
+ return i == count;
+}
+
+static void doveadm_expire_mail_cmd_deinit(struct doveadm_mail_cmd_context *ctx)
+{
+ struct doveadm_expire_mail_cmd_context *ectx =
+ DOVEADM_EXPIRE_MAIL_CMD_CONTEXT(ctx);
+
+ if (ectx->iter != NULL) {
+ if (dict_iterate_deinit(&ectx->iter) < 0)
+ i_error("Dictionary iteration failed");
+ }
+ dict_transaction_commit(&ectx->trans);
+ dict_deinit(&ectx->dict);
+ hash_table_destroy(&ectx->seen_users);
+
+ return ectx->module_ctx.super.deinit(ctx);
+}
+
+static void doveadm_expire_mail_init(struct doveadm_mail_cmd_context *ctx)
+{
+ struct doveadm_expire_mail_cmd_context *ectx;
+ struct dict *dict;
+ const struct expire_query *query;
+ const char *expire_dict;
+
+ if (ctx->search_args == NULL)
+ return;
+
+ expire_dict = doveadm_plugin_getenv("expire_dict");
+ if (expire_dict == NULL)
+ return;
+
+ ectx = p_new(ctx->pool, struct doveadm_expire_mail_cmd_context, 1);
+ ectx->module_ctx.super = ctx->v;
+ MODULE_CONTEXT_SET(ctx, doveadm_expire_mail_cmd_module, ectx);
+
+ /* we can potentially optimize this query. see if the search args
+ are valid for optimization. */
+ if (!doveadm_expire_analyze_query(ctx))
+ return;
+ i_debug("expire: Searching only users listed in expire database");
+
+ dict = dict_init(expire_dict, DICT_DATA_TYPE_UINT32, "",
+ doveadm_settings->base_dir);
+ if (dict == NULL) {
+ i_error("dict_init(%s) failed, not using it", expire_dict);
+ return;
+ }
+
+ ectx->oldest_before_time = (time_t)-1;
+ array_foreach(&ectx->queries, query) {
+ if (ectx->oldest_before_time > query->before_time ||
+ ectx->oldest_before_time == (time_t)-1)
+ ectx->oldest_before_time = query->before_time;
+ }
+
+ ctx->v.deinit = doveadm_expire_mail_cmd_deinit;
+ ctx->v.get_next_user = doveadm_expire_mail_cmd_get_next_user;
+
+ ectx->seen_users =
+ hash_table_create(default_pool, ctx->pool, 0,
+ str_hash, (hash_cmp_callback_t *)strcmp);
+ ectx->dict = dict;
+ ectx->trans = dict_transaction_begin(dict);
+ ectx->iter = dict_iterate_init(dict, DICT_EXPIRE_PREFIX,
+ DICT_ITERATE_FLAG_RECURSE |
+ DICT_ITERATE_FLAG_SORT_BY_VALUE);
+}
+
+void doveadm_expire_plugin_init(struct module *module ATTR_UNUSED)
+{
+ next_hook_doveadm_mail_init = hook_doveadm_mail_init;
+ hook_doveadm_mail_init = doveadm_expire_mail_init;
+}
+
+void doveadm_expire_plugin_deinit(void)
+{
+ i_assert(hook_doveadm_mail_init == doveadm_expire_mail_init);
+ hook_doveadm_mail_init = next_hook_doveadm_mail_init;
+}
+++ /dev/null
-/* Copyright (c) 2006-2010 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "array.h"
-#include "str.h"
-#include "strescape.h"
-#include "settings-parser.h"
-#include "imap-match.h"
-#include "mail-namespace.h"
-#include "expire-env.h"
-
-#include <stdlib.h>
-
-enum expire_type {
- EXPIRE_TYPE_EXPUNGE,
- EXPIRE_TYPE_ALTMOVE
-};
-
-struct expire_rule {
- const char *pattern;
- struct imap_match_glob *glob;
-
- enum expire_type type;
- unsigned int expire_secs;
-};
-
-struct expire_env {
- pool_t pool;
- ARRAY_DEFINE(rules, struct expire_rule);
-};
-
-static void
-expire_env_parse(struct expire_env *env, struct mail_namespace *namespaces,
- const char *str)
-{
- struct expire_rule rule;
- struct mail_namespace *ns;
- const char *const *args;
- const char *p, *ns_name, *type_str, *error;
-
- if (*str == '"') {
- /* quoted string */
- for (p = ++str; *p != '\0'; p++) {
- if (*p == '\\' && p[1] != '\0')
- p++;
- else if (*p == '"')
- break;
- }
- rule.pattern = str_unescape(p_strdup_until(env->pool, str, p));
- if (*p == '"') p++;
- } else {
- p = strchr(str, ' ');
- if (p == NULL) p = str + strlen(str);
- rule.pattern = p_strdup_until(env->pool, str, p);
- }
-
- if (*p == ' ') p++;
- args = t_strsplit_spaces(p, " ");
-
- /* find namespace's separator and create a glob */
- ns_name = rule.pattern;
- ns = mail_namespace_find(namespaces, &ns_name);
- if (ns == NULL && *rule.pattern != '*') {
- i_warning("expire: No namespace found for mailbox: %s",
- rule.pattern);
- }
- rule.glob = imap_match_init(env->pool, rule.pattern, TRUE,
- ns == NULL ? '/' : ns->sep);
-
- /* get expire time */
- if (args[0] == NULL) {
- i_fatal("expire: Missing expire time for mailbox '%s'",
- rule.pattern);
- }
- if (str_is_numeric(args[0], '\0')) {
- i_fatal("expire: Missing expire time specifier for mailbox "
- "'%s': %s (add e.g. 'days')", rule.pattern, args[0]);
- }
- if (settings_get_time(args[0], &rule.expire_secs, &error) < 0) {
- i_fatal("expire: Invalid time for mailbox '%s': %s",
- rule.pattern, error);
- }
-
- /* expire type */
- type_str = args[1] != NULL ? args[1] : "expunge";
- if (strcmp(type_str, "expunge") == 0)
- rule.type = EXPIRE_TYPE_EXPUNGE;
- else if (strcmp(type_str, "altmove") == 0)
- rule.type = EXPIRE_TYPE_ALTMOVE;
- else {
- i_fatal("expire: Unknown type for mailbox '%s': %s",
- rule.pattern, type_str);
- }
-
- if (namespaces->user->mail_debug) {
- i_debug("expire: pattern=%s secs=%u type=%s",
- rule.pattern, rule.expire_secs, type_str);
- }
- array_append(&env->rules, &rule, 1);
-}
-
-struct expire_env *expire_env_init(struct mail_namespace *namespaces)
-{
- struct mail_user *user = namespaces->user;
- struct expire_env *env;
- const char *rule_str;
- char env_name[20];
- unsigned int i;
- pool_t pool;
-
- pool = pool_alloconly_create("Expire pool", 512);
- env = p_new(pool, struct expire_env, 1);
- env->pool = pool;
- p_array_init(&env->rules, env->pool, 16);
-
- rule_str = mail_user_set_plugin_getenv(user->set, "expire");
- for (i = 2; rule_str != NULL; i++) {
- expire_env_parse(env, namespaces, rule_str);
-
- i_snprintf(env_name, sizeof(env_name), "expire%u", i);
- rule_str = mail_user_set_plugin_getenv(user->set, env_name);
- }
- return env;
-}
-
-void expire_env_deinit(struct expire_env **_env)
-{
- struct expire_env *env = *_env;
-
- *_env = NULL;
- pool_unref(&env->pool);
-}
-
-bool expire_rule_find(struct expire_env *env, const char *name,
- unsigned int *expunge_secs_r,
- unsigned int *altmove_secs_r)
-{
- const struct expire_rule *rule;
- unsigned int secs, expunge_min = 0, altmove_min = 0;
-
- array_foreach(&env->rules, rule) {
- if (imap_match(rule->glob, name) == IMAP_MATCH_YES) {
- secs = rule->expire_secs;
- i_assert(secs > 0);
-
- switch (rule->type) {
- case EXPIRE_TYPE_EXPUNGE:
- if (expunge_min == 0 || expunge_min > secs)
- expunge_min = secs;
- break;
- case EXPIRE_TYPE_ALTMOVE:
- if (altmove_min == 0 || altmove_min > secs)
- altmove_min = secs;
- break;
- }
- }
- }
- *expunge_secs_r = expunge_min;
- *altmove_secs_r = altmove_min;
- return expunge_min > 0 || altmove_min > 0;
-}
-
-unsigned int expire_rule_find_min_secs(struct expire_env *env, const char *name,
- bool *altmove_r)
-{
- unsigned int secs1, secs2;
-
- (void)expire_rule_find(env, name, &secs1, &secs2);
- if (secs1 != 0 && (secs1 < secs2 || secs2 == 0)) {
- *altmove_r = FALSE;
- return secs1;
- } else {
- *altmove_r = TRUE;
- return secs2;
- }
-}
+++ /dev/null
-#ifndef EXPIRE_ENV_H
-#define EXPIRE_ENV_H
-
-#define DICT_EXPIRE_PREFIX DICT_PATH_SHARED"expire/"
-
-struct expire_env;
-struct mail_namespace;
-
-struct expire_env *expire_env_init(struct mail_namespace *namespaces);
-void expire_env_deinit(struct expire_env **env);
-
-bool expire_rule_find(struct expire_env *env, const char *name,
- unsigned int *expunge_secs_r,
- unsigned int *altmove_secs_r);
-
-unsigned int expire_rule_find_min_secs(struct expire_env *env, const char *name,
- bool *altmove_r);
-
-#endif
#include "mail-namespace.h"
#include "index-mail.h"
#include "index-storage.h"
-#include "expire-env.h"
+#include "expire-set.h"
#include "expire-plugin.h"
#include <stdlib.h>
union mail_user_module_context module_ctx;
struct dict *db;
- struct expire_env *env;
+ struct expire_set *set;
};
struct expire_mailbox {
union mailbox_module_context module_ctx;
- time_t expire_secs;
- unsigned int altmove:1;
};
struct expire_transaction_context {
bool update_dict = FALSE;
int ret;
- if (xpr_box->altmove) {
- /* only moving mails - don't update the move stamps */
- } else if (xt->first_expunged) {
+ if (xt->first_expunged) {
/* first mail expunged. dict needs updating. */
first_nonexpunged_timestamp(t, &new_stamp);
update_dict = TRUE;
/* everything expunged */
dict_unset(dctx, key);
} else {
- new_stamp += xpr_box->expire_secs;
dict_set(dctx, key, dec2str(new_stamp));
}
dict_transaction_commit(&dctx);
return xpr_box->module_ctx.super.copy(ctx, mail);
}
-static void
-mailbox_expire_hook(struct mailbox *box, time_t expire_secs, bool altmove)
+static void expire_mailbox_allocate_init(struct mailbox *box)
{
struct expire_mailbox *xpr_box;
box->v.save_finish = expire_save_finish;
box->v.copy = expire_copy;
- xpr_box->altmove = altmove;
- xpr_box->expire_secs = expire_secs;
-
MODULE_CONTEXT_SET(box, expire_storage_module, xpr_box);
}
-static void expire_mailbox_allocate_init(struct mailbox *box,
- struct expire_mail_user *euser)
-{
- unsigned int secs;
- bool altmove;
-
- secs = expire_rule_find_min_secs(euser->env, box->vname, &altmove);
- if (box->storage->user->mail_debug) {
- if (secs == 0) {
- i_debug("expire: No expiring in mailbox: %s",
- box->vname);
- } else {
- i_debug("expire: Mails expire in %u secs in mailbox: "
- "%s", secs, box->vname);
- }
- }
- if (secs != 0)
- mailbox_expire_hook(box, secs, altmove);
-}
-
static void expire_mailbox_allocated(struct mailbox *box)
{
struct expire_mail_user *euser =
EXPIRE_USER_CONTEXT(box->storage->user);
- if (euser != NULL)
- expire_mailbox_allocate_init(box, euser);
+ if (euser != NULL && expire_set_lookup(euser->set, box->vname))
+ expire_mailbox_allocate_init(box);
}
static void expire_mail_user_deinit(struct mail_user *user)
struct expire_mail_user *euser = EXPIRE_USER_CONTEXT(user);
dict_deinit(&euser->db);
- expire_env_deinit(&euser->env);
+ expire_set_deinit(&euser->set);
euser->module_ctx.super.deinit(user);
}
+static const char *const *expire_get_patterns(struct mail_user *user)
+{
+ ARRAY_TYPE(const_string) patterns;
+ const char *str;
+ char set_name[20];
+ unsigned int i;
+
+ t_array_init(&patterns, 16);
+ str = mail_user_set_plugin_getenv(user->set, "expire");
+ for (i = 2; str != NULL; i++) {
+ array_append(&patterns, &str, 1);
+
+ i_snprintf(set_name, sizeof(set_name), "expire%u", i);
+ str = mail_user_set_plugin_getenv(user->set, set_name);
+ }
+ (void)array_append_space(&patterns);
+ return array_idx(&patterns, 0);
+}
+
static void expire_mail_namespaces_created(struct mail_namespace *ns)
{
struct mail_user *user = ns->user;
struct expire_mail_user *euser;
- const char *dict_uri, *service_name;
+ const char *dict_uri;
- service_name = master_service_get_name(master_service);
dict_uri = mail_user_plugin_getenv(user, "expire_dict");
- if (strcmp(service_name, "expire-tool") == 0) {
- /* expire-tool handles all of this internally */
- } else if (mail_user_plugin_getenv(user, "expire") == NULL) {
+ if (mail_user_plugin_getenv(user, "expire") == NULL) {
if (user->mail_debug)
i_debug("expire: No expire setting - plugin disabled");
} else if (dict_uri == NULL) {
euser->module_ctx.super = user->v;
user->v.deinit = expire_mail_user_deinit;
- euser->env = expire_env_init(ns);
+ euser->set = expire_set_init(expire_get_patterns(user));
/* we're using only shared dictionary, the username
doesn't matter. */
euser->db = dict_init(dict_uri, DICT_DATA_TYPE_UINT32, "",
--- /dev/null
+/* Copyright (c) 2006-2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "imap-match.h"
+#include "expire-set.h"
+
+#include <stdlib.h>
+
+struct expire_set {
+ pool_t pool;
+ ARRAY_DEFINE(globs, struct imap_match_glob *);
+};
+
+struct expire_set *expire_set_init(const char *const *patterns)
+{
+ struct expire_set *set;
+ struct imap_match_glob *glob;
+ const char *const *pattern;
+ pool_t pool;
+
+ pool = pool_alloconly_create("Expire pool", 512);
+ set = p_new(pool, struct expire_set, 1);
+ set->pool = pool;
+ p_array_init(&set->globs, set->pool, 16);
+
+ for (pattern = patterns; *pattern != NULL; pattern++) {
+ glob = imap_match_init(set->pool, *pattern, TRUE, '/');
+ array_append(&set->globs, &glob, 1);
+ }
+ return set;
+}
+
+void expire_set_deinit(struct expire_set **_set)
+{
+ struct expire_set *set = *_set;
+
+ *_set = NULL;
+ pool_unref(&set->pool);
+}
+
+bool expire_set_lookup(struct expire_set *set, const char *mailbox)
+{
+ struct imap_match_glob *const *globp;
+
+ array_foreach(&set->globs, globp) {
+ if (imap_match(*globp, mailbox) == IMAP_MATCH_YES)
+ return TRUE;
+ }
+ return FALSE;
+}
--- /dev/null
+#ifndef EXPIRE_SET_H
+#define EXPIRE_SET_H
+
+#define DICT_EXPIRE_PREFIX DICT_PATH_SHARED"expire/"
+
+struct expire_set *expire_set_init(const char *const *patterns);
+void expire_set_deinit(struct expire_set **set);
+
+bool expire_set_lookup(struct expire_set *set, const char *mailbox);
+
+#endif
+++ /dev/null
-/* Copyright (c) 2006-2010 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "env-util.h"
-#include "dict.h"
-#include "master-service.h"
-#include "master-service-settings.h"
-#include "mail-index.h"
-#include "mail-search-build.h"
-#include "mail-storage.h"
-#include "mail-storage-service.h"
-#include "mail-namespace.h"
-#include "auth-client.h"
-#include "auth-master.h"
-#include "expire-env.h"
-
-#include <stdlib.h>
-#include <time.h>
-
-struct expire_context {
- struct mail_storage_service_ctx *storage_service;
- struct mail_storage_service_user *service_user;
- struct mail_user *mail_user;
- struct expire_env *env;
- bool testrun;
- bool userdb_lookup_failed;
-};
-
-static int expire_init_user(struct expire_context *ctx, const char *user)
-{
- struct mail_storage_service_input input;
- const char *errstr;
- int ret;
-
- i_set_failure_prefix(t_strdup_printf("expire-tool(%s): ", user));
-
- memset(&input, 0, sizeof(input));
- input.service = "expire-tool";
- input.username = user;
-
- ret = mail_storage_service_lookup_next(ctx->storage_service, &input,
- &ctx->service_user,
- &ctx->mail_user, &errstr);
- if (ret <= 0) {
- if (ret < 0 || ctx->testrun)
- i_error("%s", errstr);
- if (ret == -1) {
- /* the next userdb lookup is most likely
- going to fail too */
- ctx->userdb_lookup_failed = TRUE;
- }
- return ret < 0 ? -1 : 0;
- }
-
- if (mail_user_set_plugin_getenv(ctx->mail_user->set, "expire") == NULL)
- i_fatal("expire settings not set");
-
- ctx->env = expire_env_init(ctx->mail_user->namespaces);
- return 1;
-}
-
-static void expire_deinit_user(struct expire_context *ctx)
-{
- mail_user_unref(&ctx->mail_user);
- mail_storage_service_user_free(&ctx->service_user);
- expire_env_deinit(&ctx->env);
-}
-
-static int
-mailbox_delete_old_mails(struct expire_context *ctx, const char *user,
- const char *mailbox, time_t *next_expire_r)
-{
- struct mail_namespace *ns;
- struct mailbox *box;
- struct mail_search_context *search_ctx;
- struct mailbox_transaction_context *t;
- struct mail_search_args *search_args;
- struct mail *mail;
- const char *ns_mailbox, *errstr;
- unsigned int expunge_secs, altmove_secs;
- time_t now, save_time;
- enum mail_error error;
- enum mail_flags flags;
- int ret;
-
- *next_expire_r = 0;
-
- if (ctx->mail_user != NULL &&
- strcmp(user, ctx->mail_user->username) != 0)
- expire_deinit_user(ctx);
- if (ctx->mail_user == NULL) {
- if ((ret = expire_init_user(ctx, user)) <= 0)
- return ret;
- }
-
- if (!expire_rule_find(ctx->env, mailbox, &expunge_secs, &altmove_secs)) {
- /* we're no longer expunging old messages from here */
- if (ctx->testrun) {
- i_info("%s: mailbox '%s' removed from config",
- user, mailbox);
- }
- return 0;
- }
-
- ns_mailbox = mailbox;
- ns = mail_namespace_find(ctx->mail_user->namespaces, &ns_mailbox);
- if (ns == NULL) {
- /* entire namespace no longer exists, remove the entry */
- if (ctx->testrun)
- i_info("Namespace lookup failed: %s", mailbox);
- return 0;
- }
-
- box = mailbox_alloc(ns->list, ns_mailbox, 0);
- if (mailbox_open(box) < 0) {
- errstr = mail_storage_get_last_error(mailbox_get_storage(box),
- &error);
- mailbox_free(&box);
- if (error != MAIL_ERROR_NOTFOUND) {
- i_error("%s: Opening mailbox %s failed: %s",
- user, mailbox, errstr);
- return -1;
- }
-
- /* mailbox no longer exists, remove the entry */
- return 0;
- }
-
- search_args = mail_search_build_init();
- mail_search_build_add_all(search_args);
-
- t = mailbox_transaction_begin(box, 0);
- search_ctx = mailbox_search_init(t, search_args, NULL);
- mail_search_args_unref(&search_args);
-
- mail = mail_alloc(t, 0, NULL);
-
- now = time(NULL); ret = 0;
- while (mailbox_search_next(search_ctx, mail)) {
- if (mail_get_save_date(mail, &save_time) < 0) {
- /* maybe just got expunged. anyway try again later. */
- if (ctx->testrun) {
- i_info("%s/%s: seq=%u uid=%u: "
- "Save date lookup failed",
- user, mailbox, mail->seq, mail->uid);
- }
- ret = -1;
- break;
- }
-
- if (save_time + (time_t)expunge_secs <= now &&
- expunge_secs != 0) {
- if (!ctx->testrun)
- mail_expunge(mail);
- else {
- i_info("%s/%s: seq=%u uid=%u: Expunge "
- "(saved=%lu + expire=%u <= now=%lu)",
- user, mailbox, mail->seq, mail->uid,
- (unsigned long)save_time, expunge_secs,
- (unsigned long)now);
- }
- } else if (save_time + (time_t)altmove_secs <= now &&
- altmove_secs != 0) {
- /* works only with dbox */
- flags = mail_get_flags(mail);
- if ((flags & MAIL_INDEX_MAIL_FLAG_BACKEND) != 0) {
- /* alread moved */
- } else if (!ctx->testrun) {
- mail_update_flags(mail, MODIFY_ADD,
- MAIL_INDEX_MAIL_FLAG_BACKEND);
- } else {
- i_info("%s/%s: seq=%u uid=%u: Move to alt dir"
- "(saved=%lu + altmove=%u <= now=%lu)",
- user, mailbox, mail->seq, mail->uid,
- (unsigned long)save_time, altmove_secs,
- (unsigned long)now);
- }
- } else {
- /* first non-expired one. */
- *next_expire_r = save_time +
- (altmove_secs != 0 ?
- altmove_secs : expunge_secs);
- break;
- }
- }
- mail_free(&mail);
-
- if (mailbox_search_deinit(&search_ctx) < 0)
- ret = -1;
- if (!ctx->testrun) {
- if (mailbox_transaction_commit(&t) < 0)
- ret = -1;
- } else {
- mailbox_transaction_rollback(&t);
- }
-
- if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FAST) < 0)
- ret = -1;
-
- mailbox_free(&box);
- return ret;
-}
-
-static void expire_run(struct master_service *service, bool testrun)
-{
- struct expire_context ctx;
- struct dict *dict = NULL;
- const struct mail_user_settings *user_set;
- struct mail_storage_service_input input;
- void **sets;
- struct dict_transaction_context *trans;
- struct dict_iterate_context *iter;
- time_t next_expire, expire_time;
- const char *p, *key, *value, *expire_dict;
- const char *userp = NULL, *mailbox;
- int ret;
-
- memset(&ctx, 0, sizeof(ctx));
- ctx.storage_service = mail_storage_service_init(service, NULL,
- MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP);
-
- memset(&input, 0, sizeof(input));
- input.service = "expire-tool";
- input.module = "mail";
- mail_storage_service_init_settings(ctx.storage_service, &input);
-
- sets = master_service_settings_get_others(service);
- user_set = sets[0];
-
- expire_dict = mail_user_set_plugin_getenv(user_set, "expire_dict");
- if (expire_dict == NULL)
- i_fatal("expire_dict setting not set");
-
- ctx.testrun = testrun;
- dict = dict_init(expire_dict, DICT_DATA_TYPE_UINT32, "",
- user_set->base_dir);
- if (dict == NULL)
- i_fatal("dict_init() failed");
-
- trans = dict_transaction_begin(dict);
- iter = dict_iterate_init(dict, DICT_EXPIRE_PREFIX,
- DICT_ITERATE_FLAG_RECURSE |
- DICT_ITERATE_FLAG_SORT_BY_VALUE);
-
- /* We'll get the oldest values (timestamps) first */
- while (dict_iterate(iter, &key, &value)) {
- /* key = DICT_EXPIRE_PREFIX<user>/<mailbox> */
- userp = key + strlen(DICT_EXPIRE_PREFIX);
-
- p = strchr(userp, '/');
- if (p == NULL) {
- i_error("Expire dictionary contains invalid key: %s",
- key);
- continue;
- }
-
- mailbox = p + 1;
- expire_time = strtoul(value, NULL, 10);
- if (time(NULL) < expire_time) {
- /* this and the rest of the timestamps are in future,
- so stop processing */
- if (testrun) {
- i_info("%s: stop, expire time in future: %s",
- userp, ctime(&expire_time));
- }
- break;
- }
-
- T_BEGIN {
- const char *username;
-
- username = t_strdup_until(userp, p);
- ret = mailbox_delete_old_mails(&ctx, username,
- mailbox, &next_expire);
- } T_END;
-
- if (ret < 0) {
- if (ctx.userdb_lookup_failed)
- break;
- /* failed to update */
- } else if (next_expire == 0) {
- /* no more messages or mailbox deleted */
- if (!testrun)
- dict_unset(trans, key);
- else
- i_info("%s: no messages left", userp);
- } else {
- char new_value[MAX_INT_STRLEN];
-
- i_snprintf(new_value, sizeof(new_value), "%lu",
- (unsigned long)next_expire);
- if (strcmp(value, new_value) == 0) {
- /* no change */
- } else if (!testrun)
- dict_set(trans, key, new_value);
- else T_BEGIN {
- i_info("%s: timestamp %s (%s) -> %s (%s)",
- userp, value,
- t_strcut(ctime(&expire_time), '\n'),
- new_value,
- t_strcut(ctime(&next_expire), '\n'));
- } T_END;
- }
- }
- if (dict_iterate_deinit(&iter) < 0)
- i_error("Dictionary iteration failed");
- if (testrun && userp == NULL)
- i_info("No entries in dictionary");
-
- if (!testrun)
- dict_transaction_commit(&trans);
- else
- dict_transaction_rollback(&trans);
- dict_deinit(&dict);
-
- if (ctx.mail_user != NULL)
- expire_deinit_user(&ctx);
- mail_storage_service_deinit(&ctx.storage_service);
-}
-
-int main(int argc, char *argv[])
-{
- bool test = FALSE;
- int c;
-
- master_service = master_service_init("expire-tool",
- MASTER_SERVICE_FLAG_STANDALONE,
- &argc, &argv, "t");
-
- while ((c = master_getopt(master_service)) > 0) {
- switch (c) {
- case 't':
- test = TRUE;
- break;
- default:
- return FATAL_DEFAULT;
- }
- }
- if (optind != argc)
- i_fatal("Unknown parameter: %s", argv[optind]);
-
- expire_run(master_service, test);
-
- master_service_deinit(&master_service);
- return 0;
-}