1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3 #include "imap-common.h"
4 #include "ostream.h"
5 #include "imap-resp-code.h"
6 #include "imap-commands.h"
7 #include "imap-fetch.h"
8 #include "imap-search-args.h"
9 #include "mail-search.h"
10
11
12 static const char *all_macro[] = {
13 "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", NULL
14 };
15 static const char *fast_macro[] = {
16 "FLAGS", "INTERNALDATE", "RFC822.SIZE", NULL
17 };
18 static const char *full_macro[] = {
19 "FLAGS", "INTERNALDATE", "RFC822.SIZE", "ENVELOPE", "BODY", NULL
20 };
21
22 static bool
imap_fetch_cmd_init_handler(struct imap_fetch_context * ctx,struct client_command_context * cmd,const char * name,const struct imap_arg ** args)23 imap_fetch_cmd_init_handler(struct imap_fetch_context *ctx,
24 struct client_command_context *cmd,
25 const char *name, const struct imap_arg **args)
26 {
27 struct imap_fetch_init_context init_ctx;
28
29 i_zero(&init_ctx);
30 init_ctx.fetch_ctx = ctx;
31 init_ctx.pool = ctx->ctx_pool;
32 init_ctx.name = name;
33 init_ctx.args = *args;
34
35 if (!imap_fetch_init_handler(&init_ctx)) {
36 i_assert(init_ctx.error != NULL);
37 client_send_command_error(cmd, init_ctx.error);
38 return FALSE;
39 }
40 *args = init_ctx.args;
41 return TRUE;
42 }
43
44 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)45 fetch_parse_args(struct imap_fetch_context *ctx,
46 struct client_command_context *cmd,
47 const struct imap_arg *arg, const struct imap_arg **next_arg_r)
48 {
49 const char *str, *const *macro;
50
51 if (cmd->uid) {
52 if (!imap_fetch_cmd_init_handler(ctx, cmd, "UID", &arg))
53 return FALSE;
54 }
55 if (imap_arg_get_atom(arg, &str)) {
56 str = t_str_ucase(str);
57 arg++;
58
59 /* handle macros first */
60 if (strcmp(str, "ALL") == 0)
61 macro = all_macro;
62 else if (strcmp(str, "FAST") == 0)
63 macro = fast_macro;
64 else if (strcmp(str, "FULL") == 0)
65 macro = full_macro;
66 else {
67 macro = NULL;
68 if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
69 return FALSE;
70 }
71 if (macro != NULL) {
72 while (*macro != NULL) {
73 if (!imap_fetch_cmd_init_handler(ctx, cmd, *macro, &arg))
74 return FALSE;
75 macro++;
76 }
77 }
78 *next_arg_r = arg;
79 } else {
80 *next_arg_r = arg + 1;
81 arg = imap_arg_as_list(arg);
82 if (IMAP_ARG_IS_EOL(arg)) {
83 client_send_command_error(cmd,
84 "FETCH list is empty.");
85 return FALSE;
86 }
87 while (imap_arg_get_atom(arg, &str)) {
88 str = t_str_ucase(str);
89 arg++;
90 if (!imap_fetch_cmd_init_handler(ctx, cmd, str, &arg))
91 return FALSE;
92 }
93 if (!IMAP_ARG_IS_EOL(arg)) {
94 client_send_command_error(cmd,
95 "FETCH list contains non-atoms.");
96 return FALSE;
97 }
98 }
99 return TRUE;
100 }
101
102 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)103 fetch_parse_modifier(struct imap_fetch_context *ctx,
104 struct client_command_context *cmd,
105 struct mail_search_args *search_args,
106 const char *name, const struct imap_arg **args,
107 bool *send_vanished)
108 {
109 const char *str;
110 uint64_t modseq;
111
112 if (strcmp(name, "CHANGEDSINCE") == 0) {
113 if (cmd->client->nonpermanent_modseqs) {
114 client_send_command_error(cmd,
115 "FETCH CHANGEDSINCE can't be used with non-permanent modseqs");
116 return FALSE;
117 }
118 if (!imap_arg_get_atom(*args, &str) ||
119 str_to_uint64(str, &modseq) < 0) {
120 client_send_command_error(cmd,
121 "Invalid CHANGEDSINCE modseq.");
122 return FALSE;
123 }
124 *args += 1;
125 imap_search_add_changed_since(search_args, modseq);
126 imap_fetch_init_nofail_handler(ctx, imap_fetch_modseq_init);
127 return TRUE;
128 }
129 if (strcmp(name, "VANISHED") == 0 && cmd->uid) {
130 if (!client_has_enabled(ctx->client, imap_feature_qresync)) {
131 client_send_command_error(cmd, "QRESYNC not enabled");
132 return FALSE;
133 }
134 *send_vanished = TRUE;
135 return TRUE;
136 }
137
138 client_send_command_error(cmd, "Unknown FETCH modifier");
139 return FALSE;
140 }
141
142 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)143 fetch_parse_modifiers(struct imap_fetch_context *ctx,
144 struct client_command_context *cmd,
145 struct mail_search_args *search_args,
146 const struct imap_arg *args, bool *send_vanished_r)
147 {
148 const char *name;
149
150 *send_vanished_r = FALSE;
151
152 while (!IMAP_ARG_IS_EOL(args)) {
153 if (!imap_arg_get_atom(args, &name)) {
154 client_send_command_error(cmd,
155 "FETCH modifiers contain non-atoms.");
156 return FALSE;
157 }
158 args++;
159 if (!fetch_parse_modifier(ctx, cmd, search_args,
160 t_str_ucase(name),
161 &args, send_vanished_r))
162 return FALSE;
163 }
164 if (*send_vanished_r &&
165 (search_args->args->next == NULL ||
166 search_args->args->next->type != SEARCH_MODSEQ)) {
167 client_send_command_error(cmd,
168 "VANISHED used without CHANGEDSINCE");
169 return FALSE;
170 }
171 return TRUE;
172 }
173
cmd_fetch_finished(struct client_command_context * cmd ATTR_UNUSED)174 static bool cmd_fetch_finished(struct client_command_context *cmd ATTR_UNUSED)
175 {
176 return TRUE;
177 }
178
imap_fetch_is_failed_retry(struct imap_fetch_context * ctx)179 static bool imap_fetch_is_failed_retry(struct imap_fetch_context *ctx)
180 {
181 if (!array_is_created(&ctx->client->fetch_failed_uids) ||
182 !array_is_created(&ctx->fetch_failed_uids))
183 return FALSE;
184 return seq_range_array_have_common(&ctx->client->fetch_failed_uids,
185 &ctx->fetch_failed_uids);
186
187 }
188
imap_fetch_add_failed_uids(struct imap_fetch_context * ctx)189 static void imap_fetch_add_failed_uids(struct imap_fetch_context *ctx)
190 {
191 if (!array_is_created(&ctx->fetch_failed_uids))
192 return;
193 if (!array_is_created(&ctx->client->fetch_failed_uids)) {
194 p_array_init(&ctx->client->fetch_failed_uids, ctx->client->pool,
195 array_count(&ctx->fetch_failed_uids));
196 }
197 seq_range_array_merge(&ctx->client->fetch_failed_uids,
198 &ctx->fetch_failed_uids);
199 }
200
cmd_fetch_finish(struct imap_fetch_context * ctx,struct client_command_context * cmd)201 static bool cmd_fetch_finish(struct imap_fetch_context *ctx,
202 struct client_command_context *cmd)
203 {
204 static const char *ok_message = "OK Fetch completed.";
205 const char *tagged_reply = ok_message;
206 enum mail_error error;
207 bool seen_flags_changed = ctx->state.seen_flags_changed;
208
209 if (ctx->state.skipped_expunged_msgs) {
210 tagged_reply = "OK ["IMAP_RESP_CODE_EXPUNGEISSUED"] "
211 "Some messages were already expunged.";
212 }
213
214 if (imap_fetch_end(ctx) < 0) {
215 const char *client_error;
216
217 if (cmd->client->output->closed) {
218 /* If we're canceling we need to finish this command
219 or we'll assert crash. But normally we want to
220 return FALSE so that the disconnect message logs
221 about this fetch command and that these latest
222 output bytes are included in it (which wouldn't
223 happen if we called client_disconnect() here
224 directly). */
225 cmd->func = cmd_fetch_finished;
226 imap_fetch_free(&ctx);
227 return cmd->cancel;
228 }
229
230 if (ctx->error == MAIL_ERROR_NONE)
231 client_error = mailbox_get_last_error(cmd->client->mailbox, &error);
232 else {
233 client_error = ctx->errstr;
234 error = ctx->error;
235 }
236 if (error == MAIL_ERROR_CONVERSION) {
237 /* BINARY found unsupported Content-Transfer-Encoding */
238 tagged_reply = t_strdup_printf(
239 "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] %s", client_error);
240 } else if (error == MAIL_ERROR_INVALIDDATA) {
241 /* Content was invalid */
242 tagged_reply = t_strdup_printf(
243 "NO ["IMAP_RESP_CODE_PARSE"] %s", client_error);
244 } else if (cmd->client->set->parsed_fetch_failure != IMAP_CLIENT_FETCH_FAILURE_NO_AFTER ||
245 imap_fetch_is_failed_retry(ctx)) {
246 /* By default we never want to reply NO to FETCH
247 requests, because many IMAP clients become confused
248 about what they should on NO. A disconnection causes
249 less confusion. */
250 const char *internal_error =
251 mailbox_get_last_internal_error(cmd->client->mailbox, NULL);
252 client_send_line(cmd->client, t_strconcat(
253 "* BYE FETCH failed: ", client_error, NULL));
254 client_disconnect(cmd->client, t_strconcat(
255 "FETCH failed: ", internal_error, NULL));
256 imap_fetch_free(&ctx);
257 return TRUE;
258 } else {
259 /* Use a tagged NO to FETCH failure, but only if client
260 hasn't repeated the FETCH to the same email (so that
261 we avoid infinitely retries from client.) */
262 imap_fetch_add_failed_uids(ctx);
263 tagged_reply = t_strdup_printf(
264 "NO ["IMAP_RESP_CODE_SERVERBUG"] %s", client_error);
265 }
266 }
267 imap_fetch_free(&ctx);
268
269 return cmd_sync(cmd,
270 (seen_flags_changed ? 0 : MAILBOX_SYNC_FLAG_FAST) |
271 (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES), 0,
272 tagged_reply);
273 }
274
cmd_fetch_continue(struct client_command_context * cmd)275 static bool cmd_fetch_continue(struct client_command_context *cmd)
276 {
277 struct imap_fetch_context *ctx = cmd->context;
278
279 if (imap_fetch_more(ctx, cmd) == 0) {
280 /* unfinished */
281 return FALSE;
282 }
283 return cmd_fetch_finish(ctx, cmd);
284 }
285
cmd_fetch(struct client_command_context * cmd)286 bool cmd_fetch(struct client_command_context *cmd)
287 {
288 struct client *client = cmd->client;
289 struct imap_fetch_context *ctx;
290 const struct imap_arg *args, *next_arg, *list_arg;
291 struct mail_search_args *search_args;
292 struct imap_fetch_qresync_args qresync_args;
293 const char *messageset;
294 bool send_vanished = FALSE;
295 int ret;
296
297 if (!client_read_args(cmd, 0, 0, &args))
298 return FALSE;
299
300 if (!client_verify_open_mailbox(cmd))
301 return TRUE;
302
303 /* <messageset> <field(s)> [(modifiers)] */
304 if (!imap_arg_get_atom(&args[0], &messageset) ||
305 (args[1].type != IMAP_ARG_LIST && args[1].type != IMAP_ARG_ATOM) ||
306 (!IMAP_ARG_IS_EOL(&args[2]) && args[2].type != IMAP_ARG_LIST)) {
307 client_send_command_error(cmd, "Invalid arguments.");
308 return TRUE;
309 }
310
311 /* UID FETCH VANISHED needs the uidset, so convert it to
312 sequence set later */
313 ret = imap_search_get_anyset(cmd, messageset, cmd->uid, &search_args);
314 if (ret <= 0)
315 return ret < 0;
316
317 ctx = imap_fetch_alloc(client, cmd->pool,
318 imap_client_command_get_reason(cmd));
319
320 if (!fetch_parse_args(ctx, cmd, &args[1], &next_arg) ||
321 (imap_arg_get_list(next_arg, &list_arg) &&
322 !fetch_parse_modifiers(ctx, cmd, search_args, list_arg,
323 &send_vanished))) {
324 imap_fetch_free(&ctx);
325 mail_search_args_unref(&search_args);
326 return TRUE;
327 }
328
329 if (send_vanished) {
330 i_zero(&qresync_args);
331 if (imap_fetch_send_vanished(client, client->mailbox,
332 search_args, &qresync_args) < 0) {
333 mail_search_args_unref(&search_args);
334 return cmd_fetch_finish(ctx, cmd);
335 }
336 }
337
338 imap_fetch_begin(ctx, client->mailbox, search_args);
339 mail_search_args_unref(&search_args);
340
341 if (imap_fetch_more(ctx, cmd) == 0) {
342 /* unfinished */
343 cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
344
345 cmd->func = cmd_fetch_continue;
346 cmd->context = ctx;
347 return FALSE;
348 }
349 return cmd_fetch_finish(ctx, cmd);
350 }
351