Esempio n. 1
0
static bool
store_parse_modifiers(struct imap_store_context *ctx,
		      const struct imap_arg *args)
{
	const char *name, *value;

	for (; !IMAP_ARG_IS_EOL(args); args += 2) {
		if (!imap_arg_get_atom(&args[0], &name) ||
		    !imap_arg_get_atom(&args[1], &value)) {
			client_send_command_error(ctx->cmd,
				"Invalid STORE modifiers.");
			return FALSE;
		}

		if (strcasecmp(name, "UNCHANGEDSINCE") == 0) {
			if (ctx->cmd->client->nonpermanent_modseqs) {
				client_send_command_error(ctx->cmd,
					"STORE UNCHANGEDSINCE can't be used with non-permanent modseqs");
				return FALSE;
			}
			if (str_to_uint64(value, &ctx->max_modseq) < 0) {
				client_send_command_error(ctx->cmd,
							  "Invalid modseq");
				return FALSE;
			}
			(void)client_enable(ctx->cmd->client,
					    MAILBOX_FEATURE_CONDSTORE);
		} else {
			client_send_command_error(ctx->cmd,
						  "Unknown STORE modifier");
			return FALSE;
		}
	}
	return TRUE;
}
Esempio n. 2
0
static bool
fetch_parse_args(struct imap_fetch_context *ctx,
		 struct client_command_context *cmd,
		 const struct imap_arg *arg, const struct imap_arg **next_arg_r)
{
	const char *str, *const *macro;

	if (cmd->uid) {
		if (!imap_fetch_cmd_init_handler(ctx, cmd, "UID", &arg))
			return FALSE;
	}
	if (imap_arg_get_atom(arg, &str)) {
		str = t_str_ucase(str);
		arg++;

		/* handle macros first */
		if (strcmp(str, "ALL") == 0)
			macro = all_macro;
		else if (strcmp(str, "FAST") == 0)
			macro = fast_macro;
		else if (strcmp(str, "FULL") == 0)
			macro = full_macro;
		else {
			macro = NULL;
			if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
				return FALSE;
		}
		if (macro != NULL) {
			while (*macro != NULL) {
				if (!imap_fetch_cmd_init_handler(ctx, cmd, *macro, &arg))
					return FALSE;
				macro++;
			}
		}
		*next_arg_r = arg;
	} else {
		*next_arg_r = arg + 1;
		arg = imap_arg_as_list(arg);
		if (IMAP_ARG_IS_EOL(arg)) {
			client_send_command_error(cmd,
						  "FETCH list is empty.");
			return FALSE;
		}
		while (imap_arg_get_atom(arg, &str)) {
			str = t_str_ucase(str);
			arg++;
			if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
				return FALSE;
		}
		if (!IMAP_ARG_IS_EOL(arg)) {
			client_send_command_error(cmd,
				"FETCH list contains non-atoms.");
			return FALSE;
		}
	}
	return TRUE;
}
Esempio n. 3
0
static bool
fetch_parse_modifiers(struct imap_fetch_context *ctx,
		      struct client_command_context *cmd,
		      struct mail_search_args *search_args,
		      const struct imap_arg *args, bool *send_vanished_r)
{
	const char *name;

	*send_vanished_r = FALSE;

	while (!IMAP_ARG_IS_EOL(args)) {
		if (!imap_arg_get_atom(args, &name)) {
			client_send_command_error(cmd,
				"FETCH modifiers contain non-atoms.");
			return FALSE;
		}
		args++;
		if (!fetch_parse_modifier(ctx, cmd, search_args,
					  t_str_ucase(name),
					  &args, send_vanished_r))
			return FALSE;
	}
	if (*send_vanished_r &&
	    (search_args->args->next == NULL ||
	     search_args->args->next->type != SEARCH_MODSEQ)) {
		client_send_command_error(cmd,
			"VANISHED used without CHANGEDSINCE");
		return FALSE;
	}
	return TRUE;
}
Esempio n. 4
0
static bool
fetch_parse_modifier(struct imap_fetch_context *ctx,
		     struct client_command_context *cmd,
		     struct mail_search_args *search_args,
		     const char *name, const struct imap_arg **args,
		     bool *send_vanished)
{
	const char *str;
	uint64_t modseq;

	if (strcmp(name, "CHANGEDSINCE") == 0) {
		if (!imap_arg_get_atom(*args, &str) ||
		    str_to_uint64(str, &modseq) < 0) {
			client_send_command_error(cmd,
				"Invalid CHANGEDSINCE modseq.");
			return FALSE;
		}
		*args += 1;
		imap_search_add_changed_since(search_args, modseq);
		imap_fetch_init_nofail_handler(ctx, imap_fetch_modseq_init);
		return TRUE;
	}
	if (strcmp(name, "VANISHED") == 0 && cmd->uid) {
		if ((ctx->client->enabled_features &
		     MAILBOX_FEATURE_QRESYNC) == 0) {
			client_send_command_error(cmd, "QRESYNC not enabled");
			return FALSE;
		}
		*send_vanished = TRUE;
		return TRUE;
	}

	client_send_command_error(cmd, "Unknown FETCH modifier");
	return FALSE;
}
Esempio n. 5
0
static struct mailbox_node *
imapc_list_update_tree(struct imapc_mailbox_list *list,
		       struct mailbox_tree_context *tree,
		       const struct imap_arg *args)
{
	struct mailbox_node *node;
	const struct imap_arg *flags;
	const char *name, *flag;
	enum mailbox_info_flags info_flag, info_flags = 0;
	bool created;

	if (!imap_arg_get_list(&args[0], &flags) ||
	    args[1].type == IMAP_ARG_EOL ||
	    !imap_arg_get_astring(&args[2], &name))
		return NULL;

	while (imap_arg_get_atom(flags, &flag)) {
		if (imap_list_flag_parse(flag, &info_flag))
			info_flags |= info_flag;
		flags++;
	}

	T_BEGIN {
		const char *vname = imapc_list_to_vname(list, name);

		if ((info_flags & MAILBOX_NONEXISTENT) != 0)
			node = mailbox_tree_lookup(tree, vname);
		else
			node = mailbox_tree_get(tree, vname, &created);
	} T_END;
	if (node != NULL)
		node->flags = info_flags;
	return node;
}
Esempio n. 6
0
bool cmd_enable(struct client_command_context *cmd)
{
	const struct imap_arg *args;
	const char *str;
	string_t *reply;

	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	reply = t_str_new(64);
	str_append(reply, "* ENABLED");
	for (; !IMAP_ARG_IS_EOL(args); args++) {
		if (!imap_arg_get_atom(args, &str)) {
			client_send_command_error(cmd, "Invalid arguments.");
			return TRUE;
		}
		str = t_str_ucase(str);
		if (strcmp(str, "CONDSTORE") == 0) {
			client_enable(cmd->client, MAILBOX_FEATURE_CONDSTORE);
			str_append(reply, " CONDSTORE");
		}
		else if (strcmp(str, "QRESYNC") == 0) {
			client_enable(cmd->client, MAILBOX_FEATURE_QRESYNC |
				      MAILBOX_FEATURE_CONDSTORE);
			str_append(reply, " QRESYNC");
		}
	}
	if (str_len(reply) > 9)
		client_send_line(cmd->client, str_c(reply));
	client_send_tagline(cmd, "OK Enabled.");
	return TRUE;
}
Esempio n. 7
0
bool imap_arg_atom_equals(const struct imap_arg *arg, const char *str)
{
	const char *value;

	if (!imap_arg_get_atom(arg, &value))
		return FALSE;
	return strcasecmp(value, str) == 0;
}
Esempio n. 8
0
static bool cmd_setquota(struct client_command_context *cmd)
{
	struct quota_root *root;
        const struct imap_arg *args, *list_args;
	const char *root_name, *name, *value_str, *error;
	uint64_t value;

	/* <quota root> <resource limits> */
	if (!client_read_args(cmd, 2, 0, &args))
		return FALSE;

	if (!imap_arg_get_astring(&args[0], &root_name) ||
	    !imap_arg_get_list(&args[1], &list_args)) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}

	root = quota_root_lookup(cmd->client->user, root_name);
	if (root == NULL) {
		client_send_tagline(cmd, "NO Quota root doesn't exist.");
		return TRUE;
	}

	for (; !IMAP_ARG_IS_EOL(list_args); list_args += 2) {
		if (!imap_arg_get_atom(&list_args[0], &name) ||
		    !imap_arg_get_atom(&list_args[1], &value_str) ||
		    str_to_uint64(value_str, &value) < 0) {
			client_send_command_error(cmd, "Invalid arguments.");
			return TRUE;
		}

		if (quota_set_resource(root, name, value, &error) < 0) {
			client_send_command_error(cmd, error);
			return TRUE;
		}
	}

	client_send_tagline(cmd, "OK Setquota completed.");
	return TRUE;
}
Esempio n. 9
0
static int
cmd_append_catenate(struct client_command_context *cmd,
		    const struct imap_arg *args, bool *nonsync_r)
{
	struct cmd_append_context *ctx = cmd->context;
	const char *catpart;

	*nonsync_r = FALSE;

	/* Handle URLs until a TEXT literal is encountered */
	while (imap_arg_get_atom(args, &catpart)) {
		const char *caturl;

		if (strcasecmp(catpart, "URL") == 0 ) {
			/* URL <url> */ 
			args++;
			if (!imap_arg_get_astring(args, &caturl))
				break;
			if (cmd_append_catenate_url(cmd, caturl) < 0) {
				/* delay failure until we can stop
				   parsing input */
				ctx->failed = TRUE;
			}
		} else if (strcasecmp(catpart, "TEXT") == 0) {
			/* TEXT <literal> */
			args++;
			if (!imap_arg_get_literal_size(args, &ctx->literal_size))
				break;
			if (args->literal8 && !ctx->binary_input &&
			    !ctx->failed) {
				client_send_tagline(cmd,
					"NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
					"Binary input allowed only when the first part is binary.");
				ctx->failed = TRUE;
			}
			*nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
			cmd_append_catenate_text(cmd);
			return 1;
		} else {
			break;
		}
		args++;
	}

	if (IMAP_ARG_IS_EOL(args)) {
		/* ")" */
		return 0;
	}
	if (!ctx->failed)
		client_send_command_error(cmd, "Invalid arguments.");
	return -1;
}
Esempio n. 10
0
int imap_status_parse_items(struct client_command_context *cmd,
			    const struct imap_arg *args,
			    struct imap_status_items *items_r)
{
	const char *item;
	enum mailbox_status_items status = 0;
	enum mailbox_metadata_items metadata = 0;

