1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "imap-common.h"
4 #include "seq-range-array.h"
5 #include "str.h"
6 #include "imap-commands.h"
7 #include "imap-search-args.h"
8 #include "imap-util.h"
9 
10 
11 struct imap_store_context {
12 	struct client_command_context *cmd;
13 	uint64_t max_modseq;
14 
15 	enum mail_flags flags;
16 	struct mail_keywords *keywords;
17 
18 	enum modify_type modify_type;
19 	bool silent;
20 };
21 
22 static bool
get_modify_type(struct imap_store_context * ctx,const char * type)23 get_modify_type(struct imap_store_context *ctx, const char *type)
24 {
25 	if (*type == '+') {
26 		ctx->modify_type = MODIFY_ADD;
27 		type++;
28 	} else if (*type == '-') {
29 		ctx->modify_type = MODIFY_REMOVE;
30 		type++;
31 	} else {
32 		ctx->modify_type = MODIFY_REPLACE;
33 	}
34 
35 	if (strncasecmp(type, "FLAGS", 5) != 0)
36 		return FALSE;
37 
38 	ctx->silent = strcasecmp(type+5, ".SILENT") == 0;
39 	if (!ctx->silent && type[5] != '\0')
40 		return FALSE;
41 	return TRUE;
42 }
43 
44 static bool
store_parse_modifiers(struct imap_store_context * ctx,const struct imap_arg * args)45 store_parse_modifiers(struct imap_store_context *ctx,
46 		      const struct imap_arg *args)
47 {
48 	const char *name, *value;
49 
50 	for (; !IMAP_ARG_IS_EOL(args); args += 2) {
51 		if (!imap_arg_get_atom(&args[0], &name) ||
52 		    !imap_arg_get_atom(&args[1], &value)) {
53 			client_send_command_error(ctx->cmd,
54 				"Invalid STORE modifiers.");
55 			return FALSE;
56 		}
57 
58 		if (strcasecmp(name, "UNCHANGEDSINCE") == 0) {
59 			if (ctx->cmd->client->nonpermanent_modseqs) {
60 				client_send_command_error(ctx->cmd,
61 					"STORE UNCHANGEDSINCE can't be used with non-permanent modseqs");
62 				return FALSE;
63 			}
64 			if (str_to_uint64(value, &ctx->max_modseq) < 0) {
65 				client_send_command_error(ctx->cmd,
66 							  "Invalid modseq");
67 				return FALSE;
68 			}
69 			client_enable(ctx->cmd->client, imap_feature_condstore);
70 		} else {
71 			client_send_command_error(ctx->cmd,
72 						  "Unknown STORE modifier");
73 			return FALSE;
74 		}
75 	}
76 	return TRUE;
77 }
78 
79 static bool
store_parse_args(struct imap_store_context * ctx,const struct imap_arg * args)80 store_parse_args(struct imap_store_context *ctx, const struct imap_arg *args)
81 {
82 	struct client_command_context *cmd = ctx->cmd;
83 	const struct imap_arg *list_args;
84 	const char *type;
85 	const char *const *keywords_list = NULL;
86 
87 	ctx->max_modseq = (uint64_t)-1;
88 	if (imap_arg_get_list(args, &list_args)) {
89 		if (!store_parse_modifiers(ctx, list_args))
90 			return FALSE;
91 		args++;
92 	}
93 
94 	if (!imap_arg_get_astring(args, &type) ||
95 	    !get_modify_type(ctx, type)) {
96 		client_send_command_error(cmd, "Invalid arguments.");
97 		return FALSE;
98 	}
99 	args++;
100 
101 	if (imap_arg_get_list(args, &list_args)) {
102 		if (!client_parse_mail_flags(cmd, list_args,
103 					     &ctx->flags, &keywords_list))
104 			return FALSE;
105 	} else {
106 		if (!client_parse_mail_flags(cmd, args,
107 					     &ctx->flags, &keywords_list))
108 			return FALSE;
109 	}
110 
111 	if (keywords_list != NULL || ctx->modify_type == MODIFY_REPLACE) {
112 		if (mailbox_keywords_create(cmd->client->mailbox, keywords_list,
113 					    &ctx->keywords) < 0) {
114 			/* invalid keywords */
115 			client_send_box_error(cmd, cmd->client->mailbox);
116 			return FALSE;
117 		}
118 	}
119 	return TRUE;
120 }
121 
cmd_store(struct client_command_context * cmd)122 bool cmd_store(struct client_command_context *cmd)
123 {
124 	struct client *client = cmd->client;
125 	const struct imap_arg *args;
126 	struct mail_search_args *search_args;
127 	struct mail_search_context *search_ctx;
128         struct mailbox_transaction_context *t;
129 	struct mail *mail;
130 	struct imap_store_context ctx;
131 	ARRAY_TYPE(seq_range) modified_set, uids;
132 	enum mailbox_transaction_flags flags = 0;
133 	enum imap_sync_flags imap_sync_flags = 0;
134 	const char *set, *reply, *tagged_reply;
135 	string_t *str;
136 	int ret;
137 	bool update_deletes;
138 	unsigned int deleted_count;
139 
140 	if (!client_read_args(cmd, 0, 0, &args))
141 		return FALSE;
142 
143 	if (!client_verify_open_mailbox(cmd))
144 		return TRUE;
145 
146 	if (!imap_arg_get_atom(args, &set)) {
147 		client_send_command_error(cmd, "Invalid arguments.");
148 		return TRUE;
149 	}
150 	ret = imap_search_get_seqset(cmd, set, cmd->uid, &search_args);
151 	if (ret <= 0)
152 		return ret < 0;
153 
154 	i_zero(&ctx);
155 	ctx.cmd = cmd;
156 	if (!store_parse_args(&ctx, ++args)) {
157 		mail_search_args_unref(&search_args);
158 		return TRUE;
159 	}
160 
161 	if (client->mailbox_examined) {
162 		mail_search_args_unref(&search_args);
163 		if (ctx.max_modseq < (uint64_t)-1)
164 			reply = "NO CONDSTORE failed: Mailbox is read-only.";
165 		else
166 			reply = "OK Store ignored with read-only mailbox.";
167 		return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
168 				(cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
169 				0, reply);
170 	}
171 
172 	if (ctx.silent)
173 		flags |= MAILBOX_TRANSACTION_FLAG_HIDE;
174 	if (ctx.max_modseq < (uint64_t)-1) {
175 		/* update modseqs so we can check them early */
176 		flags |= MAILBOX_TRANSACTION_FLAG_REFRESH;
177 	}
178 
179 	t = mailbox_transaction_begin(client->mailbox, flags,
180 				      imap_client_command_get_reason(cmd));
181 
182 	search_ctx = mailbox_search_init(t, search_args, NULL,
183 					 MAIL_FETCH_FLAGS, NULL);
184 	mail_search_args_unref(&search_args);
185 
186 	i_array_init(&modified_set, 64);
187 	if (ctx.max_modseq < (uint64_t)-1) {
188 		/* STORE UNCHANGEDSINCE is being used */
189 		mailbox_transaction_set_max_modseq(t, ctx.max_modseq,
190 						   &modified_set);
191 	}
192 
193 	update_deletes = (ctx.flags & MAIL_DELETED) != 0 &&
194 		ctx.modify_type != MODIFY_REMOVE;
195 	deleted_count = 0;
196 	while (mailbox_search_next(search_ctx, &mail)) {
197 		if (ctx.max_modseq < (uint64_t)-1) {
198 			/* check early so there's less work for transaction
199 			   commit if something has to be cancelled */
200 			if (mail_get_modseq(mail) > ctx.max_modseq) {
201 				seq_range_array_add(&modified_set, mail->seq);
202 				continue;
203 			}
204 		}
205 		if (update_deletes) {
206 			if ((mail_get_flags(mail) & MAIL_DELETED) == 0)
207 				deleted_count++;
208 		}
209 		if (ctx.modify_type == MODIFY_REPLACE || ctx.flags != 0)
210 			mail_update_flags(mail, ctx.modify_type, ctx.flags);
211 		if (ctx.modify_type == MODIFY_REPLACE || ctx.keywords != NULL) {
212 			mail_update_keywords(mail, ctx.modify_type,
213 					     ctx.keywords);
214 		}
215 	}
216 
217 	if (ctx.keywords != NULL)
218 		mailbox_keywords_unref(&ctx.keywords);
219 
220 	ret = mailbox_search_deinit(&search_ctx);
221 	if (ret < 0)
222 		mailbox_transaction_rollback(&t);
223 	 else
224 		ret = mailbox_transaction_commit(&t);
225 	if (ret < 0) {
226 		array_free(&modified_set);
227 		client_send_box_error(cmd, client->mailbox);
228 		return TRUE;
229 	}
230 	client->deleted_count += deleted_count;
231 
232 	if (array_count(&modified_set) == 0)
233 		tagged_reply = "OK Store completed.";
234 	else {
235 		if (cmd->uid) {
236 			i_array_init(&uids, array_count(&modified_set)*2);
237 			mailbox_get_uid_range(client->mailbox, &modified_set,
238 					      &uids);
239 			array_free(&modified_set);
240 			modified_set = uids;
241 		}
242 		str = str_new(cmd->pool, 256);
243 		str_append(str, "OK [MODIFIED ");
244 		imap_write_seq_range(str, &modified_set);
245 		str_append(str, "] Conditional store failed.");
246 		tagged_reply = str_c(str);
247 	}
248 	array_free(&modified_set);
249 
250 	/* With UID STORE we have to return UID for the flags as well.
251 	   Unfortunately we don't have the ability to separate those
252 	   flag changes that were caused by UID STORE and those that
253 	   came externally, so we'll just send the UID for all flag
254 	   changes that we see. */
255 	if (cmd->uid && (!ctx.silent ||
256 			 client_has_enabled(client, imap_feature_condstore)))
257 		imap_sync_flags |= IMAP_SYNC_FLAG_SEND_UID;
258 
259 	return cmd_sync(cmd, (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
260 			imap_sync_flags, tagged_reply);
261 }
262