changeset 15814:e63d1cf19ec7

Merged changes from v2.1 tree.
author Timo Sirainen <tss@iki.fi>
date Sat, 16 Feb 2013 18:57:33 +0200
parents 890b5b52ffd0 (current diff) dd0eebe378fe (diff)
children e7aabd79c9d5
files .hgsigs .hgtags NEWS configure.ac src/auth/auth-request.c src/imap/imap-client.c src/lib-index/mail-cache-fields.c src/lib-index/mail-cache-lookup.c src/lib-index/mail-cache-sync-update.c src/lib-index/mail-cache.c src/lib-ssl-iostream/istream-openssl.c src/lib-storage/index/dbox-common/dbox-storage.c src/lib-storage/index/dbox-multi/mdbox-save.c src/lib-storage/index/dbox-multi/mdbox-storage-rebuild.c src/lib-storage/index/index-mail-headers.c src/lib-storage/index/index-mail.c src/lib-storage/index/index-mail.h src/lib-storage/mail-storage.c src/lib/Makefile.am src/lib/buffer.c src/lib/iostream-rawlog.c src/lib/istream-tee.c src/lib/istream.c src/lib/str.c src/lib/test-lib.c src/lib/test-lib.h src/lmtp/client.c src/lmtp/client.h src/lmtp/commands.c src/plugins/acl/doveadm-acl.c
diffstat 22 files changed, 182 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsigs	Sat Feb 16 18:24:28 2013 +0200
+++ b/.hgsigs	Sat Feb 16 18:57:33 2013 +0200
@@ -55,3 +55,5 @@
 75bfda4a7c6c9aa04b6a6ef233fc527356171a06 0 iEYEABECAAYFAlC4WKwACgkQyUhSUUBViskaOACgmcwWV8hgsCOWvkbdh0OIw1ImSQYAn1RcTL0CG3M8+XG7QrrxSfQ7+V99
 86bccdf46d172524ca19a1a8a16a50ac30a6743c 0 iEYEABECAAYFAlDqonoACgkQyUhSUUBVisnqqACfaqdR6GxUAJznotKT9WHIUVhVgcIAoJIEa0SBzlGIWThmLvtQByF9vXcc
 cf9d62fd0b143efa8e49fac998eb78a648cdd8a9 0 iEYEABECAAYFAlDqjXUACgkQyUhSUUBViskUEwCfYTWHeDmPr8HfxSBQN17SD5IwDygAnROhb3IVTm9niDun4gxPxbHLo/Pe
+b314c97d4bbffd01b20f8492592aa422c13e3d55 0 iEYEABECAAYFAlEJlGMACgkQyUhSUUBVismNdQCgggPP/dt1duU1CMYfkpE4Kyc9Ju0An0kphokRqrtppkeqg7pF1JR01Mgq
+fc75811f3c08d80ed339cbb4d37c66f549542ba7 0 iEYEABECAAYFAlEU+CEACgkQyUhSUUBViskh9QCgnqPHUkNvtOioWxo4W7fXjCFLVAwAnR9Z26jgBpoejXDkgwT07wdfYiL3
--- a/.hgtags	Sat Feb 16 18:24:28 2013 +0200
+++ b/.hgtags	Sat Feb 16 18:57:33 2013 +0200
@@ -92,3 +92,5 @@
 75bfda4a7c6c9aa04b6a6ef233fc527356171a06 2.1.12
 cf9d62fd0b143efa8e49fac998eb78a648cdd8a9 2.1.13
 86bccdf46d172524ca19a1a8a16a50ac30a6743c 2.2.beta1
+b314c97d4bbffd01b20f8492592aa422c13e3d55 2.1.14
+fc75811f3c08d80ed339cbb4d37c66f549542ba7 2.1.15
--- a/src/imap/imap-client.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/imap/imap-client.c	Sat Feb 16 18:57:33 2013 +0200
@@ -83,6 +83,8 @@
 					   set->imap_max_line_length, FALSE);
 	client->output = o_stream_create_fd(fd_out, (size_t)-1, FALSE);
 	o_stream_set_no_error_handling(client->output, TRUE);
