[dovecot-cvs] dovecot/src/plugins/expire .cvsignore, NONE, 1.1 Makefile.am, NONE, 1.1 auth-client.c, NONE, 1.1 auth-client.h, NONE, 1.1 expire-env.c, NONE, 1.1 expire-env.h, NONE, 1.1 expire-plugin.c, NONE, 1.1 expire-plugin.h, NONE, 1.1 expire-tool.c, NONE, 1.1

tss-movial at dovecot.org tss-movial at dovecot.org
Mon Jul 31 02:12:56 EEST 2006


Update of /var/lib/cvs/dovecot/src/plugins/expire
In directory talvi:/tmp/cvs-serv31018/src/plugins/expire

Added Files:
	.cvsignore Makefile.am auth-client.c auth-client.h 
	expire-env.c expire-env.h expire-plugin.c expire-plugin.h 
	expire-tool.c 
Log Message:
Initial import of expire plugin code. Seems to work with at least one user. :)



--- NEW FILE: .cvsignore ---
*.la
*.lo
*.o
.deps
.libs
Makefile
Makefile.in
so_locations
expire-tool

--- NEW FILE: Makefile.am ---
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

--- NEW FILE: auth-client.c ---
/* 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;
}

--- NEW FILE: auth-client.h ---
#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

--- NEW FILE: expire-env.c ---
/* 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;
}

--- NEW FILE: expire-env.h ---
#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

--- NEW FILE: expire-plugin.c ---
/* 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);
	}
}

--- NEW FILE: expire-plugin.h ---
#ifndef __EXPIRE_PLUGIN_H
#define __EXPIRE_PLUGIN_H

void expire_plugin_init(void);
void expire_plugin_deinit(void);

#endif

--- NEW FILE: expire-tool.c ---
/* 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;
}



More information about the dovecot-cvs mailing list