]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
expire: Rewrote expire-tool as a plugin for doveadm.
authorTimo Sirainen <tss@iki.fi>
Thu, 27 May 2010 19:30:04 +0000 (20:30 +0100)
committerTimo Sirainen <tss@iki.fi>
Thu, 27 May 2010 19:30:04 +0000 (20:30 +0100)
Whenever using some doveadm mail command with -A parameter, the plugin checks
if it can get the list of potentially matching users from the expire database.

Currently only the list of users is filtered based on expire database, list of
mailboxes could also be filtered but this isn't supported (yet).

--HG--
branch : HEAD

src/plugins/expire/Makefile.am
src/plugins/expire/doveadm-expire.c [new file with mode: 0644]
src/plugins/expire/expire-env.c [deleted file]
src/plugins/expire/expire-env.h [deleted file]
src/plugins/expire/expire-plugin.c
src/plugins/expire/expire-set.c [new file with mode: 0644]
src/plugins/expire/expire-set.h [new file with mode: 0644]
src/plugins/expire/expire-tool.c [deleted file]

index 985ef5ceb0201b622a1ba8151eccca4d63c72246..4a214e75b0ea3d917e0bf5655e2b69a1a733ab1c 100644 (file)
@@ -1,4 +1,4 @@
-pkglibexecdir = $(libexecdir)/dovecot
+doveadm_moduledir = $(moduledir)/doveadm
 
 AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib \
@@ -10,35 +10,25 @@ AM_CPPFLAGS = \
        -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
diff --git a/src/plugins/expire/doveadm-expire.c b/src/plugins/expire/doveadm-expire.c
new file mode 100644 (file)
index 0000000..967eb32
--- /dev/null
@@ -0,0 +1,388 @@
+/* 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;
+}
diff --git a/src/plugins/expire/expire-env.c b/src/plugins/expire/expire-env.c
deleted file mode 100644 (file)
index f0b8e10..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-/* 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;
-       }
-}
diff --git a/src/plugins/expire/expire-env.h b/src/plugins/expire/expire-env.h
deleted file mode 100644 (file)
index c8dbec4..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#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
index 7bbe762a7d78d9de958cf1cbb3022ad16e7348c6..008f36c7ccfe197170cb37a72ea85145a3d71de8 100644 (file)
@@ -9,7 +9,7 @@
 #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>
@@ -25,13 +25,11 @@ struct expire_mail_user {
        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 {
@@ -105,9 +103,7 @@ expire_mailbox_transaction_commit(struct mailbox_transaction_context *t,
        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;
@@ -150,7 +146,6 @@ expire_mailbox_transaction_commit(struct mailbox_transaction_context *t,
                                /* everything expunged */
                                dict_unset(dctx, key);
                        } else {
-                               new_stamp += xpr_box->expire_secs;
                                dict_set(dctx, key, dec2str(new_stamp));
                        }
                        dict_transaction_commit(&dctx);
@@ -227,8 +222,7 @@ expire_copy(struct mail_save_context *ctx, struct mail *mail)
        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;
 
@@ -242,39 +236,16 @@ mailbox_expire_hook(struct mailbox *box, time_t expire_secs, bool altmove)
        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)
@@ -282,22 +253,38 @@ 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) {
@@ -307,7 +294,7 @@ static void expire_mail_namespaces_created(struct mail_namespace *ns)
                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, "",
diff --git a/src/plugins/expire/expire-set.c b/src/plugins/expire/expire-set.c
new file mode 100644 (file)
index 0000000..8b796f3
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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;
+}
diff --git a/src/plugins/expire/expire-set.h b/src/plugins/expire/expire-set.h
new file mode 100644 (file)
index 0000000..70f241b
--- /dev/null
@@ -0,0 +1,11 @@
+#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
diff --git a/src/plugins/expire/expire-tool.c b/src/plugins/expire/expire-tool.c
deleted file mode 100644 (file)
index 756af9e..0000000
+++ /dev/null
@@ -1,346 +0,0 @@
-/* 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;
-}