+	i_stream_set_name(client->input, "<imap client>");
+	o_stream_set_name(client->output, "<imap client>");
 
 	o_stream_set_flush_callback(client->output, client_output, client);
 
--- a/src/lib-index/mail-cache-fields.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-index/mail-cache-fields.c	Sat Feb 16 18:57:33 2013 +0200
@@ -280,6 +280,8 @@
 			file_cache_invalidate(cache->file_cache, offset,
 					      field_hdr_size);
 		}
+		if (cache->read_buf != NULL)
+			buffer_set_used_size(cache->read_buf, 0);
 		ret = mail_cache_map(cache, offset, field_hdr_size, &data);
 		if (ret < 0)
 			return -1;
--- a/src/lib-index/mail-cache-lookup.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-index/mail-cache-lookup.c	Sat Feb 16 18:57:33 2013 +0200
@@ -15,6 +15,7 @@
 {
 	const struct mail_cache_record *rec;
 	const void *data;
+	int ret;
 
 	i_assert(offset != 0);
 
@@ -41,17 +42,15 @@
 	}
 	if (rec->size > CACHE_PREFETCH) {
 		/* larger than we guessed. map the rest of the record. */
-		if (mail_cache_map(cache, offset, rec->size, &data) < 0)
+		if ((ret = mail_cache_map(cache, offset, rec->size, &data)) < 0)
 			return -1;
+		if (ret == 0) {
+			mail_cache_set_corrupted(cache, "record points outside file");
+			return -1;
+		}
 		rec = data;
 	}
 
-	if (rec->size > cache->mmap_length ||
-	    offset + rec->size > cache->mmap_length) {
-		mail_cache_set_corrupted(cache, "record points outside file");
-		return -1;
-	}
-
 	*rec_r = rec;
 	return 0;
 }
--- a/src/lib-index/mail-cache.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-index/mail-cache.c	Sat Feb 16 18:57:33 2013 +0200
@@ -370,12 +370,30 @@
 int mail_cache_map(struct mail_cache *cache, size_t offset, size_t size,
 		   const void **data_r)
 {
+	struct stat st;
 	const void *data;
 	ssize_t ret;
 
 	if (size == 0)
 		size = sizeof(struct mail_cache_header);
 
+	/* verify offset + size before trying to allocate a huge amount of
+	   memory due to them. note that we may be prefetching more than we
+	   actually need, so don't fail too early. */
+	if ((size > cache->mmap_length || offset + size > cache->mmap_length) &&
+	    (offset > 0 || size > sizeof(struct mail_cache_header))) {
+		if (fstat(cache->fd, &st) < 0) {
+			i_error("fstat(%s) failed: %m", cache->filepath);
+			return -1;
+		}
+		if (offset >= (uoff_t)st.st_size) {
+			*data_r = NULL;
+			return 0;
+		}
+		if (offset + size > (uoff_t)st.st_size)
+			size = st.st_size - offset;
+	}
+
 	cache->remap_counter++;
 	if (cache->map_with_read)
 		return mail_cache_map_with_read(cache, offset, size, data_r);
@@ -432,6 +450,7 @@
 	cache->mmap_base = mmap_ro_file(cache->fd, &cache->mmap_length);
 	if (cache->mmap_base == MAP_FAILED) {
 		cache->mmap_base = NULL;
+		cache->mmap_length = 0;
 		mail_cache_set_syscall_error(cache, "mmap()");
 		return -1;
 	}
@@ -464,8 +483,7 @@
 
 	mail_cache_init_file_cache(cache);
 
-	if (mail_cache_map(cache, 0, sizeof(struct mail_cache_header),
-			   &data) < 0)
+	if (mail_cache_map(cache, 0, 0, &data) < 0)
 		return -1;
 	return 1;
 }
@@ -685,6 +703,8 @@
 			file_cache_invalidate(cache->file_cache, 0,
 					      sizeof(struct mail_cache_header));
 		}
