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