changeset 3738:732b62dc1976 HEAD

Added beginnings of plugin infrastructure. TODO: These could be optionally compiled into binaries with some configure options. Added quota plugin and a new trash plugin. Not very well tested.
author Timo Sirainen <tss@iki.fi>
date Sat, 10 Dec 2005 21:44:45 +0200
parents d67092398377
children ab929802ad6c
files configure.in src/Makefile.am src/plugins/.cvsignore src/plugins/Makefile.am src/plugins/imap-quota/.cvsignore src/plugins/imap-quota/Makefile.am src/plugins/imap-quota/imap-quota-plugin.c src/plugins/imap-quota/imap-quota-plugin.h src/plugins/quota/.cvsignore src/plugins/quota/Makefile.am src/plugins/quota/quota-dict.c src/plugins/quota/quota-dirsize.c src/plugins/quota/quota-plugin.c src/plugins/quota/quota-plugin.h src/plugins/quota/quota-private.h src/plugins/quota/quota-storage.c src/plugins/quota/quota.c src/plugins/quota/quota.h src/plugins/trash/.cvsignore src/plugins/trash/Makefile.am src/plugins/trash/trash-plugin.c src/plugins/trash/trash-plugin.h
diffstat 22 files changed, 1805 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Sat Dec 10 20:58:59 2005 +0200
+++ b/configure.in	Sat Dec 10 21:44:45 2005 +0200
@@ -1499,6 +1499,10 @@
 src/pop3-login/Makefile
 src/deliver/Makefile
 src/util/Makefile
+src/plugins/Makefile
+src/plugins/quota/Makefile
+src/plugins/imap-quota/Makefile
+src/plugins/trash/Makefile
 stamp.h
 dovecot.spec
 dovecot-config])
--- a/src/Makefile.am	Sat Dec 10 20:58:59 2005 +0200
+++ b/src/Makefile.am	Sat Dec 10 21:44:45 2005 +0200
@@ -25,4 +25,5 @@
 	imap \
 	$(POP3D) \
 	$(DELIVER) \