	if (IMAP_ARG_IS_EOL(args)) {
		client_send_command_error(cmd, "Empty status list.");
		return -1;
	}

	memset(items_r, 0, sizeof(*items_r));
	for (; !IMAP_ARG_IS_EOL(args); args++) {
		if (!imap_arg_get_atom(args, &item)) {
			/* list may contain only atoms */
			client_send_command_error(cmd,
				"Status list contains non-atoms.");
			return -1;
		}

		item = t_str_ucase(item);
		if (strcmp(item, "MESSAGES") == 0)
			status |= STATUS_MESSAGES;
		else if (strcmp(item, "RECENT") == 0)
			status |= STATUS_RECENT;
		else if (strcmp(item, "UIDNEXT") == 0)
			status |= STATUS_UIDNEXT;
		else if (strcmp(item, "UIDVALIDITY") == 0)
			status |= STATUS_UIDVALIDITY;
		else if (strcmp(item, "UNSEEN") == 0)
			status |= STATUS_UNSEEN;
		else if (strcmp(item, "HIGHESTMODSEQ") == 0)
			status |= STATUS_HIGHESTMODSEQ;
		else if (strcmp(item, "X-SIZE") == 0)
			metadata |= MAILBOX_METADATA_VIRTUAL_SIZE;
		else if (strcmp(item, "X-GUID") == 0)
			metadata |= MAILBOX_METADATA_GUID;
		else {
			client_send_command_error(cmd, t_strconcat(
				"Invalid status item ", item, NULL));
			return -1;
		}
	}

