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