-	util
+	util \
+	plugins
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/.cvsignore	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/Makefile.am	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,1 @@
+SUBDIRS = quota imap-quota trash
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-quota/.cvsignore	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-quota/Makefile.am	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-imap \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/imap \
+	-I$(top_srcdir)/src/plugins/quota
+
+imap_moduledir = $(moduledir)/imap
+
+libimap_quota_plugin_la_LDFLAGS = -module -avoid-version
+
+imap_module_LTLIBRARIES = \
+	libimap_quota_plugin.la
+
+libimap_quota_plugin_la_SOURCES = \
+	imap-quota-plugin.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-quota/imap-quota-plugin.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,198 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "common.h"
+#include "str.h"
+#include "imap-quote.h"
+#include "commands.h"
+#include "quota.h"
+#include "quota-plugin.h"
+#include "imap-quota-plugin.h"
+
+#include <stdlib.h>
+
+static void
+quota_send(struct client_command_context *cmd, struct quota_root *root)
+{
+        const char *const *list;
+	string_t *str;
+	unsigned int i;
+	uint64_t value, limit;
+	int ret;
+
+	t_push();
+
+	str = t_str_new(128);
+	str_append(str, "* QUOTA ");
+	imap_quote_append_string(str, quota_root_get_name(root), FALSE);
+
+	str_append(str, " (");
+	list = quota_root_get_resources(root);
+	for (i = 0; *list != NULL; list++, i++) {
+		ret = quota_get_resource(root, *list, &value, &limit);
+		if (ret > 0) {
+			if (i > 0)
+				str_append_c(str, ' ');
+			str_printfa(str, "%s %llu %llu", *list,
+				    (unsigned long long)value,
+				    (unsigned long long)limit);
+		} else if (ret < 0) {
+			client_send_line(cmd->client, t_strconcat(
+				"* BAD ", quota_last_error(quota), NULL));
+		}
+	}
+	str_append_c(str, ')');
+	client_send_line(cmd->client, str_c(str));
+
+	t_pop();
+}
+
+static int cmd_getquotaroot(struct client_command_context *cmd)
+{
+	struct mail_storage *storage;
+	struct mailbox *box;
+	struct quota_root_iter *iter;
+        struct quota_root *root;
+	const char *mailbox;
+	string_t *str;
+
+	/* <mailbox> */
+	if (!client_read_string_args(cmd, 1, &mailbox))
+		return FALSE;
+
+	storage = client_find_storage(cmd, &mailbox);
+	if (storage == NULL)
+		return TRUE;
+
+	box = mailbox_open(storage, mailbox, NULL, (MAILBOX_OPEN_READONLY |
+						    MAILBOX_OPEN_FAST |
+						    MAILBOX_OPEN_KEEP_RECENT));
+	if (box == NULL) {
+		client_send_storage_error(cmd, storage);
+		return TRUE;
+	}
+
+	if (quota == NULL) {
+		client_send_tagline(cmd, "OK No quota.");
+		return TRUE;
+	}
+
+	/* send QUOTAROOT reply */
+	str = t_str_new(128);
+	str_append(str, "* QUOTAROOT ");
+	imap_quote_append_string(str, mailbox, FALSE);
+
+	iter = quota_root_iter_init(quota, box);
+	while ((root = quota_root_iter_next(iter)) != NULL) {
+		str_append_c(str, ' ');
+		imap_quote_append_string(str, quota_root_get_name(root), FALSE);
+	}
+	if (quota_root_iter_deinit(iter) < 0) {
+		/* some failure, send as untagged error */
+		client_send_line(cmd->client, t_strconcat(
+			"* BAD ", quota_last_error(quota), NULL));
+	}
+	client_send_line(cmd->client, str_c(str));
+
+	/* send QUOTA reply for each quotaroot */
+	iter = quota_root_iter_init(quota, box);
+	while ((root = quota_root_iter_next(iter)) != NULL)
+		quota_send(cmd, root);
+	if (quota_root_iter_deinit(iter) < 0) {
+		/* some failure, send as untagged error */
+		client_send_line(cmd->client, t_strconcat(
+			"* BAD ", quota_last_error(quota), NULL));
+	}
+
+	mailbox_close(box);
+
+	client_send_tagline(cmd, "OK Getquotaroot completed.");
+	return TRUE;
+}
+
+static int cmd_getquota(struct client_command_context *cmd)
+{
+	const char *root_name;
+        struct quota_root *root;
+
+	/* <quota root> */
+	if (!client_read_string_args(cmd, 1, &root_name))
+		return FALSE;
+
+	if (quota == NULL) {
+		client_send_tagline(cmd, "OK No quota.");
+		return TRUE;
+	}
+
+	root = quota_root_lookup(quota, root_name);
+	if (root == NULL) {
+		client_send_tagline(cmd, "NO Quota root doesn't exist.");
+		return TRUE;
+	}
+
+	quota_send(cmd, root);
+	client_send_tagline(cmd, "OK Getquota completed.");
+	return TRUE;
+}
+
+static int cmd_setquota(struct client_command_context *cmd)
+{
+	struct quota_root *root;
+        struct imap_arg *args, *arg;
+	const char *root_name, *name;
+	uint64_t value;
+
+	/* <quota root> <resource limits> */
+	if (!client_read_args(cmd, 2, 0, &args))
+		return FALSE;
+
+	root_name = imap_arg_string(&args[0]);
+	if (args[1].type != IMAP_ARG_LIST || root_name == NULL) {
+		client_send_command_error(cmd, "Invalid arguments.");
+		return TRUE;
+	}
+
+	if (quota == NULL) {
+		client_send_tagline(cmd, "OK No quota.");
+		return TRUE;
+	}
+
+	root = quota_root_lookup(quota, root_name);
+	if (root == NULL) {
+		client_send_tagline(cmd, "NO Quota root doesn't exist.");
+		return TRUE;
+	}
+
+        arg = IMAP_ARG_LIST(&args[1])->args;
+	for (; arg->type != IMAP_ARG_EOL; arg += 2) {
+		name = imap_arg_string(arg);
+		if (name == NULL || arg[1].type != IMAP_ARG_ATOM ||
+		    !is_numeric(IMAP_ARG_STR(&arg[1]), '\0')) {
+			client_send_command_error(cmd, "Invalid arguments.");
+			return TRUE;
+		}
+
+                value = strtoull(IMAP_ARG_STR_NONULL(&arg[1]), NULL, 10);
+		if (quota_set_resource(root, name, value) < 0) {
+			client_send_command_error(cmd,
+						  quota_last_error(quota));
+			return TRUE;
+		}
+	}
+
+	client_send_tagline(cmd, "OK Setquota completed.");
+	return TRUE;
+}
+
+void imap_quota_init(void)
+{
+	command_register("GETQUOTAROOT", cmd_getquotaroot);
+	command_register("GETQUOTA", cmd_getquota);
+	command_register("SETQUOTA", cmd_setquota);
+}
+
+void imap_quota_deinit(void)
+{
+	command_unregister("GETQUOTAROOT");
+	command_unregister("GETQUOTA");
+	command_unregister("SETQUOTA");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/imap-quota/imap-quota-plugin.h	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,7 @@
+#ifndef __IMAP_QUOTA_PLUGIN_H
+#define __IMAP_QUOTA_PLUGIN_H
+
+void imap_quota_init(void);
+void imap_quota_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/.cvsignore	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/Makefile.am	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,24 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-dict \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-storage
+
+libquota_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+	libquota_plugin.la
+
+libquota_plugin_la_SOURCES = \
+	quota.c \
+	quota-dict.c \
+	quota-dirsize.c \
+        quota-plugin.c \
+	quota-storage.c
+
+install-exec-local:
+	$(mkdir_p) $(moduledir)/imap $(moduledir)/lda
+	for d in imap lda; do \
+	  rm -f $(moduledir)/$$d/libquota_plugin.so; \
+	  $(LN_S) ../libquota_plugin.so $(moduledir)/$$d; \
+	done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-dict.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,309 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "str.h"
+#include "dict.h"
+#include "quota-private.h"
+
+#include <stdlib.h>
+
+#define DICT_QUOTA_LIMIT_PATH DICT_PATH_PRIVATE"quota/limit/"
+#define DICT_QUOTA_CURRENT_PATH DICT_PATH_PRIVATE"quota/current/"
+
+struct dict_quota {
+	struct quota quota;
+
+	pool_t pool;
+	const char *error;
+	struct quota_root root;
+
+	struct dict *dict;
+};
+
+struct dict_quota_root_iter {
+	struct quota_root_iter iter;
+
+	int sent;
+};
+
+struct dict_quota_transaction_context {
+	struct quota_transaction_context ctx;
+
+	uint64_t storage_limit;
+	uint64_t storage_current;
+};
+
+extern struct quota dict_quota;
+
+static struct quota *dict_quota_init(const char *data)
+{
+	struct dict_quota *quota;
+	struct dict *dict;
+	pool_t pool;
+
+	if (getenv("DEBUG") != NULL)
+		i_info("dict quota uri = %s", data);
+
+	dict = dict_init(data);
+	if (dict == NULL)
+		return NULL;
+
+	pool = pool_alloconly_create("quota", 1024);
+	quota = p_new(pool, struct dict_quota, 1);
+	quota->pool = pool;
+	quota->quota = dict_quota;
+	quota->dict = dict;
+
+	quota->root.quota = &quota->quota;
+	return &quota->quota;
+}
+
+static void dict_quota_deinit(struct quota *_quota)
+{
+	struct dict_quota *quota = (struct dict_quota *)_quota;
+
+	pool_unref(quota->pool);
+}
+
+static struct quota_root_iter *
+dict_quota_root_iter_init(struct quota *quota,
+			  struct mailbox *box __attr_unused__)
+{
+	struct dict_quota_root_iter *iter;
+
+	iter = i_new(struct dict_quota_root_iter, 1);
+	iter->iter.quota = quota;
+	return &iter->iter;
+}
+
+static struct quota_root *
+dict_quota_root_iter_next(struct quota_root_iter *_iter)
+{
+	struct dict_quota_root_iter *iter =
+		(struct dict_quota_root_iter *)_iter;
+	struct dict_quota *quota = (struct dict_quota *)_iter->quota;
+
+	if (iter->sent)
+		return NULL;
+
+	iter->sent = TRUE;
+	return &quota->root;
+}
+
+static int dict_quota_root_iter_deinit(struct quota_root_iter *iter)
+{
+	i_free(iter);
+	return 0;
+}
+
+static struct quota_root *
+dict_quota_root_lookup(struct quota *_quota, const char *name)
+{
+	struct dict_quota *quota = (struct dict_quota *)_quota;
+
+	if (*name == '\0')
+		return &quota->root;
+	else
+		return NULL;
+}
+
+static const char *
+dict_quota_root_get_name(struct quota_root *root __attr_unused__)
+{
+	return "";
+}
+
+static const char *const *
+dict_quota_root_get_resources(struct quota_root *root __attr_unused__)
+{
+	static const char *resources[] = { QUOTA_NAME_STORAGE, NULL };
+
+	return resources;
+}
+
+static int
+dict_quota_root_create(struct quota *_quota,
+		       const char *name __attr_unused__,
+		       struct quota_root **root_r __attr_unused__)
+{
+	struct dict_quota *quota = (struct dict_quota *)_quota;
+
+        quota->error = "Permission denied";
+	return -1;
+}
+
+static int
+dict_quota_get_resource(struct quota_root *root, const char *name,
+			uint64_t *value_r, uint64_t *limit_r)
+{
+	struct dict_quota *quota = (struct dict_quota *)root->quota;
+	const char *value;
+
+	if (quota->dict == NULL)
+		return 0;
+
+	t_push();
+	value = dict_lookup(quota->dict, unsafe_data_stack_pool,
+			    t_strconcat(DICT_QUOTA_LIMIT_PATH, name, NULL));
+	*limit_r = value == NULL ? 0 : strtoull(value, NULL, 10);
+
+	if (value == NULL) {
+		/* resource doesn't exist */
+		*value_r = 0;
+	} else {
+		value = dict_lookup(quota->dict, unsafe_data_stack_pool,
+				    t_strconcat(DICT_QUOTA_CURRENT_PATH,
+						name, NULL));
+		*value_r = value == NULL ? 0 : strtoull(value, NULL, 10);
+	}
+	t_pop();
+
+	*limit_r /= 1024;
+	*value_r /= 1024;
+
+	return value == NULL;
+}
+
+static int
+dict_quota_set_resource(struct quota_root *root,
+			const char *name __attr_unused__,
+			uint64_t value __attr_unused__)
+{
+	struct dict_quota *quota = (struct dict_quota *)root->quota;
+
+	quota->error = "Permission denied";
+	return -1;
+}
+
+static struct quota_transaction_context *
+dict_quota_transaction_begin(struct quota *_quota)
+{
+	struct dict_quota *quota = (struct dict_quota *)_quota;
+	struct dict_quota_transaction_context *ctx;
+	const char *value;
+
+	ctx = i_new(struct dict_quota_transaction_context, 1);
+	ctx->ctx.quota = _quota;
+
+	if (quota->dict != NULL) {
+		t_push();
+		value = dict_lookup(quota->dict, unsafe_data_stack_pool,
+				    DICT_QUOTA_LIMIT_PATH"storage");
+		ctx->storage_limit = value == NULL ? 0 :
+			strtoull(value, NULL, 10);
+
+		value = dict_lookup(quota->dict, unsafe_data_stack_pool,
+				    DICT_QUOTA_CURRENT_PATH"storage");
+		ctx->storage_current = value == NULL ? 0 :
+			strtoull(value, NULL, 10);
+		t_pop();
+	} else {
+		ctx->storage_limit = (uint64_t)-1;
+	}
+
+	return &ctx->ctx;
+}
+
+static int
+dict_quota_transaction_commit(struct quota_transaction_context *_ctx)
+{
+	struct dict_quota_transaction_context *ctx =
+		(struct dict_quota_transaction_context *)_ctx;
+	struct dict_quota *quota = (struct dict_quota *)_ctx->quota;
+
+	if (quota->dict != NULL) {
+		struct dict_transaction_context *dt;
+
+		dt = dict_transaction_begin(quota->dict);
+		dict_atomic_inc(dt, DICT_QUOTA_CURRENT_PATH"storage",
+				_ctx->bytes_diff);
+		if (dict_transaction_commit(dt) < 0)
+			i_error("dict_quota: Couldn't update quota");
+	}
+
+	i_free(ctx);
+	return 0;
+}
+
+static void
+dict_quota_transaction_rollback(struct quota_transaction_context *ctx)
+{
+	i_free(ctx);
+}
+
+static int
+dict_quota_try_alloc(struct quota_transaction_context *_ctx,
+		     struct mail *mail, int *too_large_r)
+{
+	struct dict_quota_transaction_context *ctx =
+		(struct dict_quota_transaction_context *)_ctx;
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	*too_large_r = size > ctx->storage_limit;
+
+	if (ctx->storage_current + _ctx->bytes_diff + size > ctx->storage_limit)
+		return 0;
+
+	_ctx->bytes_diff += size;
+	return 1;
+}
+
+static void
+dict_quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff += size;
+}
+
+static void
+dict_quota_free(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff -= size;
+}
+
+static const char *dict_quota_last_error(struct quota *_quota)
+{
+	struct dict_quota *quota = (struct dict_quota *)_quota;
+
+	return quota->error;
+}
+
+struct quota dict_quota = {
+	"dict",
+
+	dict_quota_init,
+	dict_quota_deinit,
+
+	dict_quota_root_iter_init,
+	dict_quota_root_iter_next,
+	dict_quota_root_iter_deinit,
+
+	dict_quota_root_lookup,
+
+	dict_quota_root_get_name,
+	dict_quota_root_get_resources,
+
+	dict_quota_root_create,
+	dict_quota_get_resource,
+	dict_quota_set_resource,
+
+	dict_quota_transaction_begin,
+	dict_quota_transaction_commit,
+	dict_quota_transaction_rollback,
+
+	dict_quota_try_alloc,
+	dict_quota_alloc,
+	dict_quota_free,
+
+	dict_quota_last_error,
+
+	ARRAY_INIT
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-dirsize.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,329 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+/* Quota reporting based on simply summing sizes of all files in mailbox
+   together. */
+
+#include "lib.h"
+#include "str.h"
+#include "quota-private.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct dirsize_quota {
+	struct quota quota;
+
+	pool_t pool;
+	const char *path;
+	const char *error;
+	struct quota_root root;
+
+	uint64_t storage_limit;
+};
+
+struct dirsize_quota_root_iter {
+	struct quota_root_iter iter;
+
+	int sent;
+};
+
+extern struct quota dirsize_quota;
+
+static struct quota *dirsize_quota_init(const char *data)
+{
+	struct dirsize_quota *quota;
+	const char *const *args;
+	pool_t pool;
+
+	pool = pool_alloconly_create("quota", 1024);
+	quota = p_new(pool, struct dirsize_quota, 1);
+	quota->pool = pool;
+	quota->quota = dirsize_quota;
+
+	args = t_strsplit(data, ":");
+	quota->path = p_strdup(pool, args[0]);
+
+	for (args++; *args != '\0'; args++) {
+		if (strncmp(*args, "storage=", 8) == 0)
+			quota->storage_limit = strtoull(*args + 8, NULL, 10);
+	}
+
+	if (getenv("DEBUG") != NULL) {
+		i_info("dirsize quota path = %s", quota->path);
+		i_info("dirsize quota limit = %llukB",
+		       (unsigned long long)quota->storage_limit);
+	}
+
+	quota->root.quota = &quota->quota;
+	return &quota->quota;
+}
+
+static void dirsize_quota_deinit(struct quota *_quota)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)_quota;
+
+	pool_unref(quota->pool);
+}
+
+static struct quota_root_iter *
+dirsize_quota_root_iter_init(struct quota *quota,
+			     struct mailbox *box __attr_unused__)
+{
+	struct dirsize_quota_root_iter *iter;
+
+	iter = i_new(struct dirsize_quota_root_iter, 1);
+	iter->iter.quota = quota;
+	return &iter->iter;
+}
+
+static struct quota_root *
+dirsize_quota_root_iter_next(struct quota_root_iter *_iter)
+{
+	struct dirsize_quota_root_iter *iter =
+		(struct dirsize_quota_root_iter *)_iter;
+	struct dirsize_quota *quota = (struct dirsize_quota *)_iter->quota;
+
+	if (iter->sent)
+		return NULL;
+
+	iter->sent = TRUE;
+	return &quota->root;
+}
+
+static int dirsize_quota_root_iter_deinit(struct quota_root_iter *iter)
+{
+	i_free(iter);
+	return 0;
+}
+
+static struct quota_root *
+dirsize_quota_root_lookup(struct quota *_quota, const char *name)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)_quota;
+
+	if (*name == '\0')
+		return &quota->root;
+	else
+		return NULL;
+}
+
+static const char *
+dirsize_quota_root_get_name(struct quota_root *root __attr_unused__)
+{
+	return "";
+}
+
+static const char *const *
+dirsize_quota_root_get_resources(struct quota_root *root __attr_unused__)
+{
+	static const char *resources[] = { QUOTA_NAME_STORAGE, NULL };
+
+	return resources;
+}
+
+static int
+dirsize_quota_root_create(struct quota *_quota,
+			  const char *name __attr_unused__,
+			  struct quota_root **root_r __attr_unused__)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)_quota;
+
+        quota->error = "Permission denied";
+	return -1;
+}
+
+static int get_dir_usage(const char *dir, uint64_t *value)
+{
+	DIR *dirp;
+	string_t *path;
+	struct dirent *d;
+	struct stat st;
+	unsigned int path_pos;
+        int ret;
+
+	dirp = opendir(dir);
+	if (dirp == NULL) {
+		if (errno == ENOENT)
+			return 0;
+
+		i_error("opendir(%s) failed: %m", dir);
+		return -1;
+	}
+
+	path = t_str_new(128);
+	str_append(path, dir);
+	str_append_c(path, '/');
+	path_pos = str_len(path);
+
+	ret = 0;
+	while ((d = readdir(dirp)) != NULL) {
+		if (d->d_name[0] == '.' &&
+		    (d->d_name[1] == '\0' ||
+		     (d->d_name[1] == '.' && d->d_name[2] == '\0'))) {
+			/* skip . and .. */
+			continue;
+		}
+
+		str_truncate(path, path_pos);
+		str_append(path, d->d_name);
+
+		if (lstat(str_c(path), &st) < 0) {
+			if (errno == ENOENT)
+				continue;
+
+			i_error("lstat(%s) failed: %m", dir);
+			ret = -1;
+			break;
+		} else if (S_ISDIR(st.st_mode)) {
+			if (get_dir_usage(str_c(path), value) < 0) {
+				ret = -1;
+				break;
+			}
+		} else {
+			*value += st.st_size;
+		}
+	}
+
+	(void)closedir(dirp);
+	return ret;
+}
+
+static int
+dirsize_quota_get_resource(struct quota_root *root, const char *name,
+			   uint64_t *value_r, uint64_t *limit_r)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)root->quota;
+
+	*value_r = 0;
+	*limit_r = 0;
+
+	if (strcasecmp(name, QUOTA_NAME_STORAGE) != 0)
+		return 0;
+
+	if (get_dir_usage(quota->path, value_r) < 0) {
+		quota->error = "Internal quota calculation error";
+		return -1;
+	}
+	*value_r /= 1024;
+	*limit_r = quota->storage_limit;
+	return 1;
+}
+
+static int
+dirsize_quota_set_resource(struct quota_root *root,
+			   const char *name __attr_unused__,
+			   uint64_t value __attr_unused__)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)root->quota;
+
+	quota->error = "Permission denied";
+	return -1;
+}
+
+static struct quota_transaction_context *
+dirsize_quota_transaction_begin(struct quota *quota)
+{
+	struct quota_transaction_context *ctx;
+
+	ctx = i_new(struct quota_transaction_context, 1);
+	ctx->quota = quota;
+	return ctx;
+}
+
+static int
+dirsize_quota_transaction_commit(struct quota_transaction_context *ctx)
+{
+	i_free(ctx);
+	return 0;
+}
+
+static void
+dirsize_quota_transaction_rollback(struct quota_transaction_context *ctx)
+{
+	i_free(ctx);
+}
+
+static int
+dirsize_quota_try_alloc(struct quota_transaction_context *ctx,
+			struct mail *mail, int *too_large_r)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)ctx->quota;
+	uint64_t value = 0;
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	*too_large_r = size / 1024 > quota->storage_limit;
+
+	if (get_dir_usage(quota->path, &value) < 0 || size == (uoff_t)-1) {
+		quota->error = "Internal quota calculation error";
+		return -1;
+	}
+	value += ctx->bytes_diff;
+
+	if ((value + size) / 1024 > quota->storage_limit)
+		return 0;
+
+	ctx->bytes_diff += size;
+	return 1;
+}
+
+static void
+dirsize_quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff += size;
+}
+
+static void
+dirsize_quota_free(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	uoff_t size;
+
+	size = mail_get_physical_size(mail);
+	if (size != (uoff_t)-1)
+		ctx->bytes_diff -= size;
+}
+
+static const char *dirsize_quota_last_error(struct quota *_quota)
+{
+	struct dirsize_quota *quota = (struct dirsize_quota *)_quota;
+
+	return quota->error;
+}
+
+struct quota dirsize_quota = {
+	"dirsize",
+
+	dirsize_quota_init,
+	dirsize_quota_deinit,
+
+	dirsize_quota_root_iter_init,
+	dirsize_quota_root_iter_next,
+	dirsize_quota_root_iter_deinit,
+
+	dirsize_quota_root_lookup,
+
+	dirsize_quota_root_get_name,
+	dirsize_quota_root_get_resources,
+
+	dirsize_quota_root_create,
+	dirsize_quota_get_resource,
+	dirsize_quota_set_resource,
+
+	dirsize_quota_transaction_begin,
+	dirsize_quota_transaction_commit,
+	dirsize_quota_transaction_rollback,
+
+	dirsize_quota_try_alloc,
+	dirsize_quota_alloc,
+	dirsize_quota_free,
+
+	dirsize_quota_last_error,
+
+	ARRAY_INIT
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-plugin.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,38 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-storage.h"
+#include "quota.h"
+#include "quota-plugin.h"
+
+#include <stdlib.h>
+
+/* defined by imap, pop3, lda */
+extern void (*hook_mail_storage_created)(struct mail_storage *storage);
+
+void (*quota_next_hook_mail_storage_created)(struct mail_storage *storage);
+struct quota *quota = NULL;
+
+void quota_plugin_init(void)
+{
+	const char *env;
+
+	env = getenv("QUOTA");
+	quota = env == NULL ? NULL : quota_init(env);
+
+	if (quota != NULL) {
+		quota_next_hook_mail_storage_created =
+			hook_mail_storage_created;
+		hook_mail_storage_created = quota_mail_storage_created;
+	}
+}
+
+void quota_plugin_deinit(void)
+{
+	if (quota != NULL) {
+		hook_mail_storage_created =
+			quota_next_hook_mail_storage_created;
+		quota_deinit(quota);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-plugin.h	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,15 @@
+#ifndef __QUOTA_PLUGIN_H
+#define __QUOTA_PLUGIN_H
+
+struct mail_storage;
+
+extern void (*quota_next_hook_mail_storage_created)
+	(struct mail_storage *storage);
+extern struct quota *quota;
+
+void quota_mail_storage_created(struct mail_storage *storage);
+
+void quota_plugin_init(void);
+void quota_plugin_deinit(void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-private.h	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,66 @@
+#ifndef __QUOTA_PRIVATE_H
+#define __QUOTA_PRIVATE_H
+
+#include "mail-storage-private.h"
+#include "quota.h"
+
+/* Modules should use do "my_id = quota_module_id++" and
+   use quota_module_contexts[id] for their own purposes. */
+extern unsigned int quota_module_id;
+
+struct quota {
+	const char *name;
+
+	struct quota *(*init)(const char *data);
+	void (*deinit)(struct quota *quota);
+
+	struct quota_root_iter *
+		(*root_iter_init)(struct quota *quota, struct mailbox *box);
+	struct quota_root *(*root_iter_next)(struct quota_root_iter *iter);
+	int (*root_iter_deinit)(struct quota_root_iter *iter);
+
+	struct quota_root *(*root_lookup)(struct quota *quota,
+					  const char *name);
+
+	const char *(*root_get_name)(struct quota_root *root);
+	const char *const *(*root_get_resources)(struct quota_root *root);
+
+	int (*root_create)(struct quota *quota, const char *name,
+			   struct quota_root **root_r);
+	int (*get_resource)(struct quota_root *root, const char *name,
+			    uint64_t *value_r, uint64_t *limit_r);
+	int (*set_resource)(struct quota_root *root,
+			    const char *name, uint64_t value);
+
+	struct quota_transaction_context *
+		(*transaction_begin)(struct quota *quota);
+	int (*transaction_commit)(struct quota_transaction_context *ctx);
+	void (*transaction_rollback)(struct quota_transaction_context *ctx);
+
+	int (*try_alloc)(struct quota_transaction_context *ctx,
+			 struct mail *mail, int *too_large_r);
+	void (*alloc)(struct quota_transaction_context *ctx, struct mail *mail);
+	void (*free)(struct quota_transaction_context *ctx, struct mail *mail);
+
+	const char *(*last_error)(struct quota *quota);
+
+	/* Module-specific contexts. See quota_module_id. */
+	array_t ARRAY_DEFINE(quota_module_contexts, void);
+};
+
+struct quota_root {
+	struct quota *quota;
+};
+
+struct quota_root_iter {
+	struct quota *quota;
+};
+
+struct quota_transaction_context {
+	struct quota *quota;
+
+	int count_diff;
+	int64_t bytes_diff;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota-storage.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,246 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "mail-storage-private.h"
+#include "quota.h"
+#include "quota-plugin.h"
+
+#include <sys/stat.h>
+
+#define QUOTA_CONTEXT(obj) \
+	*((void **)array_idx_modifyable(&(obj)->module_contexts, \
+					quota_storage_module_id))
+
+struct quota_mail_storage {
+	struct mail_storage_vfuncs super;
+};
+
+struct quota_mailbox {
+	struct mailbox_vfuncs super;
+
+	unsigned int save_hack:1;
+};
+
+struct quota_mail {
+	struct mail_vfuncs super;
+};
+
+static unsigned int quota_storage_module_id = 0;
+static int quota_storage_module_id_set = FALSE;
+
+static int quota_mail_expunge(struct mail *_mail)
+{
+	struct mail_private *mail = (struct mail_private *)_mail;
+	struct quota_mail *qmail = QUOTA_CONTEXT(mail);
+	struct quota_transaction_context *qt =
+		QUOTA_CONTEXT(_mail->transaction);
+
+	if (qmail->super.expunge(_mail) < 0)
+		return -1;
+
+	quota_free(qt, _mail);
+	return 0;
+}
+
+static struct mailbox_transaction_context *
+quota_mailbox_transaction_begin(struct mailbox *box,
+				enum mailbox_transaction_flags flags)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(box);
+	struct mailbox_transaction_context *t;
+	struct quota_transaction_context *qt;
+
+	t = qbox->super.transaction_begin(box, flags);
+	qt = quota_transaction_begin(quota);
+
+	array_idx_set(&t->module_contexts, quota_storage_module_id, &qt);
+	return t;
+}
+
+static int
+quota_mailbox_transaction_commit(struct mailbox_transaction_context *ctx,
+				 enum mailbox_sync_flags flags)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
+	struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
+
+	if (qbox->super.transaction_commit(ctx, flags) < 0) {
+		quota_transaction_rollback(qt);
+		return -1;
+	} else {
+		(void)quota_transaction_commit(qt);
+		return 0;
+	}
+}
+
+static void
+quota_mailbox_transaction_rollback(struct mailbox_transaction_context *ctx)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->box);
+	struct quota_transaction_context *qt = QUOTA_CONTEXT(ctx);
+
+	qbox->super.transaction_rollback(ctx);
+	quota_transaction_rollback(qt);
+}
+
+static struct mail *
+quota_mail_alloc(struct mailbox_transaction_context *t,
+		 enum mail_fetch_field wanted_fields,
+		 struct mailbox_header_lookup_ctx *wanted_headers)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
+	struct quota_mail *qmail;
+	struct mail *_mail;
+	struct mail_private *mail;
+
+	_mail = qbox->super.mail_alloc(t, wanted_fields, wanted_headers);
+	mail = (struct mail_private *)_mail;
+
+	qmail = p_new(mail->pool, struct quota_mail, 1);
+	qmail->super = mail->v;
+
+	mail->v.expunge = quota_mail_expunge;
+	array_idx_set(&mail->module_contexts, quota_storage_module_id, &qmail);
+	return _mail;
+}
+
+static int quota_check(struct mailbox_transaction_context *t, struct mail *mail)
+{
+	struct quota_transaction_context *qt = QUOTA_CONTEXT(t);
+	int ret, too_large;
+
+	ret = quota_try_alloc(qt, mail, &too_large);
+	if (ret > 0)
+		return 0;
+	else if (ret == 0) {
+		mail_storage_set_error(t->box->storage, "Quota exceeded");
+		return -1;
+	} else {
+		mail_storage_set_error(t->box->storage,  "%s",
+				       quota_last_error(quota));
+		return -1;
+	}
+}
+
+static int
+quota_copy(struct mailbox_transaction_context *t, struct mail *mail,
+	   enum mail_flags flags, struct mail_keywords *keywords,
+	   struct mail *dest_mail)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
+	struct mail *copy_dest_mail;
+	int ret;
+
+	if (dest_mail != NULL)
+		copy_dest_mail = dest_mail;
+	else
+                copy_dest_mail = mail_alloc(t, MAIL_FETCH_PHYSICAL_SIZE, NULL);
+
+	qbox->save_hack = FALSE;
+	if (qbox->super.copy(t, mail, flags, keywords, copy_dest_mail) < 0)
+		return -1;
+
+	/* if copying used saving internally, we already checked the quota
+	   and set qbox->save_hack = TRUE. */
+	ret = qbox->save_hack ? 0 : quota_check(t, copy_dest_mail);
+
+	if (copy_dest_mail != dest_mail)
+		mail_free(copy_dest_mail);
+	return ret;
+}
+
+static struct mail_save_context *
+quota_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,
+		int want_mail __attr_unused__)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(t->box);
+	const struct stat *st;
+
+	st = i_stream_stat(input, TRUE);
+	if (st != NULL && st->st_size != -1) {
+		/* FIXME: input size is known, check for quota.
+		   the API needs changing however to do this, we'd need to
+		   return failure before "+ OK".. */
+	}
+
+	/* note that we set want_mail = TRUE in here. */
+	return qbox->super.save_init(t, flags, keywords, received_date,
+				     timezone_offset, from_envelope,
+				     input, TRUE);
+}
+
+static int quota_save_finish(struct mail_save_context *ctx,
+			     struct mail *dest_mail)
+{
+	struct quota_mailbox *qbox = QUOTA_CONTEXT(ctx->transaction->box);
+	struct mail *save_dest_mail;
+	int ret;
+
+	if (dest_mail != NULL)
+		save_dest_mail = dest_mail;
+	else {
+		save_dest_mail = mail_alloc(ctx->transaction,
+					    MAIL_FETCH_PHYSICAL_SIZE, NULL);
+	}
+
+	if (qbox->super.save_finish(ctx, save_dest_mail) < 0)
+		return -1;
+
+	qbox->save_hack = TRUE;
+	ret = quota_check(ctx->transaction, save_dest_mail);
+
+	if (save_dest_mail != dest_mail)
+		mail_free(save_dest_mail);
+	return ret;
+}
+
+static struct mailbox *
+quota_mailbox_open(struct mail_storage *storage, const char *name,
+		   struct istream *input, enum mailbox_open_flags flags)
+{
+	struct quota_mail_storage *qstorage = QUOTA_CONTEXT(storage);
+	struct mailbox *box;
+	struct quota_mailbox *qbox;
+
+	box = qstorage->super.mailbox_open(storage, name, input, flags);
+	if (box == NULL)
+		return NULL;
+
+	qbox = p_new(box->pool, struct quota_mailbox, 1);
+	qbox->super = box->v;
+
+	box->v.transaction_begin = quota_mailbox_transaction_begin;
+	box->v.transaction_commit = quota_mailbox_transaction_commit;
+	box->v.transaction_rollback = quota_mailbox_transaction_rollback;
+	box->v.mail_alloc = quota_mail_alloc;
+	box->v.save_init = quota_save_init;
+	box->v.save_finish = quota_save_finish;
+	box->v.copy = quota_copy;
+	array_idx_set(&box->module_contexts, quota_storage_module_id, &qbox);
+	return box;
+}
+
+void quota_mail_storage_created(struct mail_storage *storage)
+{
+	struct quota_mail_storage *qstorage;
+
+	if (quota_next_hook_mail_storage_created != NULL)
+		quota_next_hook_mail_storage_created(storage);
+
+	qstorage = p_new(storage->pool, struct quota_mail_storage, 1);
+	qstorage->super = storage->v;
+	storage->v.mailbox_open = quota_mailbox_open;
+
+	if (!quota_storage_module_id_set) {
+		quota_storage_module_id = mail_storage_module_id++;
+		quota_storage_module_id_set = TRUE;
+	}
+
+	array_idx_set(&storage->module_contexts,
+		      quota_storage_module_id, &qstorage);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,136 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "quota-private.h"
+
+unsigned int quota_module_id = 0;
+
+extern struct quota dirsize_quota;
+extern struct quota dict_quota;
+
+static struct quota *quota_classes[] = { &dirsize_quota, &dict_quota };
+#define QUOTA_CLASS_COUNT (sizeof(quota_classes)/sizeof(quota_classes[0]))
+
+struct quota *quota_init(const char *data)
+{
+	struct quota *quota;
+	const char *name, *p;
+	unsigned int i;
+
+	t_push();
+	p = strchr(data, ':');
+	if (p == NULL) {
+		name = data;
+		data = "";
+	} else {
+		name = t_strdup_until(data, p);
+		data = p+1;
+	}
+	for (i = 0; i < QUOTA_CLASS_COUNT; i++) {
+		if (strcmp(quota_classes[i]->name, name) == 0)
+			break;
+	}
+	t_pop();
+
+	quota = i == QUOTA_CLASS_COUNT ? NULL :
+		quota_classes[i]->init(data);
+	if (quota != NULL) {
+		array_create(&quota->quota_module_contexts,
+			     default_pool, sizeof(void *), 5);
+	}
+	return quota;
+}
+
+void quota_deinit(struct quota *quota)
+{
+	array_t *module_contexts = &quota->quota_module_contexts;
+
+	quota->deinit(quota);
+	array_free(module_contexts);
+}
+
+struct quota_root_iter *
+quota_root_iter_init(struct quota *quota, struct mailbox *box)
+{
+	return quota->root_iter_init(quota, box);
+}
+
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter)
+{
+	return iter->quota->root_iter_next(iter);
+}
+
+int quota_root_iter_deinit(struct quota_root_iter *iter)
+{
+	return iter->quota->root_iter_deinit(iter);
+}
+
+struct quota_root *quota_root_lookup(struct quota *quota, const char *name)
+{
+	return quota->root_lookup(quota, name);
+}
+
+const char *quota_root_get_name(struct quota_root *root)
+{
+	return root->quota->root_get_name(root);
+}
+
+const char *const *quota_root_get_resources(struct quota_root *root)
+{
+	return root->quota->root_get_resources(root);
+}
+
+int quota_root_create(struct quota *quota, const char *name,
+		      struct quota_root **root_r)
+{
+	return quota->root_create(quota, name, root_r);
+}
+
+int quota_get_resource(struct quota_root *root, const char *name,
+		       uint64_t *value_r, uint64_t *limit_r)
+{
+	return root->quota->get_resource(root, name, value_r, limit_r);
+}
+
+int quota_set_resource(struct quota_root *root,
+		       const char *name, uint64_t value)
+{
+	return root->quota->set_resource(root, name, value);
+}
+
+struct quota_transaction_context *quota_transaction_begin(struct quota *quota)
+{
+	return quota->transaction_begin(quota);
+}
+
+int quota_transaction_commit(struct quota_transaction_context *ctx)
+{
+	return ctx->quota->transaction_commit(ctx);
+}
+
+void quota_transaction_rollback(struct quota_transaction_context *ctx)
+{
+	ctx->quota->transaction_rollback(ctx);
+}
+
+int quota_try_alloc(struct quota_transaction_context *ctx,
+		    struct mail *mail, int *too_large_r)
+{
+	return ctx->quota->try_alloc(ctx, mail, too_large_r);
+}
+
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	ctx->quota->alloc(ctx, mail);
+}
+
+void quota_free(struct quota_transaction_context *ctx, struct mail *mail)
+{
+	ctx->quota->free(ctx, mail);
+}
+
+const char *quota_last_error(struct quota *quota)
+{
+	return quota->last_error(quota);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/quota/quota.h	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,64 @@
+#ifndef __QUOTA_H
+#define __QUOTA_H
+
+struct mail;
+struct mailbox;
+
+/* Message storage size kilobytes. */
+#define QUOTA_NAME_STORAGE "STORAGE"
+/* Number of messages. */
+#define QUOTA_NAME_MESSAGES "MESSAGES"
+
+struct quota;
+struct quota_root;
+struct quota_root_iter;
+struct quota_transaction_context;
+
+struct quota *quota_init(const char *data);
+void quota_deinit(struct quota *quota);
+
+/* List all quota roots. Returned quota roots are freed by quota_deinit(). */
+struct quota_root_iter *
+quota_root_iter_init(struct quota *quota, struct mailbox *box);
+struct quota_root *quota_root_iter_next(struct quota_root_iter *iter);
+int quota_root_iter_deinit(struct quota_root_iter *iter);
+
+/* Return quota root or NULL. */
+struct quota_root *quota_root_lookup(struct quota *quota, const char *name);
+
+/* Returns name of the quota root. */
+const char *quota_root_get_name(struct quota_root *root);
+/* Return a list of all resources set for the quota root. */
+const char *const *quota_root_get_resources(struct quota_root *root);
+
+/* Create a new quota root. Returns 0 if OK, -1 if error (eg. permission
+   denied). */
+int quota_root_create(struct quota *quota, const char *name,
+		      struct quota_root **root_r);
+/* Returns 1 if quota value was found, 0 if not, -1 if error. */
+int quota_get_resource(struct quota_root *root,
+		       const char *name, uint64_t *value_r, uint64_t *limit_r);
+/* Returns 0 if OK, -1 if error (eg. permission denied, invalid name). */
+int quota_set_resource(struct quota_root *root,
+		       const char *name, uint64_t value);
+
+/* Start a new quota transaction. */
+struct quota_transaction_context *quota_transaction_begin(struct quota *quota);
+/* Commit quota transaction. Returns 0 if ok, -1 if failed. */
+int quota_transaction_commit(struct quota_transaction_context *ctx);
+/* Rollback quota transaction changes. */
+void quota_transaction_rollback(struct quota_transaction_context *ctx);
+
+/* Allocate from quota if there's space. Returns 1 if updated, 0 if not,
+   -1 if error. If mail size is larger than even maximum allowed quota,
+   too_large_r is set to TRUE. */
+int quota_try_alloc(struct quota_transaction_context *ctx,
+		    struct mail *mail, int *too_large_r);
+/* Update quota by allocating/freeing space used by mail. */
+void quota_alloc(struct quota_transaction_context *ctx, struct mail *mail);
+void quota_free(struct quota_transaction_context *ctx, struct mail *mail);
+
+/* Returns the last error message. */
+const char *quota_last_error(struct quota *quota);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/trash/.cvsignore	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
+so_locations
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/trash/Makefile.am	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,21 @@
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/plugins/quota
+
+libtrash_plugin_la_LDFLAGS = -module -avoid-version
+
+module_LTLIBRARIES = \
+	libtrash_plugin.la
+
+libtrash_plugin_la_SOURCES = \
+	trash-plugin.c
+
+install-exec-local:
+	$(mkdir_p) $(moduledir)/imap $(moduledir)/lda
+	for d in imap lda; do \
+	  rm -f $(moduledir)/$$d/libtrash_plugin.so; \
+	  $(LN_S) ../libtrash_plugin.so $(moduledir)/$$d; \
+	done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/trash/trash-plugin.c	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,295 @@
+/* Copyright (C) 2005 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "istream.h"
+#include "home-expand.h"
+#include "mail-search.h"
+#include "quota-private.h"
+#include "quota-plugin.h"
+#include "trash-plugin.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define LOCAL_CONFIG_FILE "~/.dovecot.trash.conf"
+#define GLOBAL_CONFIG_FILE "/etc/dovecot-trash.conf"
+
+#define MAX_RETRY_COUNT 3
+
+#define TRASH_CONTEXT(obj) \
+	*((void **)array_idx_modifyable(&(obj)->quota_module_contexts, \
+					trash_quota_module_id))
+
+struct trash_quota {
+	struct quota super;
+};
+
+struct trash_mailbox {
+	const char *name;
+	int priority; /* lower number = higher priority */
+
+	struct mail_storage *storage;
+
+	/* temporarily set while cleaning: */
+	struct mailbox *box;
+	struct mailbox_transaction_context *trans;
+	struct mail_search_context *search_ctx;
+	struct mail *mail;
+
+	unsigned int mail_set:1;
+};
+
+/* defined by imap, pop3, lda */
+extern void (*hook_mail_storage_created)(struct mail_storage *storage);
+
+static void (*trash_next_hook_mail_storage_created)
+	(struct mail_storage *storage);
+static int quota_initialized;
+static unsigned int trash_quota_module_id;
+
+static pool_t config_pool;
+/* trash_boxes ordered by priority, highest first */
+static array_t ARRAY_DEFINE(trash_boxes, struct trash_mailbox);
+
+static int trash_clean_mailbox_open(struct trash_mailbox *trash)
+{
+        struct mail_search_arg search_arg;
+
+	trash->box = mailbox_open(trash->storage, trash->name, NULL,
+				  MAILBOX_OPEN_KEEP_RECENT);
+	trash->trans = mailbox_transaction_begin(trash->box, 0);
+
+	memset(&search_arg, 0, sizeof(search_arg));
+	search_arg.type = SEARCH_ALL;
+
+	trash->search_ctx =
+		mailbox_search_init(trash->trans, NULL, &search_arg, NULL);
+	trash->mail = mail_alloc(trash->trans, MAIL_FETCH_PHYSICAL_SIZE |
+				 MAIL_FETCH_RECEIVED_DATE, NULL);
+
+	return mailbox_search_next(trash->search_ctx, trash->mail);
+}
+
+static int trash_clean_mailbox_get_next(struct trash_mailbox *trash,
+					time_t *received_time_r)
+{
+	int ret;
+
+	if (!trash->mail_set) {
+		if (trash->box == NULL)
+			ret = trash_clean_mailbox_open(trash);
+		else
+			ret = mailbox_search_next(trash->search_ctx,
+						  trash->mail);
+		if (ret <= 0)
+			return ret;
+
+		trash->mail_set = TRUE;
+	}
+
+	*received_time_r = mail_get_received_date(trash->mail);
+	return 1;
+}
+
+static int trash_try_clean_mails(uint64_t size_needed)
+{
+	struct trash_mailbox *trashes;
+	unsigned int i, j, count, oldest_idx;
+	time_t oldest, received;
+	uint64_t size;
+	int ret = 0;
+
+	trashes = array_get_modifyable(&trash_boxes, &count);
+	for (i = 0; i < count; ) {
+		/* expunge oldest mails first in all trash boxes with
+		   same priority */
+		oldest_idx = count;
+		oldest = (time_t)-1;
+		for (j = i; j < count; j++) {
+			if (trashes[j].priority != trashes[j].priority)
+				break;
+
+			ret = trash_clean_mailbox_get_next(&trashes[i],
+							   &received);
+			if (ret < 0)
+				goto __err;
+			if (ret > 0) {
+				if (oldest == (time_t)-1 ||
+				    received < oldest) {
+					oldest = received;
+					oldest_idx = j;
+				}
+			}
+		}
+
+		if (oldest_idx < count) {
+			if (mail_expunge(trashes[oldest_idx].mail) < 0)
+				break;
+
+			size = mail_get_physical_size(trashes[oldest_idx].mail);
+			if (size >= size_needed) {
+				size_needed = 0;
+				break;
+			}
+			trashes[oldest_idx].mail_set = FALSE;
+
+			size_needed -= size;
+		} else {
+			/* find more mails from next priority's mailbox */
+			i = j;
+		}
+	}
+
+__err:
+	for (i = 0; i < count; i++) {
+		struct trash_mailbox *trash = &trashes[i];
+
+		mail_free(trash->mail);
+		trash->mail = NULL;
+
+		(void)mailbox_search_deinit(trash->search_ctx);
+		trash->search_ctx = NULL;
+
+		if (size_needed == 0) {
+			(void)mailbox_transaction_commit(trash->trans,
+				MAILBOX_SYNC_FLAG_FULL_WRITE);
+		} else {
+			/* couldn't get enough space, don't expunge anything */
+                        mailbox_transaction_rollback(trash->trans);
+		}
+		trash->trans = NULL;
+
+		mailbox_close(trash->box);
+		trash->box = NULL;
+	}
+	return size_needed == 0;
+}
+
+static int
+trash_quota_try_alloc(struct quota_transaction_context *ctx,
+		      struct mail *mail, int *too_large_r)
+{
+	struct trash_quota *tquota = TRASH_CONTEXT(quota);
+	int ret, i;
+
+	for (i = 0; ; i++) {
+		ret = tquota->super.try_alloc(ctx, mail, too_large_r);
+		if (ret != 0 || *too_large_r)
+			return ret;
+
+		if (i == MAX_RETRY_COUNT) {
+			/* trash_try_clean_mails() should have returned 0 if
+			   it couldn't get enough space, but allow retrying
+			   it a couple of times if there was some extra space
+			   that was needed.. */
+			break;
+		}
+
+		/* not enough space. try deleting some from mailbox. */
+		ret = trash_try_clean_mails(mail_get_physical_size(mail));
+		if (ret <= 0)
+			return 0;
+	}
+
+	return 0;
+}
+
+static void trash_quota_deinit(struct quota *quota)
+{
+	struct trash_quota *tquota = TRASH_CONTEXT(quota);
+	void *null = NULL;
+
+	array_idx_set(&quota->quota_module_contexts,
+		      trash_quota_module_id, &null);
+	tquota->super.deinit(quota);
+	i_free(tquota);
+}
+
+static void trash_mail_storage_created(struct mail_storage *storage)
+{
+	struct trash_quota *tquota;
+
+	if (trash_next_hook_mail_storage_created != NULL)
+		trash_next_hook_mail_storage_created(storage);
+
+	if (quota_initialized || quota == NULL)
+		return;
+
+	/* initialize here because plugins could be loaded in wrong order */
+	quota_initialized = TRUE;
+
+	tquota = i_new(struct trash_quota, 1);
+	tquota->super = *quota;
+	quota->deinit = trash_quota_deinit;
+	quota->try_alloc = trash_quota_try_alloc;
+
+	trash_quota_module_id = quota_module_id++;
+	array_idx_set(&quota->quota_module_contexts,
+		      trash_quota_module_id, &tquota);
+}
+
+static int trash_mailbox_priority_cmp(const void *p1, const void *p2)
+{
+	const struct trash_mailbox *t1 = p1, *t2 = p2;
+
+	return t1->priority - t2->priority;
+}
+
+static int read_configuration(const char *path)
+{
+	struct istream *input;
+	const char *line, *name;
+	struct trash_mailbox *trash;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		if (errno != ENOENT)
+			i_error("open(%s) failed: %m", path);
+		return -1;
+	}
+
+	p_clear(config_pool);
+	ARRAY_CREATE(&trash_boxes, config_pool, struct trash_mailbox, 8);
+
+	input = i_stream_create_file(fd, default_pool, (size_t)-1, FALSE);
+	while ((line = i_stream_read_next_line(input)) != NULL) {
+		/* <priority> <mailbox name> */
+		name = strchr(line, ' ');
+		if (name == NULL || name[1] == '\0')
+			continue;
+
+		trash = array_append_space(&trash_boxes);
+		trash->name = p_strdup(config_pool, name+1);
+		trash->priority = atoi(t_strdup_until(line, name));
+	}
+	i_stream_unref(input);
+	(void)close(fd);
+
+	qsort(array_get_modifyable(&trash_boxes, NULL),
+	      array_count(&trash_boxes), sizeof(struct trash_mailbox),
+	      trash_mailbox_priority_cmp);
+	return 0;
+}
+
+void trash_plugin_init(void)
+{
+	quota_initialized = FALSE;
+	trash_next_hook_mail_storage_created = hook_mail_storage_created;
+
+	config_pool = pool_alloconly_create("trash config", 1024);
+	if (read_configuration(home_expand(LOCAL_CONFIG_FILE)) < 0) {
+		if (read_configuration(GLOBAL_CONFIG_FILE) < 0)
+			return;
+	}
+
+	hook_mail_storage_created = trash_mail_storage_created;
+}
+
+void trash_plugin_deinit(void)
+{
+	pool_unref(config_pool);
+	hook_mail_storage_created = trash_next_hook_mail_storage_created;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/trash/trash-plugin.h	Sat Dec 10 21:44:45 2005 +0200
@@ -0,0 +1,7 @@
+#ifndef __TRASH_PLUGIN_H
+#define __TRASH_PLUGIN_H
+
+void trash_plugin_init(void);
+void trash_plugin_deinit(void);
+
+#endif