+		if (cache->read_buf != NULL)
+			buffer_set_used_size(cache->read_buf, 0);
 		if (mail_cache_map(cache, 0, 0, &data) > 0)
 			cache->hdr_copy = *cache->hdr;
 		else {
--- a/src/lib-storage/index/dbox-common/dbox-storage.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-storage/index/dbox-common/dbox-storage.c	Sat Feb 16 18:57:33 2013 +0200
@@ -270,6 +270,12 @@
 	if (mailbox_open(box) < 0)
 		return -1;
 
+	if (mail_index_get_header(box->view)->uid_validity != 0) {
+		mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+				       "Mailbox already exists");
+		return -1;
+	}
+
 	/* if alt path already exists and contains files, rebuild storage so
 	   that we don't start overwriting files. */
 	ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX, &alt_path);
--- a/src/lib-storage/index/index-mail-headers.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-storage/index/index-mail-headers.c	Sat Feb 16 18:57:33 2013 +0200
@@ -374,6 +374,7 @@
 	input2 = tee_i_stream_create_child(mail->data.tee_stream);
 
 	index_mail_parse_header_init(mail, NULL);
+	mail->data.parser_input = input;
 	mail->data.parser_ctx =
 		message_parser_init(mail->mail.data_pool, input,
 				    hdr_parser_flags, msg_parser_flags);
@@ -386,10 +387,13 @@
 	struct index_mail_data *data = &mail->data;
 	struct message_part *parts;
 
