Mercurial > dovecot > original-hg > dovecot-1.2
changeset 4523:99699cf9df43 HEAD
Initial import of expire plugin code. Seems to work with at least one user. :)
author | Timo Sirainen <timo.sirainen@movial.fi> |
---|---|
date | Mon, 31 Jul 2006 02:12:51 +0300 |
parents | 706c0cb1c821 |
children | da7c51c193e2 |
files | configure.in dovecot-example.conf src/plugins/Makefile.am src/plugins/expire/.cvsignore src/plugins/expire/Makefile.am src/plugins/expire/auth-client.c src/plugins/expire/auth-client.h src/plugins/expire/expire-env.c src/plugins/expire/expire-env.h src/plugins/expire/expire-plugin.c src/plugins/expire/expire-plugin.h src/plugins/expire/expire-tool.c |
diffstat | 12 files changed, 968 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Mon Jul 31 02:07:46 2006 +0300 +++ b/configure.in Mon Jul 31 02:12:51 2006 +0300 @@ -1793,6 +1793,7 @@ src/plugins/Makefile src/plugins/acl/Makefile src/plugins/convert/Makefile +src/plugins/expire/Makefile src/plugins/quota/Makefile src/plugins/imap-quota/Makefile src/plugins/trash/Makefile
--- a/dovecot-example.conf Mon Jul 31 02:07:46 2006 +0300 +++ b/dovecot-example.conf Mon Jul 31 02:12:51 2006 +0300 @@ -1001,4 +1001,13 @@ # is a text file where each line is in format: <priority> <mailbox name> # Mails are first deleted in lowest -> highest priority number order #trash = /etc/dovecot-trash.conf + + # Expire plugin. Mails are expunged from mailboxes after being there the + # configurable time. The first expiration date for each mailbox is stored in + # a dictionary so it can be quickly determined which mailboxes contain + # expired mails. The actual expunging is done in a nightly cronjob, which + # you must set up: + # dovecot --exec-mail ext /usr/libexec/dovecot/expire-mails + #expire = Trash 7 Spam 30 + #expire_dict = db:/var/lib/dovecot/expire.db }
--- a/src/plugins/Makefile.am Mon Jul 31 02:07:46 2006 +0300 +++ b/src/plugins/Makefile.am Mon Jul 31 02:12:51 2006 +0300 @@ -2,4 +2,4 @@ ZLIB = zlib endif -SUBDIRS = acl convert quota imap-quota trash $(ZLIB) +SUBDIRS = acl convert expire quota imap-quota trash $(ZLIB)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/.cvsignore Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,9 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations +expire-tool
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/Makefile.am Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,55 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -DPKG_RUNDIR=\""$(rundir)"\" + +lib01_expire_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib01_expire_plugin.la + +lib01_expire_plugin_la_SOURCES = \ + expire-env.c \ + expire-plugin.c + +noinst_HEADERS = \ + auth-client.h \ + expire-env.h \ + expire-plugin.h + +noinst_PROGRAMS = expire-tool + +expire_tool_SOURCES = \ + auth-client.c \ + expire-tool.c + +libs = \ + $(top_builddir)/src/lib-storage/register/libstorage-register.a \ + $(STORAGE_LIBS) \ + $(top_builddir)/src/lib-storage/libstorage.a \ + $(top_builddir)/src/lib-storage/subscription-file/libstorage_subscription_file.a \ + $(top_builddir)/src/lib-imap/libimap.a \ + $(top_builddir)/src/lib-mail/libmail.a \ + $(top_builddir)/src/lib-dict/libdict.a \ + $(top_builddir)/src/lib-charset/libcharset.a \ + $(top_builddir)/src/lib/liblib.a + +expire_tool_LDADD = \ + $(libs) \ + $(LIBICONV) \ + $(RAND_LIBS) + +expire_tool_DEPENDENCIES = $(libs) + +install-exec-local: + $(mkdir_p) $(DESTDIR)$(moduledir)/imap \ + $(DESTDIR)$(moduledir)/pop3 \ + $(DESTDIR)$(moduledir)/lda + for d in imap pop3 lda; do \ + rm -f $(DESTDIR)$(moduledir)/$$d/lib01_expire_plugin.so; \ + $(LN_S) ../lib01_expire_plugin.so $(DESTDIR)$(moduledir)/$$d; \ + done
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/auth-client.c Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,221 @@ +/* Copyright (C) 2005-2006 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "network.h" +#include "istream.h" +#include "ostream.h" +#include "env-util.h" +#include "restrict-access.h" +#include "auth-client.h" + +#include <stdlib.h> +#include <unistd.h> + +#define MAX_INBUF_SIZE 8192 +#define MAX_OUTBUF_SIZE 512 + +struct auth_connection { + char *auth_socket; + + int fd; + struct io *io; + struct istream *input; + struct ostream *output; + + uid_t orig_uid, current_uid; + const char *current_user; + int return_value; + + unsigned int handshaked:1; +}; + +static void auth_input(void *context); + +static int auth_connection_connect(struct auth_connection *conn) +{ + int fd; + + if (conn->fd != -1) + return 0; + + fd = net_connect_unix(conn->auth_socket); + if (fd < 0) { + i_error("net_connect(%s) failed: %m", conn->auth_socket); + return -1; + } + + conn->fd = fd; + conn->input = + i_stream_create_file(fd, default_pool, MAX_INBUF_SIZE, FALSE); + conn->output = + o_stream_create_file(fd, default_pool, MAX_OUTBUF_SIZE, FALSE); + conn->io = io_add(fd, IO_READ, auth_input, conn); + + o_stream_send_str(conn->output, "VERSION\t1\t0\n"); + return 0; +} + +static void auth_connection_close(struct auth_connection *conn) +{ + if (conn->fd == -1) + return; + + io_remove(&conn->io); + i_stream_unref(&conn->input); + o_stream_unref(&conn->output); + + if (close(conn->fd) < 0) + i_error("close() failed: %m"); + conn->fd = -1; +} + +struct auth_connection *auth_connection_init(const char *auth_socket) +{ + struct auth_connection *conn; + + conn = i_new(struct auth_connection, 1); + conn->auth_socket = i_strdup(auth_socket); + conn->orig_uid = conn->current_uid = geteuid(); + conn->fd = -1; + + (void)auth_connection_connect(conn); + return conn; +} + +void auth_connection_deinit(struct auth_connection *conn) +{ + auth_connection_close(conn); + i_free(conn->auth_socket); + i_free(conn); +} + +static void auth_parse_input(struct auth_connection *conn, const char *args) +{ + const char *const *tmp, *key, *value; + uid_t uid = (uid_t)-1; + int home_found = FALSE; + + for (tmp = t_strsplit(args, "\t"); *tmp != NULL; tmp++) { + if (strncmp(*tmp, "uid=", 4) == 0) + uid = strtoul(*tmp + 4, NULL, 10); + else if (strncmp(*tmp, "gid=", 4) == 0) { + gid_t gid = strtoul(*tmp + 4, NULL, 10); + + if (conn->orig_uid == 0 || getegid() != gid) { + env_put(t_strconcat("RESTRICT_SETGID=", + *tmp + 4, NULL)); + } + } else if (strncmp(*tmp, "chroot=", 7) == 0) { + env_put(t_strconcat("RESTRICT_CHROOT=", + *tmp + 7, NULL)); + } else if (strncmp(*tmp, "home=", 5) == 0) { + home_found = TRUE; + env_put(t_strconcat("HOME=", *tmp + 5, NULL)); + } else { + key = t_str_ucase(t_strcut(*tmp, '=')); + value = strchr(*tmp, '='); + if (value != NULL) + env_put(t_strconcat(key, "=", value+1, NULL)); + } + } + + if (!home_found) { + /* we must have a home directory */ + i_error("userdb(%s) didn't return a home directory", + conn->current_user); + return; + } + + if (uid == (uid_t)-1) { + i_error("userdb(%s) didn't return uid", conn->current_user); + return; + } + + /* we'll change only effective UID. This is a bit unfortunate since + it allows reverting back to root, but we'll have to be able to + access different users' mailboxes.. */ + if (uid != conn->current_uid) { + if (conn->current_uid != 0) { + if (seteuid(0) != 0) + i_fatal("seteuid(0) failed: %m"); + } + if (seteuid(uid) < 0) + i_fatal("seteuid(%s) failed: %m", dec2str(uid)); + conn->current_uid = uid; + } + + restrict_access_by_env(FALSE); + conn->return_value = 1; +} + +static void auth_input(void *context) +{ + struct auth_connection *conn = context; + const char *line; + + switch (i_stream_read(conn->input)) { + case 0: + return; + case -1: + /* disconnected */ + auth_connection_close(conn); + return; + case -2: + /* buffer full */ + i_error("BUG: Auth master sent us more than %d bytes", + MAX_INBUF_SIZE); + auth_connection_close(conn); + return; + } + + if (!conn->handshaked) { + while ((line = i_stream_next_line(conn->input)) != NULL) { + if (strncmp(line, "VERSION\t", 8) == 0) { + if (strncmp(line + 8, "1\t", 2) != 0) { + i_error("Auth master version mismatch"); + auth_connection_close(conn); + return; + } + } else if (strncmp(line, "SPID\t", 5) == 0) { + conn->handshaked = TRUE; + break; + } + } + } + + line = i_stream_next_line(conn->input); + if (line != NULL) { + if (strncmp(line, "USER\t1\t", 7) == 0) { + auth_parse_input(conn, line + 7); + } else if (strcmp(line, "NOTFOUND\t1") == 0) + conn->return_value = 0; + else if (strncmp(line, "FAIL\t1\t", 7) == 0) + conn->return_value = -1; + else { + i_error("BUG: Unexpected input from auth master: %s", + line); + auth_connection_close(conn); + } + io_loop_stop(current_ioloop); + } +} + +int auth_client_put_user_env(struct auth_connection *conn, + const char *user) +{ + if (auth_connection_connect(conn) < 0) + return -1; + + conn->current_user = user; + conn->return_value = -1; + + o_stream_send_str(conn->output, + t_strconcat("USER\t1\t", user, "\t" + "service=expire\n", NULL)); + + io_loop_run(current_ioloop); + + conn->current_user = NULL; + return conn->return_value; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/auth-client.h Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,11 @@ +#ifndef __AUTH_CLIENT_H +#define __AUTH_CLIENT_H + +struct auth_connection *auth_connection_init(const char *auth_socket); +void auth_connection_deinit(struct auth_connection *conn); + +/* Returns -1 = error, 0 = user not found, 1 = ok */ +int auth_client_put_user_env(struct auth_connection *conn, + const char *user); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/expire-env.c Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,61 @@ +/* Copyright (C) 2006 PT.COM / SAPO. Code by Timo Sirainen. */ + +#include "lib.h" +#include "array.h" +#include "expire-env.h" + +#include <stdlib.h> + +struct expire_env { + pool_t pool; + ARRAY_DEFINE(expire_boxes, struct expire_box); +}; + +struct expire_env *expire_env_init(const char *str) +{ + struct expire_env *env; + struct expire_box box; + pool_t pool; + char *const *names; + unsigned int len; + + pool = pool_alloconly_create("Expire pool", 512); + env = p_new(pool, struct expire_env, 1); + env->pool = pool; + + names = p_strsplit(pool, str, " "); + len = strarray_length((const char *const *)names); + + ARRAY_CREATE(&env->expire_boxes, pool, struct expire_box, len / 2); + for (; *names != NULL; names += 2) { + if (names[1] == NULL) { + i_fatal("expire: Missing expire days for mailbox '%s'", + *names); + } + + box.name = *names; + box.expire_secs = strtoul(names[1], NULL, 10) * 3600 * 24; + array_append(&env->expire_boxes, &box, 1); + } + + return env; +} + +void expire_env_deinit(struct expire_env *env) +{ + pool_unref(env->pool); +} + +const struct expire_box *expire_box_find(struct expire_env *env, + const char *name) +{ + const struct expire_box *expire_boxes; + unsigned int i, count; + + expire_boxes = array_get(&env->expire_boxes, &count); + for (i = 0; i < count; i++) { + if (strcmp(name, expire_boxes[i].name) == 0) + return &expire_boxes[i]; + } + return NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/expire-env.h Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,17 @@ +#ifndef __EXPIRE_ENV_H +#define __EXPIRE_ENV_H + +struct expire_env; + +struct expire_box { + const char *name; + time_t expire_secs; +}; + +struct expire_env *expire_env_init(const char *str); +void expire_env_deinit(struct expire_env *env); + +const struct expire_box *expire_box_find(struct expire_env *env, + const char *name); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/expire-plugin.c Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,346 @@ +/* Copyright (C) 2006 PT.COM / SAPO. Code by Tianyan Liu */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "dict.h" +#include "index-mail.h" +#include "index-storage.h" +#include "expire-env.h" +#include "expire-plugin.h" + +#include <stdlib.h> + +#define EXPIRE_CONTEXT(obj) \ + *((void **)array_idx_modifiable(&(obj)->module_contexts, \ + expire.storage_module_id)) + +struct expire { + struct dict *db; + struct expire_env *env; + const char *username; + + unsigned int storage_module_id; + bool storage_module_id_set; + + void (*next_hook_mail_storage_created)(struct mail_storage *storage); +}; + +struct expire_mail_storage { + struct mail_storage_vfuncs super; +}; + +struct expire_mailbox { + struct mailbox_vfuncs super; + time_t expire_secs; +}; + +struct expire_mail { + struct mail_vfuncs super; +}; + +struct expire_transaction_context { + struct mail *mail; + time_t first_save_time; + + unsigned int first_expunged:1; +}; + +/* defined by imap, pop3, lda */ +extern void (*hook_mail_storage_created)(struct mail_storage *storage); + +static struct expire expire; + +static struct mailbox_transaction_context * +expire_mailbox_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags) +{ + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(box); + struct mailbox_transaction_context *t; + struct expire_transaction_context *xt; + + t = xpr_box->super.transaction_begin(box, flags); + xt = i_new(struct expire_transaction_context, 1); + xt->mail = mail_alloc(t, 0, NULL); + + array_idx_set(&t->module_contexts, expire.storage_module_id, &xt); + return t; +} + +static int first_nonexpunged_timestamp(struct mailbox_transaction_context *_t, + time_t *stamp_r) +{ + struct index_transaction_context *t = + (struct index_transaction_context *)_t; + struct expire_transaction_context *xt = EXPIRE_CONTEXT(_t); + struct mail_index_view *view = t->trans_view; + const struct mail_index_header *hdr; + const struct mail_index_record *rec; + uint32_t seq; + int ret = 0; + + /* find the first non-expunged mail. we're here because the first + mail was expunged, so don't bother checking it. */ + hdr = mail_index_get_header(view); + for (seq = 2; seq <= hdr->messages_count; seq++) { + ret = mail_index_lookup(view, seq, &rec); + if (ret != 0) + break; + } + if (ret < 0) { + *stamp_r = 0; + return -1; + } + + if (ret > 0) { + mail_set_seq(xt->mail, seq); + *stamp_r = mail_get_save_date(xt->mail); + if (*stamp_r == (time_t)-1) + return -1; + } else { + /* everything expunged */ + *stamp_r = 0; + } + return 0; +} + +static int +expire_mailbox_transaction_commit(struct mailbox_transaction_context *t, + enum mailbox_sync_flags flags) +{ + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box); + struct expire_transaction_context *xt = EXPIRE_CONTEXT(t); + const char *key, *value; + time_t new_stamp; + bool update_dict; + int ret; + + t_push(); + key = t_strconcat(DICT_PATH_SHARED, expire.username, "/", + t->box->name, NULL); + + if (xt->first_expunged) { + /* first mail expunged. dict needs updating. */ + update_dict = first_nonexpunged_timestamp(t, &new_stamp) == 0; + } else { + /* saved new mails. dict needs to be updated only if this is + the first mail in the database */ + ret = dict_lookup(expire.db, pool_datastack_create(), + key, &value); + update_dict = ret == 0 || strtoul(value, NULL, 10) == 0; + new_stamp = xt->first_save_time; + } + + mail_free(&xt->mail); + i_free(xt); + + if (xpr_box->super.transaction_commit(t, flags) < 0) { + t_pop(); + return -1; + } + + if (update_dict) { + struct dict_transaction_context *dctx; + + new_stamp += xpr_box->expire_secs; + + dctx = dict_transaction_begin(expire.db); + dict_set(dctx, key, dec2str(new_stamp)); + dict_transaction_commit(dctx); + } + t_pop(); + return 0; +} + +static void +expire_mailbox_transaction_rollback(struct mailbox_transaction_context *t) +{ + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box); + struct expire_transaction_context *xt = EXPIRE_CONTEXT(t); + + mail_free(&xt->mail); + + xpr_box->super.transaction_rollback(t); + i_free(xt); +} + +static int expire_mail_expunge(struct mail *_mail) +{ + struct mail_private *mail = (struct mail_private *)_mail; + struct expire_mail *xpr_mail = EXPIRE_CONTEXT(mail); + struct expire_transaction_context *xt = + EXPIRE_CONTEXT(_mail->transaction); + + if (xpr_mail->super.expunge(_mail) < 0) + return -1; + + if (_mail->seq == 1) { + /* first mail expunged, database needs to be updated */ + xt->first_expunged = TRUE; + } + return 0; +} + +static struct mail * +expire_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box); + struct expire_mail *xpr_mail; + struct mail *_mail; + struct mail_private *mail; + + _mail = xpr_box->super.mail_alloc(t, wanted_fields, wanted_headers); + mail = (struct mail_private *)_mail; + + xpr_mail = p_new(mail->pool, struct expire_mail, 1); + xpr_mail->super = mail->v; + + mail->v.expunge = expire_mail_expunge; + array_idx_set(&mail->module_contexts, expire.storage_module_id, + &xpr_mail); + return _mail; +} + +static void +mail_set_save_time(struct mailbox_transaction_context *t, uint32_t seq) +{ + struct expire_transaction_context *xt = EXPIRE_CONTEXT(t); + struct index_transaction_context *it = + (struct index_transaction_context *)t; + + if (xt->first_save_time == 0) + xt->first_save_time = ioloop_time; + + mail_cache_add(it->cache_trans, seq, MAIL_CACHE_SAVE_DATE, + &ioloop_time, sizeof(ioloop_time)); +} + +static int +expire_save_init(struct mailbox_transaction_context *t, + enum mail_flags flags, struct mail_keywords *keywords, + time_t received_date, int timezone_offset, + const char *from_envelope, struct istream *input, + struct mail *dest_mail, struct mail_save_context **ctx_r) +{ + struct expire_transaction_context *xt = EXPIRE_CONTEXT(t); + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box); + int ret; + + if (dest_mail == NULL) + dest_mail = xt->mail; + + ret = xpr_box->super.save_init(t, flags, keywords, received_date, + timezone_offset, from_envelope, input, + dest_mail, ctx_r); + if (ret >= 0) + mail_set_save_time(t, dest_mail->seq); + return ret; +} + +static int +expire_copy(struct mailbox_transaction_context *t, struct mail *mail, + enum mail_flags flags, struct mail_keywords *keywords, + struct mail *dest_mail) +{ + struct expire_transaction_context *xt = EXPIRE_CONTEXT(t); + struct expire_mailbox *xpr_box = EXPIRE_CONTEXT(t->box); + int ret; + + if (dest_mail == NULL) + dest_mail = xt->mail; + + ret = xpr_box->super.copy(t, mail, flags, keywords, dest_mail); + if (ret >= 0) + mail_set_save_time(t, dest_mail->seq); + return ret; +} + +static void mailbox_expire_hook(struct mailbox *box, time_t expire_secs) +{ + struct expire_mailbox *xpr_box; + + xpr_box = p_new(box->pool, struct expire_mailbox, 1); + xpr_box->super = box->v; + + box->v.transaction_begin = expire_mailbox_transaction_begin; + box->v.transaction_commit = expire_mailbox_transaction_commit; + box->v.transaction_rollback = expire_mailbox_transaction_rollback; + box->v.mail_alloc = expire_mail_alloc; + box->v.save_init = expire_save_init; + box->v.copy = expire_copy; + + xpr_box->expire_secs = expire_secs; + + array_idx_set(&box->module_contexts, + expire.storage_module_id, &xpr_box); +} + +static struct mailbox * +expire_mailbox_open(struct mail_storage *storage, const char *name, + struct istream *input, enum mailbox_open_flags flags) +{ + struct expire_mail_storage *xpr_storage = EXPIRE_CONTEXT(storage); + struct mailbox *box; + const struct expire_box *expire_box; + + box = xpr_storage->super.mailbox_open(storage, name, input, flags); + if (box != NULL) { + expire_box = expire_box_find(expire.env, name); + if (expire_box != NULL) + mailbox_expire_hook(box, expire_box->expire_secs); + } + return box; +} + +static void expire_mail_storage_created(struct mail_storage *storage) +{ + struct expire_mail_storage *xpr_storage; + + if (expire.next_hook_mail_storage_created != NULL) + expire.next_hook_mail_storage_created(storage); + + xpr_storage = p_new(storage->pool, struct expire_mail_storage, 1); + xpr_storage->super = storage->v; + storage->v.mailbox_open = expire_mailbox_open; + + if (!expire.storage_module_id_set) { + expire.storage_module_id = mail_storage_module_id++; + expire.storage_module_id_set = TRUE; + } + + array_idx_set(&storage->module_contexts, + expire.storage_module_id, &xpr_storage); +} + +void expire_plugin_init(void) +{ + const char *env, *dict_uri; + + env = getenv("EXPIRE"); + if (env != NULL) { + dict_uri = getenv("EXPIRE_DICT"); + if (dict_uri == NULL) + i_fatal("expire plugin: expire_dict setting missing"); + + expire.env = expire_env_init(env); + expire.db = dict_init(dict_uri, DICT_DATA_TYPE_UINT32, NULL); + expire.username = getenv("USER"); + + expire.next_hook_mail_storage_created = + hook_mail_storage_created; + hook_mail_storage_created = expire_mail_storage_created; + } +} + +void expire_plugin_deinit(void) +{ + if (expire.db != NULL) { + hook_mail_storage_created = + expire.next_hook_mail_storage_created; + + dict_deinit(&expire.db); + expire_env_deinit(expire.env); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/expire-plugin.h Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,7 @@ +#ifndef __EXPIRE_PLUGIN_H +#define __EXPIRE_PLUGIN_H + +void expire_plugin_init(void); +void expire_plugin_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/expire/expire-tool.c Mon Jul 31 02:12:51 2006 +0300 @@ -0,0 +1,230 @@ +/* Copyright (C) 2006 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "randgen.h" +#include "lib-signals.h" +#include "dict-client.h" +#include "mail-search.h" +#include "mail-storage.h" +#include "auth-client.h" +#include "expire-env.h" + +#include <stdlib.h> + +/* ugly, but automake doesn't like having it built as both static and + dynamic object.. */ +#include "expire-env.c" + +#define DEFAULT_AUTH_SOCKET_PATH PKG_RUNDIR"/auth-master" + +struct expire_context { + struct auth_connection *auth_conn; + + char *user; + struct mail_storage *storage; +}; + +static int user_init(struct expire_context *ctx, const char *user) +{ + enum mail_storage_flags flags; + enum mail_storage_lock_method lock_method; + const char *mail_env; + int ret; + + if ((ret = auth_client_put_user_env(ctx->auth_conn, user)) <= 0) { + if (ret < 0) + return ret; + + /* user no longer exists */ + return 0; + } + + mail_env = getenv("MAIL"); + mail_storage_parse_env(&flags, &lock_method); + ctx->storage = mail_storage_create_with_data(mail_env, user, + flags, lock_method); + if (ctx->storage == NULL) { + i_error("Failed to create storage for '%s' with mail '%s'", + user, mail_env == NULL ? "(null)" : mail_env); + return -1; + } + return 1; +} + +static void user_deinit(struct expire_context *ctx) +{ + mail_storage_destroy(&ctx->storage); + i_free_and_null(ctx->user); +} + +static int +mailbox_delete_old_mails(struct expire_context *ctx, const char *user, + const char *mailbox, time_t expire_secs, + time_t *oldest_r) +{ + struct mailbox *box; + struct mail_search_context *search_ctx; + struct mailbox_transaction_context *t; + struct mail_search_arg search_arg; + struct mail *mail; + time_t now, save_time; + int ret = 0; + + *oldest_r = 0; + + if (ctx->user != NULL && strcmp(user, ctx->user) != 0) + user_deinit(ctx); + if (ctx->user == NULL) { + if ((ret = user_init(ctx, user)) <= 0) + return ret; + ctx->user = i_strdup(user); + } + + memset(&search_arg, 0, sizeof(search_arg)); + search_arg.type = SEARCH_ALL; + search_arg.next = NULL; + + box = mailbox_open(ctx->storage, mailbox, NULL, 0); + t = mailbox_transaction_begin(box, 0); + search_ctx = mailbox_search_init(t, NULL, &search_arg, NULL); + mail = mail_alloc(t, 0, NULL); + + now = time(NULL); + while (mailbox_search_next(search_ctx, mail) > 0) { + save_time = mail_get_save_date(mail); + if (save_time == (time_t)-1) { + /* maybe just got expunged. anyway try again later. */ + ret = -1; + break; + } + + if (save_time + expire_secs <= now) { + if (mail_expunge(mail) < 0) { + ret = -1; + break; + } + } else { + /* first non-expunged one. */ + *oldest_r = save_time; + break; + } + } + mail_free(&mail); + + if (mailbox_search_deinit(&search_ctx) < 0) + ret = -1; + if (mailbox_transaction_commit(&t, MAILBOX_SYNC_FLAG_FULL_READ | + MAILBOX_SYNC_FLAG_FULL_WRITE) < 0) + ret = -1; + mailbox_close(&box); + return ret < 0 ? -1 : 0; +} + +static void expire_run(void) +{ + struct expire_context ctx; + struct dict *dict = NULL; + struct dict_transaction_context *trans; + struct dict_iterate_context *iter; + struct expire_env *env; + const struct expire_box *expire_box; + time_t oldest; + const char *auth_socket, *p, *key, *value; + const char *username, *mailbox; + + dict_driver_register(&dict_driver_client); + mail_storage_init(); + mail_storage_register_all(); + + if (getenv("EXPIRE") == NULL) + i_fatal("expire setting not set"); + if (getenv("EXPIRE_DICT") == NULL) + i_fatal("expire_dict setting not set"); + + auth_socket = getenv("AUTH_SOCKET_PATH"); + if (auth_socket == NULL) + auth_socket = DEFAULT_AUTH_SOCKET_PATH; + + memset(&ctx, 0, sizeof(ctx)); + ctx.auth_conn = auth_connection_init(auth_socket); + env = expire_env_init(getenv("EXPIRE")); + dict = dict_init(getenv("EXPIRE_DICT"), DICT_DATA_TYPE_UINT32, ""); + trans = dict_transaction_begin(dict); + iter = dict_iterate_init(dict, DICT_PATH_SHARED, + DICT_ITERATE_FLAG_SORT_BY_VALUE); + + /* We'll get the oldest values (timestamps) first */ + while (dict_iterate(iter, &key, &value) > 0) { + /* key = DICT_PATH_SHARED<user>/<mailbox> */ + username = key + strlen(DICT_PATH_SHARED); + + p = strchr(username, '/'); + if (p == NULL) { + i_error("Expire dictionary contains invalid key: %s", + key); + continue; + } + + t_push(); + username = t_strdup_until(username, p); + mailbox = p + 1; + + expire_box = expire_box_find(env, mailbox); + if (expire_box == NULL) { + /* we're no longer expunging old messages from here */ + dict_unset(trans, key); + } else if (now < strtoul(value, NULL, 10)) { + /* this and the rest of the timestamps are in future, + so stop processing */ + t_pop(); + break; + } else { + if (mailbox_delete_old_mails(&ctx, username, mailbox, + expire_box->expire_secs, + &oldest) == 0) { + /* successful update */ + if (oldest == 0) { + /* no more messages or we're no longer + expunging messages from here */ + dict_unset(trans, key); + } else { + const char *new_value; + + oldest += expire_box->expire_secs; + new_value = dec2str(oldest); + if (strcmp(value, new_value) != 0) + dict_set(trans, key, new_value); + } + } + } + t_pop(); + } + dict_iterate_deinit(iter); + dict_transaction_commit(trans); + dict_deinit(&dict); + + if (ctx.user != NULL) + user_deinit(&ctx); + auth_connection_deinit(ctx.auth_conn); + + mail_storage_deinit(); + dict_driver_unregister(&dict_driver_client); +} + +int main(void) +{ + struct ioloop *ioloop; + + lib_init(); + lib_signals_init(); + random_init(); + + ioloop = io_loop_create(system_pool); + expire_run(); + io_loop_destroy(&ioloop); + + lib_signals_deinit(); + lib_deinit(); + return 0; +}