	items_r->status = status;
	items_r->metadata = metadata;
	return 0;
}
Esempio n. 11
0
bool client_parse_mail_flags(struct client_command_context *cmd,
			     const struct imap_arg *args,
			     enum mail_flags *flags_r,
			     const char *const **keywords_r)
{
	const char *atom;
	enum mail_flags flag;
	ARRAY(const char *) keywords;

	*flags_r = 0;
	*keywords_r = NULL;
	p_array_init(&keywords, cmd->pool, 16);

	while (!IMAP_ARG_IS_EOL(args)) {
		if (!imap_arg_get_atom(args, &atom)) {
			client_send_command_error(cmd,
				"Flags list contains non-atoms.");
			return FALSE;
		}

		if (*atom == '\\') {
			/* system flag */
			atom = t_str_ucase(atom);
			flag = imap_parse_system_flag(atom);
			if (flag != 0 && flag != MAIL_RECENT)
				*flags_r |= flag;
			else {
				client_send_command_error(cmd, t_strconcat(
					"Invalid system flag ", atom, NULL));
				return FALSE;
			}
		} else {
			/* keyword validity checks are done by lib-storage */
			array_append(&keywords, &atom, 1);
		}

		args++;
	}

	if (array_count(&keywords) == 0)
		*keywords_r = NULL;
	else {
		array_append_zero(&keywords); /* NULL-terminate */
		*keywords_r = array_idx(&keywords, 0);
	}
	return TRUE;
}
bool cmd_genurlauth(struct client_command_context *cmd)
{
	const struct imap_arg *args;
	string_t *response;
	int ret;

	if (cmd->client->urlauth_ctx == NULL) {
		client_send_command_error(cmd, "URLAUTH disabled.");
		return TRUE;
	}

	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	response = t_str_new(1024);
	str_append(response, "* GENURLAUTH");
	for (;;) {
		const char *url_rump, *mechanism, *url, *error;

		if (IMAP_ARG_IS_EOL(args))
			break;
		if (!imap_arg_get_astring(args++, &url_rump) ||
		    !imap_arg_get_atom(args++, &mechanism)) {
			client_send_command_error(cmd, "Invalid arguments.");
			return FALSE;
		}

		ret = imap_urlauth_generate(cmd->client->urlauth_ctx,
					    mechanism, url_rump, &url, &error);
		if (ret <= 0) {
			if (ret < 0)
				client_send_internal_error(cmd);
			else
				client_send_command_error(cmd, error);
			return TRUE;
		}

		str_append_c(response, ' ');
		imap_append_astring(response, url);
	}

	client_send_line(cmd->client, str_c(response));
	client_send_tagline(cmd, "OK GENURLAUTH completed.");
	return TRUE;
}
Esempio n. 13
0
static bool
list_parse_directives(struct list_directives_context *ctx,
		      const struct imap_arg *args, const char **error_r)
{
	const char *str;

	while (imap_arg_get_atom(args, &str) &&
	       strncmp(str, "$!", 2) == 0) {
		str += 2;

		if (strncmp(str, "unordered", 9) == 0) {
			if (str[9] == '\0')
				;
			else if (str[9] == '=' &&
				 str_to_uint(str + 10, &ctx->chain_count) == 0)
				;
			else {
				*error_r = "Broken $!unordered directive";
				return FALSE;
			}
		} else if (strcmp(str, "ordered") == 0 ||
			   strcmp(str, "noextra") == 0 ||
			   strcmp(str, "extra") == 0 ||
			   strncmp(str, "ignore=", 7) == 0 ||
			   strncmp(str, "ban=", 4) == 0) {
			/* ok */
		} else {
			*error_r = t_strdup_printf("Unknown directive: %s",
						   str);
			return FALSE;
		}
		ctx->directives = TRUE;
		args++;
	}
	return TRUE;
}
Esempio n. 14
0
bool cmd_fetch(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct imap_fetch_context *ctx;
	const struct imap_arg *args, *next_arg, *list_arg;
	struct mail_search_args *search_args;
	struct imap_fetch_qresync_args qresync_args;
	const char *messageset;
	bool send_vanished = FALSE;
	int ret;

	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	/* <messageset> <field(s)> [(modifiers)] */
	if (!imap_arg_get_atom(&args[0], &messageset) ||
	    (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) ||
	    (!IMAP_ARG_IS_EOL(&args[2]) && args[2].type != IMAP_ARG_LIST)) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}

	/* UID FETCH VANISHED needs the uidset, so convert it to
	   sequence set later */
	ret = imap_search_get_anyset(cmd, messageset, cmd->uid, &search_args);
	if (ret <= 0)
		return ret < 0;

	ctx = imap_fetch_alloc(client, cmd->pool);

	if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) ||
	    (imap_arg_get_list(next_arg, &list_arg) &&
	     !fetch_parse_modifiers(ctx, cmd, search_args, list_arg,
				    &send_vanished))) {
		imap_fetch_free(&ctx);
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	if (send_vanished) {
		memset(&qresync_args, 0, sizeof(qresync_args));
		if (imap_fetch_send_vanished(client, client->mailbox,
					     search_args, &qresync_args) < 0) {
			mail_search_args_unref(&search_args);
			return cmd_fetch_finish(ctx, cmd);
		}
	}

	imap_fetch_begin(ctx, client->mailbox, search_args);
	mail_search_args_unref(&search_args);

	if (imap_fetch_more(ctx, cmd) == 0) {
		/* unfinished */
		cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;

		cmd->func = cmd_fetch_continue;
		cmd->context = ctx;
		return FALSE;
	}
	return cmd_fetch_finish(ctx, cmd);
}
static bool cmd_compress(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	struct zlib_client *zclient = IMAP_ZLIB_IMAP_CONTEXT(client);
	const struct compression_handler *handler;
	const struct imap_arg *args;
	struct istream *old_input;
	struct ostream *old_output;
	const char *mechanism, *value;
	unsigned int level;

	/* <mechanism> */
	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	if (!imap_arg_get_atom(args, &mechanism) ||
	    !IMAP_ARG_IS_EOL(&args[1])) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}
	if (zclient->handler != NULL) {
		client_send_tagline(cmd, t_strdup_printf(
			"NO [COMPRESSIONACTIVE] COMPRESSION=%s already enabled.",
			t_str_ucase(zclient->handler->name)));
		return TRUE;
	}
	if (client->tls_compression) {
		client_send_tagline(cmd,
			"NO [COMPRESSIONACTIVE] TLS compression already enabled.");
		return TRUE;
	}

	handler = compression_lookup_handler(t_str_lcase(mechanism));
	if (handler == NULL || handler->create_istream == NULL) {
		client_send_tagline(cmd, "NO Unknown compression mechanism.");
		return TRUE;
	}

	imap_zlib_client_skip_line(client);
	client_send_tagline(cmd, "OK Begin compression.");

	value = mail_user_plugin_getenv(client->user,
					"imap_zlib_compress_level");
	if (value == NULL || str_to_uint(value, &level) < 0 ||
	    level <= 0 || level > 9)
		level = IMAP_COMPRESS_DEFAULT_LEVEL;

	old_input = client->input;
	old_output = client->output;
	client->input = handler->create_istream(old_input, FALSE);
	client->output = handler->create_ostream(old_output, level);
	/* preserve output offset so that the bytes out counter in logout
	   message doesn't get reset here */
	client->output->offset = old_output->offset;
	i_stream_unref(&old_input);
	o_stream_unref(&old_output);

	client_update_imap_parser_streams(client);
	zclient->handler = handler;
	return TRUE;
}
Esempio n. 16
0
bool imap_fetch_binary_init(struct imap_fetch_init_context *ctx)
{
	struct imap_fetch_body_data *body;
	const struct imap_arg *list_args;
	unsigned int list_count;
	const char *str, *p, *error;

	i_assert(strncmp(ctx->name, "BINARY", 6) == 0);
	p = ctx->name + 6;

	body = p_new(ctx->pool, struct imap_fetch_body_data, 1);
	body->binary = TRUE;

	if (strncmp(p, ".SIZE", 5) == 0) {
		/* fetch decoded size of the section */
		p += 5;
		body->binary_size = TRUE;
	} else if (strncmp(p, ".PEEK", 5) == 0) {
		p += 5;
	} else {
		ctx->fetch_ctx->flags_update_seen = TRUE;
	}
	if (*p != '[') {
		ctx->error = "Invalid BINARY[..] parameter: Missing '['";
		return FALSE;
	}

	if (imap_arg_get_list_full(&ctx->args[0], &list_args, &list_count)) {
		/* BINARY[HEADER.FIELDS.. (headers list)] */
		if (!imap_arg_get_atom(&ctx->args[1], &str) ||
		    str[0] != ']') {
			ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
			return FALSE;
		}
		if (body_header_fields_parse(ctx, body, p+1,
					     list_args, list_count) < 0)
			return FALSE;
		p = str+1;
		ctx->args += 2;
	} else {
		/* no headers list */
		body->section = p+1;
		p = strchr(body->section, ']');
		if (p == NULL) {
			ctx->error = "Invalid BINARY[..] parameter: Missing ']'";
			return FALSE;
		}
		body->section = p_strdup_until(ctx->pool, body->section, p);
		p++;
	}
	if (imap_msgpart_parse(body->section, &body->msgpart) < 0) {
		ctx->error = "Invalid BINARY[..] section";
		return FALSE;
	}
	imap_msgpart_set_decode_to_binary(body->msgpart);
	ctx->fetch_ctx->fetch_data |=
		imap_msgpart_get_fetch_data(body->msgpart);

	if (!body->binary_size) {
		if (body_parse_partial(body, p, &error) < 0) {
			ctx->error = p_strdup_printf(ctx->pool,
				"Invalid BINARY[..] parameter: %s", error);
			return FALSE;
		}
	}

	/* update the section name for the imap_fetch_add_handler() */
	ctx->name = p_strdup(ctx->pool, get_body_name(body));
	if (body->binary_size) {
		imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
				       "0", fetch_binary_size, body);
	} else {
		imap_fetch_add_handler(ctx, IMAP_FETCH_HANDLER_FLAG_WANT_DEINIT,
				       "NIL", fetch_body_msgpart, body);
	}
	return TRUE;
}
Esempio n. 17
0
bool cmd_store(struct client_command_context *cmd)
{
	struct client *client = cmd->client;
	const struct imap_arg *args;
	struct mail_search_args *search_args;
	struct mail_search_context *search_ctx;
        struct mailbox_transaction_context *t;
	struct mail *mail;
	struct imap_store_context ctx;
	ARRAY_TYPE(seq_range) modified_set, uids;
	enum mailbox_transaction_flags flags = 0;
	enum imap_sync_flags imap_sync_flags = 0;
	const char *set, *reply, *tagged_reply;
	string_t *str;
	int ret;

	if (!client_read_args(cmd, 0, 0, &args))
		return FALSE;

	if (!client_verify_open_mailbox(cmd))
		return TRUE;

	if (!imap_arg_get_atom(args, &set)) {
		client_send_command_error(cmd, "Invalid arguments.");
		return TRUE;
	}
	ret = imap_search_get_seqset(cmd, set, cmd->uid, &search_args);
	if (ret <= 0)
		return ret < 0;

	memset(&ctx, 0, sizeof(ctx));
	ctx.cmd = cmd;
	if (!store_parse_args(&ctx, ++args)) {
		mail_search_args_unref(&search_args);
		return TRUE;
	}

	if (client->mailbox_examined) {
		mail_search_args_unref(&search_args);
		if (ctx.max_modseq < (uint64_t)-1)
			reply = "NO CONDSTORE failed: Mailbox is read-only.";
		else
			reply = "OK Store ignored with read-only mailbox.";
		return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
				(cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
				0, reply);
	}

	if (ctx.silent)
		flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
	if (ctx.max_modseq < (uint64_t)-1) {
		/* update modseqs so we can check them early */
		flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
	}

	t = mailbox_transaction_begin(client->mailbox, flags);
	search_ctx = mailbox_search_init(t, search_args, NULL,
					 MAIL_FETCH_FLAGS, NULL);
	mail_search_args_unref(&search_args);

	i_array_init(&modified_set, 64);
	if (ctx.max_modseq < (uint32_t)-1) {
		/* STORE UNCHANGEDSINCE is being used */
		mailbox_transaction_set_max_modseq(t, ctx.max_modseq,
						   &modified_set);
	}

	while (mailbox_search_next(search_ctx, &mail)) {
		if (ctx.max_modseq < (uint64_t)-1) {
			/* check early so there's less work for transaction
			   commit if something has to be cancelled */
			if (mail_get_modseq(mail) > ctx.max_modseq) {
				seq_range_array_add(&modified_set, mail->seq);
				continue;
			}
		}
		if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0)
			mail_update_flags(mail, ctx.modify_type, ctx.flags);
		if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) {
			mail_update_keywords(mail, ctx.modify_type,
					     ctx.keywords);
		}
	}

	if (ctx.keywords != NULL)
		mailbox_keywords_unref(&ctx.keywords);

	ret = mailbox_search_deinit(&search_ctx);
	if (ret < 0)
		mailbox_transaction_rollback(&t);
	 else
		ret = mailbox_transaction_commit(&t);
	if (ret < 0) {
		array_free(&modified_set);
		client_send_box_error(cmd, client->mailbox);
		return TRUE;
	}

	if (array_count(&modified_set) == 0)
		tagged_reply = "OK Store completed.";
	else {
		if (cmd->uid) {
			i_array_init(&uids, array_count(&modified_set)*2);
			mailbox_get_uid_range(client->mailbox, &modified_set,
					      &uids);
			array_free(&modified_set);
			modified_set = uids;
		}
		str = str_new(cmd->pool, 256);
		str_append(str, "OK [MODIFIED ");
		imap_write_seq_range(str, &modified_set);
		str_append(str, "] Conditional store failed.");
		tagged_reply = str_c(str);
	}
	array_free(&modified_set);

	/* With UID STORE we have to return UID for the flags as well.
	   Unfortunately we don't have the ability to separate those
	   flag changes that were caused by UID STORE and those that
	   came externally, so we'll just send the UID for all flag
	   changes that we see. */
	if (cmd->uid && (!ctx.silent || (client->enabled_features &
					 MAILBOX_FEATURE_CONDSTORE) != 0))
		imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;

	return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
			imap_sync_flags, tagged_reply);
}