-	if (data->parser_ctx != NULL)
+	if (data->parser_ctx != NULL) {
+		data->parser_input = NULL;
 		(void)message_parser_deinit(&data->parser_ctx, &parts);
+	}
 
 	if (data->parts == NULL) {
+		data->parser_input = data->stream;
 		data->parser_ctx = message_parser_init(mail->mail.data_pool,
 						       data->stream,
 						       hdr_parser_flags,
--- a/src/lib-storage/index/index-mail.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-storage/index/index-mail.c	Sat Feb 16 18:57:33 2013 +0200
@@ -805,13 +805,43 @@
 static int index_mail_parse_body_finish(struct index_mail *mail,
 					enum index_cache_field field)
 {
-	if (message_parser_deinit(&mail->data.parser_ctx,
-				  &mail->data.parts) < 0) {
-		mail_set_cache_corrupted(&mail->mail.mail,
-					 MAIL_FETCH_MESSAGE_PARTS);
+	struct istream *parser_input = mail->data.parser_input;
+	int ret;
+
+	if (parser_input == NULL) {
+		ret = message_parser_deinit(&mail->data.parser_ctx,
+					    &mail->data.parts) < 0 ? 0 : 1;
+	} else {
+		mail->data.parser_input = NULL;
+		i_stream_ref(parser_input);
+		ret = message_parser_deinit(&mail->data.parser_ctx,
+					    &mail->data.parts) < 0 ? 0 : 1;
+		if (parser_input->stream_errno == 0 ||
+		    parser_input->stream_errno == EPIPE) {
+			/* EPIPE = input already closed. allow the caller to
+			   decide if that is an error or not. */
+			i_assert(i_stream_read(parser_input) == -1 &&
+				 !i_stream_have_bytes_left(parser_input));
+		} else {
+			errno = parser_input->stream_errno;
+			mail_storage_set_critical(mail->mail.mail.box->storage,
+				"mail parser: read(%s, box=%s) failed: %m",
+				i_stream_get_name(parser_input),
+				mail->mail.mail.box->vname);
+			ret = -1;
+		}
+		i_stream_unref(&parser_input);
+	}
+	if (ret <= 0) {
+		if (ret == 0) {
+			mail_set_cache_corrupted(&mail->mail.mail,
+						 MAIL_FETCH_MESSAGE_PARTS);
+		}
+		mail->data.parts = NULL;
 		mail->data.parsed_bodystructure = FALSE;
 		return -1;
 	}
+
 	if (mail->data.no_caching) {
 		/* if we're here because we aborted parsing, don't get any
 		   further or we may crash while generating output from
@@ -1221,6 +1251,7 @@
 			mail_set_cache_corrupted(&mail->mail.mail,
 						 MAIL_FETCH_MESSAGE_PARTS);
 		}
+		mail->data.parser_input = NULL;
 	}
 	if (data->filter_stream != NULL)
 		i_stream_unref(&data->filter_stream);
--- a/src/lib-storage/index/index-mail.h	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-storage/index/index-mail.h	Sat Feb 16 18:57:33 2013 +0200
@@ -98,6 +98,7 @@
 	struct istream *stream, *filter_stream;
 	struct tee_istream *tee_stream;
 	struct message_size hdr_size, body_size;
+	struct istream *parser_input;
 	struct message_parser_ctx *parser_ctx;
 	int parsing_count;
 	ARRAY_TYPE(keywords) keywords;
--- a/src/lib-storage/mail-storage.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib-storage/mail-storage.c	Sat Feb 16 18:57:33 2013 +0200
@@ -641,10 +641,12 @@
 
 	i_assert(uni_utf8_str_is_valid(vname));
 
-	if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
-	    strncasecmp(vname, "INBOX", 5) == 0 &&
+	if (strncasecmp(vname, "INBOX", 5) == 0 &&
 	    strncmp(vname, "INBOX", 5) != 0) {
-		/* make sure INBOX shows up in uppercase everywhere */
+		/* make sure INBOX shows up in uppercase everywhere. do this
+		   regardless of whether we're in inbox=yes namespace, because
+		   clients expect INBOX to be case insensitive regardless of
+		   server's internal configuration. */
 		if (vname[5] == '\0')
 			vname = "INBOX";
 		else if (vname[5] == mail_namespace_get_sep(list->ns))
--- a/src/lib/Makefile.am	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/Makefile.am	Sat Feb 16 18:57:33 2013 +0200
@@ -286,6 +286,7 @@
 	test-primes.c \
 	test-priorityq.c \
 	test-seq-range-array.c \
+	test-str.c \
 	test-strescape.c \
 	test-strfuncs.c \
 	test-str-find.c \
--- a/src/lib/buffer.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/buffer.c	Sat Feb 16 18:57:33 2013 +0200
@@ -39,6 +39,7 @@
 static inline void
 buffer_check_limits(struct real_buffer *buf, size_t pos, size_t data_size)
 {
+	unsigned int extra;
 	size_t new_size;
 
 	if (unlikely((size_t)-1 - pos < data_size)) {
@@ -53,7 +54,13 @@
 
 		memset(buf->w_buffer + buf->used, 0, max - buf->used);
 	}
-	if (new_size > buf->alloc) {
+
+	/* always keep +1 byte allocated available in case str_c() is called
+	   for this buffer. this is mainly for cases where the buffer is
+	   allocated from data stack, and str_c() is called in a separate stack
+	   frame. */
+	extra = buf->dynamic ? 1 : 0;
+	if (new_size + extra > buf->alloc) {
 		if (unlikely(!buf->dynamic)) {
 			i_panic("Buffer full (%"PRIuSIZE_T" > %"PRIuSIZE_T", "
 				"pool %s)", pos + data_size, buf->alloc,
@@ -62,7 +69,7 @@
 		}
 
 		buffer_alloc(buf, pool_get_exp_grown_size(buf->pool, buf->alloc,
-							  new_size));
+							  new_size + extra));
 	}
 #if 0
 	else if (new_size > buf->used && buf->alloced &&
--- a/src/lib/istream-tee.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/istream-tee.c	Sat Feb 16 18:57:33 2013 +0200
@@ -225,6 +225,7 @@
 	tee->children = tstream;
 
 	ret = i_stream_create(&tstream->istream, input, i_stream_get_fd(input));
+	i_stream_set_name(&tstream->istream.istream, i_stream_get_name(input));
 	/* we keep the reference in tee stream, no need for extra references */
 	i_stream_unref(&input);
 	return ret;
--- a/src/lib/str.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/str.c	Sat Feb 16 18:57:33 2013 +0200
@@ -45,9 +45,6 @@
 	size_t len = str_len(str);
 	size_t alloc = buffer_get_size(str);
 
-#ifdef DEBUG
-	buffer_verify_pool(str);
-#endif
 	if (len == alloc || data[len] != '\0') {
 		buffer_write(str, len, "", 1);
 		/* remove the \0 - we don't want to keep it */
--- a/src/lib/test-lib.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/test-lib.c	Sat Feb 16 18:57:33 2013 +0200
@@ -29,6 +29,7 @@
 		test_primes,
 		test_priorityq,
 		test_seq_range_array,
+		test_str,
 		test_strescape,
 		test_strfuncs,
 		test_str_find,
--- a/src/lib/test-lib.h	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lib/test-lib.h	Sat Feb 16 18:57:33 2013 +0200
@@ -28,6 +28,7 @@
 void test_primes(void);
 void test_priorityq(void);
 void test_seq_range_array(void);
+void test_str(void);
 void test_strescape(void);
 void test_strfuncs(void);
 void test_str_find(void);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/test-str.c	Sat Feb 16 18:57:33 2013 +0200
@@ -0,0 +1,26 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+
+static void test_str_c(void)
+{
+	string_t *str;
+	unsigned int i, j;
+
+	test_begin("str_c()");
+	for (i = 0; i < 32; i++) T_BEGIN {
+		str = t_str_new(15);
+		for (j = 0; j < i; j++)
+			str_append_c(str, 'x');
+		T_BEGIN {
+			(void)str_c(str);
+		} T_END;
+	} T_END;
+	test_end();
+}
+
+void test_str(void)
+{
+	test_str_c();
+}
--- a/src/lmtp/client.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lmtp/client.c	Sat Feb 16 18:57:33 2013 +0200
@@ -8,6 +8,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "hostpid.h"
+#include "process-title.h"
 #include "var-expand.h"
 #include "settings-parser.h"
 #include "master-service.h"
@@ -31,6 +32,20 @@
 static struct client *clients = NULL;
 unsigned int clients_count = 0;
 
+void client_state_set(struct client *client, const char *name)
+{
+	client->state.name = name;
+
+	if (!client->service_set->verbose_proctitle)
+		return;
+	if (clients_count == 0)
+		process_title_set("[idling]");
+	else if (clients_count > 1)
+		process_title_set(t_strdup_printf("[%u clients]", clients_count));
+	else
+		process_title_set(t_strdup_printf("[%s]", client->state.name));
+}
+
 static void client_idle_timeout(struct client *client)
 {
 	client_destroy(client,
@@ -158,6 +173,7 @@
 	lmtp_settings_dup(set_parser, client->pool, &lmtp_set, &lda_set);
 	settings_var_expand(&lmtp_setting_parser_info, lmtp_set, client->pool,
 		mail_storage_service_get_var_expand_table(storage_service, &input));
+	client->service_set = master_service_settings_get(master_service);
 	client->lmtp_set = lmtp_set;
 	client->set = lda_set;
 }
@@ -222,7 +238,6 @@
 	client_io_reset(client);
 	client->state_pool = pool_alloconly_create("client state", 4096);
 	client->state.mail_data_fd = -1;
-	client->state.name = "banner";
 	client_read_settings(client);
 	client_raw_user_create(client);
 	client_generate_session_id(client);
@@ -233,6 +248,7 @@
 	DLLIST_PREPEND(&clients, client);
 	clients_count++;
 
+	client_state_set(client, "banner");
 	client_send_line(client, "220 %s %s", client->my_domain,
 			 client->lmtp_set->login_greeting);
 	i_info("Connect from %s", client_remote_id(client));
@@ -247,6 +263,8 @@
 	clients_count--;
 	DLLIST_REMOVE(&clients, client);
 
+	client_state_set(client, "destroyed");
+
 	if (client->raw_mail_user != NULL)
 		mail_user_unref(&client->raw_mail_user);
 	if (client->proxy != NULL)
@@ -330,7 +348,7 @@
 	client->state.mail_data_fd = -1;
 
 	client_generate_session_id(client);
-	client->state.name = "reset";
+	client_state_set(client, "reset");
 }
 
 void client_send_line(struct client *client, const char *fmt, ...)
--- a/src/lmtp/client.h	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lmtp/client.h	Sat Feb 16 18:57:33 2013 +0200
@@ -43,6 +43,7 @@
 	const struct setting_parser_info *user_set_info;
 	const struct lda_settings *set;
 	const struct lmtp_settings *lmtp_set;
+	const struct master_service_settings *service_set;
 	int fd_in, fd_out;
 	struct io *io;
 	struct istream *input;
@@ -78,6 +79,7 @@
 		       const char *reason);
 void client_io_reset(struct client *client);
 void client_state_reset(struct client *client);
+void client_state_set(struct client *client, const char *name);
 
 void client_input_handle(struct client *client);
 int client_input_read(struct client *client);
--- a/src/lmtp/commands.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/lmtp/commands.c	Sat Feb 16 18:57:33 2013 +0200
@@ -77,7 +77,7 @@
 
 	i_free(client->lhlo);
 	client->lhlo = i_strdup(str_c(domain));
-	client->state.name = "LHLO";
+	client_state_set(client, "LHLO");
 	return 0;
 }
 
@@ -146,7 +146,7 @@
 	client->state.mail_from = p_strdup(client->state_pool, addr);
 	p_array_init(&client->state.rcpt_to, client->state_pool, 64);
 	client_send_line(client, "250 2.1.0 OK");
-	client->state.name = "MAIL FROM";
+	client_state_set(client, "MAIL FROM");
 	return 0;
 }
 
@@ -507,7 +507,7 @@
 	const char *error = NULL;
 	int ret = 0;
 
-	client->state.name = "RCPT TO";
+	client_state_set(client, "RCPT TO");
 
 	if (client->state.mail_from == NULL) {
 		client_send_line(client, "503 5.5.1 MAIL needed first");
@@ -1050,7 +1050,7 @@
 	client_send_line(client, "354 OK");
 
 	io_remove(&client->io);
-	client->state.name = "DATA";
+	client_state_set(client, "DATA");
 	client->io = io_add(client->fd_in, IO_READ, client_input_data, client);
 	client_input_data_handle(client);
 	return -1;
--- a/src/plugins/acl/doveadm-acl.c	Sat Feb 16 18:24:28 2013 +0200
+++ b/src/plugins/acl/doveadm-acl.c	Sat Feb 16 18:57:33 2013 +0200
@@ -394,6 +394,34 @@
 }
 
 static int
+cmd_acl_recalc_run(struct doveadm_mail_cmd_context *ctx, struct mail_user *user)
+{
+	struct acl_user *auser = ACL_USER_CONTEXT(user);
+
+	if (auser == NULL) {
+		i_error("ACL not enabled for %s", user->username);
+		doveadm_mail_failed_error(ctx, MAIL_ERROR_NOTFOUND);
+		return -1;
+	}
+	if (acl_lookup_dict_rebuild(auser->acl_lookup_dict) < 0) {
+		i_error("Failed to recalculate ACL dicts");
+		doveadm_mail_failed_error(ctx, MAIL_ERROR_TEMP);
+		return -1;
+	}
+	return 0;
+}
+
+static struct doveadm_mail_cmd_context *
+cmd_acl_recalc_alloc(void)
+{
+	struct doveadm_mail_cmd_context *ctx;
+
+	ctx = doveadm_mail_cmd_alloc(struct doveadm_mail_cmd_context);
+	ctx->v.run = cmd_acl_recalc_run;
+	return ctx;
+}
+
+static int
 cmd_acl_debug_mailbox_open(struct doveadm_mail_cmd_context *ctx,
 			   struct mail_user *user, const char *mailbox,
 			   struct mailbox **box_r)
@@ -582,6 +610,7 @@
 	{ cmd_acl_add_alloc, "acl add", "<mailbox> <id> <right> [<right> ...]" },
 	{ cmd_acl_remove_alloc, "acl remove", "<mailbox> <id> <right> [<right> ...]" },
 	{ cmd_acl_delete_alloc, "acl delete", "<mailbox> <id>" },
+	{ cmd_acl_recalc_alloc, "acl recalc", "" },
 	{ cmd_acl_debug_alloc, "acl debug", "<mailbox>" }
 };