1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2  */
3 
4 /* NOTE: this file also contains the checkscript command due to its obvious
5  * similarities.
6  */
7 
8 #include "lib.h"
9 #include "ioloop.h"
10 #include "istream.h"
11 #include "ostream.h"
12 #include "iostream.h"
13 #include "str.h"
14 
15 #include "sieve.h"
16 #include "sieve-script.h"
17 #include "sieve-storage.h"
18 
19 #include "managesieve-parser.h"
20 
21 #include "managesieve-common.h"
22 #include "managesieve-client.h"
23 #include "managesieve-commands.h"
24 #include "managesieve-quota.h"
25 
26 #include <sys/time.h>
27 
28 struct cmd_putscript_context {
29 	struct client *client;
30 	struct client_command_context *cmd;
31 	struct sieve_storage *storage;
32 
33 	struct istream *input;
34 
35 	const char *scriptname;
36 	uoff_t script_size, max_script_size;
37 
38 	struct managesieve_parser *save_parser;
39 	struct sieve_storage_save_context *save_ctx;
40 
41 	bool script_size_valid:1;
42 };
43 
44 static void cmd_putscript_finish(struct cmd_putscript_context *ctx);
45 static bool cmd_putscript_continue_script(struct client_command_context *cmd);
46 
client_input_putscript(struct client * client)47 static void client_input_putscript(struct client *client)
48 {
49 	struct client_command_context *cmd = &client->cmd;
50 
51 	i_assert(!client->destroyed);
52 
53 	client->last_input = ioloop_time;
54 	timeout_reset(client->to_idle);
55 
56 	switch (i_stream_read(client->input)) {
57 	case -1:
58 		/* disconnected */
59 		cmd_putscript_finish(cmd->context);
60 		/* Reset command so that client_destroy() doesn't try to call
61 		   cmd_putscript_continue_script() anymore. */
62 		_client_reset_command(client);
63 		client_destroy(client, "Disconnected in PUTSCRIPT/CHECKSCRIPT");
64 		return;
65 	case -2:
66 		cmd_putscript_finish(cmd->context);
67 		if (client->command_pending) {
68 			/* uploaded script data, this is handled internally by
69 			   mailbox_save_continue() */
70 			break;
71 		}
72 
73 		/* parameter word is longer than max. input buffer size.
74 		   this is most likely an error, so skip the new data
75 		   until newline is found. */
76 		client->input_skip_line = TRUE;
77 
78 		client_send_command_error(cmd, "Too long argument.");
79 		cmd->param_error = TRUE;
80 		_client_reset_command(client);
81 		return;
82 	}
83 
84 	if (cmd->func(cmd)) {
85 		/* command execution was finished. Note that if cmd_sync()
86 		   didn't finish, we didn't get here but the input handler
87 		   has already been moved. So don't do anything important
88 		   here..
89 
90 		   reset command once again to reset cmd_sync()'s changes. */
91 		_client_reset_command(client);
92 
93 		if (client->input_pending)
94 			client_input(client);
95 	}
96 }
97 
cmd_putscript_finish(struct cmd_putscript_context * ctx)98 static void cmd_putscript_finish(struct cmd_putscript_context *ctx)
99 {
100 	managesieve_parser_destroy(&ctx->save_parser);
101 
102 	io_remove(&ctx->client->io);
103 	o_stream_set_flush_callback(ctx->client->output,
104 				    client_output, ctx->client);
105 
106 	if (ctx->save_ctx != NULL) {
107 		ctx->client->input_skip_line = TRUE;
108 		sieve_storage_save_cancel(&ctx->save_ctx);
109 	}
110 }
111 
cmd_putscript_continue_cancel(struct client_command_context * cmd)112 static bool cmd_putscript_continue_cancel(struct client_command_context *cmd)
113 {
114 	struct cmd_putscript_context *ctx = cmd->context;
115 	size_t size;
116 
117 	(void)i_stream_read(ctx->input);
118 	(void)i_stream_get_data(ctx->input, &size);
119 	i_stream_skip(ctx->input, size);
120 
121 	if (cmd->client->input->closed || ctx->input->eof ||
122 	    ctx->input->v_offset == ctx->script_size) {
123 		cmd_putscript_finish(ctx);
124 		return TRUE;
125 	}
126 	return FALSE;
127 }
128 
cmd_putscript_cancel(struct cmd_putscript_context * ctx,bool skip)129 static bool cmd_putscript_cancel(struct cmd_putscript_context *ctx, bool skip)
130 {
131 	ctx->client->input_skip_line = TRUE;
132 
133 	if (!skip) {
134 		cmd_putscript_finish(ctx);
135 		return TRUE;
136 	}
137 
138 	/* we have to read the nonsynced literal so we don't treat the uploaded
139 	   script as commands. */
140 	ctx->client->command_pending = TRUE;
141 	ctx->cmd->func = cmd_putscript_continue_cancel;
142 	ctx->cmd->context = ctx;
143 	return cmd_putscript_continue_cancel(ctx->cmd);
144 }
145 
cmd_putscript_storage_error(struct cmd_putscript_context * ctx)146 static void cmd_putscript_storage_error(struct cmd_putscript_context *ctx)
147 {
148 	struct client_command_context *cmd = ctx->cmd;
149 
150 	if (ctx->scriptname == NULL) {
151 		client_command_storage_error(cmd, "Failed to check script");
152 	} else {
153 		client_command_storage_error(cmd, "Failed to store script `%s'",
154 					     ctx->scriptname);
155 	}
156 }
157 
cmd_putscript_save(struct cmd_putscript_context * ctx)158 static bool cmd_putscript_save(struct cmd_putscript_context *ctx)
159 {
160 	/* Commit to save only when this is a putscript command */
161 	if (ctx->scriptname == NULL)
162 		return TRUE;
163 
164 	/* Check commit */
165 	if (sieve_storage_save_commit(&ctx->save_ctx) < 0) {
166 		cmd_putscript_storage_error(ctx);
167 		return FALSE;
168 	}
169 	return TRUE;
170 }
171 
172 static void
cmd_putscript_finish_script(struct cmd_putscript_context * ctx,struct sieve_script * script)173 cmd_putscript_finish_script(struct cmd_putscript_context *ctx,
174 			    struct sieve_script *script)
175 {
176 	struct client *client = ctx->client;
177 	struct client_command_context *cmd = ctx->cmd;
178 	struct sieve_error_handler *ehandler;
179 	enum sieve_compile_flags cpflags =
180 		SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_UPLOADED;
181 	struct sieve_binary *sbin;
182 	bool success = TRUE;
183 	enum sieve_error error;
184 	string_t *errors;
185 
186 	/* Mark this as an activation when we are replacing the
187 	   active script */
188 	if (sieve_storage_save_will_activate(ctx->save_ctx))
189 		cpflags |= SIEVE_COMPILE_FLAG_ACTIVATED;
190 
191 	/* Prepare error handler */
192 	errors = str_new(default_pool, 1024);
193 	ehandler = sieve_strbuf_ehandler_create(
194 		client->svinst, errors, TRUE,
195 		client->set->managesieve_max_compile_errors);
196 
197 	/* Compile */
198 	sbin = sieve_compile_script(script, ehandler, cpflags, &error);
199 	if (sbin == NULL) {
200 		const char *errormsg = NULL, *action;
201 
202 		if (error != SIEVE_ERROR_NOT_VALID) {
203 			errormsg = sieve_script_get_last_error(script, &error);
204 			if (error == SIEVE_ERROR_NONE)
205 				errormsg = NULL;
206 		}
207 
208 		action = (ctx->scriptname != NULL ?
209 			  t_strdup_printf("store script `%s'",
210 					  ctx->scriptname) :
211 			  "check script");
212 
213 		if (errormsg == NULL) {
214 			struct event_passthrough *e =
215 				client_command_create_finish_event(cmd)->
216 				add_str("error", "Compilation failed")->
217 				add_int("compile_errors",
218 					sieve_get_errors(ehandler))->
219 				add_int("compile_warnings",
220 					sieve_get_warnings(ehandler));
221 			e_debug(e->event(), "Failed to %s: "
222 				"Compilation failed (%u errors, %u warnings)",
223 				action, sieve_get_errors(ehandler),
224 				sieve_get_warnings(ehandler));
225 
226 			client_send_no(client, str_c(errors));
227 		} else {
228 			struct event_passthrough *e =
229 				client_command_create_finish_event(cmd)->
230 				add_str("error", errormsg);
231 			e_debug(e->event(), "Failed to %s: %s",
232 				action, errormsg);
233 
234 			client_send_no(client, errormsg);
235 		}
236 
237 		success = FALSE;
238 	} else {
239 		sieve_close(&sbin);
240 
241 		if (!cmd_putscript_save(ctx))
242 			success = FALSE;
243 	}
244 
245 	/* Finish up */
246 	cmd_putscript_finish(ctx);
247 
248 	/* Report result to user */
249 	if (success) {
250 		if (ctx->scriptname != NULL) {
251 			client->put_count++;
252 			client->put_bytes += ctx->script_size;
253 		} else {
254 			client->check_count++;
255 			client->check_bytes += ctx->script_size;
256 		}
257 
258 		struct event_passthrough *e =
259 			client_command_create_finish_event(cmd)->
260 			add_int("compile_warnings",
261 				sieve_get_warnings(ehandler));
262 		if (ctx->scriptname != NULL) {
263 			e_debug(e->event(), "Stored script `%s' successfully "
264 				"(%u warnings)", ctx->scriptname,
265 				sieve_get_warnings(ehandler));
266 		} else {
267 			e_debug(e->event(), "Checked script successfully "
268 				"(%u warnings)", sieve_get_warnings(ehandler));
269 		}
270 
271 		if (sieve_get_warnings(ehandler) > 0)
272 			client_send_okresp(client, "WARNINGS", str_c(errors));
273 		else if (ctx->scriptname != NULL)
274 			client_send_ok(client, "PUTSCRIPT completed.");
275 		else
276 			client_send_ok(client, "Script checked successfully.");
277 	}
278 
279 	sieve_error_handler_unref(&ehandler);
280 	str_free(&errors);
281 }
282 
cmd_putscript_handle_script(struct cmd_putscript_context * ctx)283 static void cmd_putscript_handle_script(struct cmd_putscript_context *ctx)
284 {
285 	struct client_command_context *cmd = ctx->cmd;
286 	struct sieve_script *script;
287 
288 	/* Obtain script object for uploaded script */
289 	script = sieve_storage_save_get_tempscript(ctx->save_ctx);
290 
291 	/* Check result */
292 	if (script == NULL) {
293 		cmd_putscript_storage_error(ctx);
294 		cmd_putscript_finish(ctx);
295 		return;
296 	}
297 
298 	/* If quoted string, the size was not known until now */
299 	if (!ctx->script_size_valid) {
300 		if (sieve_script_get_size(script, &ctx->script_size) < 0) {
301 			cmd_putscript_storage_error(ctx);
302 			cmd_putscript_finish(ctx);
303 			return;
304 		}
305 		ctx->script_size_valid = TRUE;
306 
307 		/* Check quota; max size is already checked */
308 		if (ctx->scriptname != NULL &&
309 		    !managesieve_quota_check_all(cmd, ctx->scriptname,
310 						 ctx->script_size)) {
311 			cmd_putscript_finish(ctx);
312 			return;
313 		}
314 	}
315 
316 	/* Try to compile and store the script */
317 	T_BEGIN {
318 		cmd_putscript_finish_script(ctx, script);
319 	} T_END;
320 }
321 
cmd_putscript_finish_parsing(struct client_command_context * cmd)322 static bool cmd_putscript_finish_parsing(struct client_command_context *cmd)
323 {
324 	struct client *client = cmd->client;
325 	struct cmd_putscript_context *ctx = cmd->context;
326 	const struct managesieve_arg *args;
327 	int ret;
328 
329 	/* if error occurs, the CRLF is already read. */
330 	client->input_skip_line = FALSE;
331 
332 	/* <script literal> */
333 	ret = managesieve_parser_read_args(ctx->save_parser, 0, 0, &args);
334 	if (ret == -1 || client->output->closed) {
335 		if (ctx->storage != NULL) {
336 			const char *msg;
337 			bool fatal ATTR_UNUSED;
338 
339 			msg = managesieve_parser_get_error(
340 				ctx->save_parser, &fatal);
341 			client_send_command_error(cmd, msg);
342 		}
343 		cmd_putscript_finish(ctx);
344 		return TRUE;
345 	}
346 	if (ret < 0) {
347 		/* need more data */
348 		return FALSE;
349 	}
350 
351 	if (MANAGESIEVE_ARG_IS_EOL(&args[0])) {
352 		/* Eat away the trailing CRLF */
353 		client->input_skip_line = TRUE;
354 
355 		cmd_putscript_handle_script(ctx);
356 		return TRUE;
357 	}
358 
359 	client_send_command_error(cmd, "Too many command arguments.");
360 	cmd_putscript_finish(ctx);
361 	return TRUE;
362 }
363 
cmd_putscript_continue_parsing(struct client_command_context * cmd)364 static bool cmd_putscript_continue_parsing(struct client_command_context *cmd)
365 {
366 	struct client *client = cmd->client;
367 	struct cmd_putscript_context *ctx = cmd->context;
368 	const struct managesieve_arg *args;
369 	int ret;
370 
371 	/* if error occurs, the CRLF is already read. */
372 	client->input_skip_line = FALSE;
373 
374 	/* <script literal> */
375 	ret = managesieve_parser_read_args(
376 		ctx->save_parser, 0, MANAGESIEVE_PARSE_FLAG_STRING_STREAM,
377 		&args);
378 	if (ret == -1 || client->output->closed) {
379 		cmd_putscript_finish(ctx);
380 		client_send_command_error(cmd, "Invalid arguments.");
381 		client->input_skip_line = TRUE;
382 		return TRUE;
383 	}
384 	if (ret < 0) {
385 		/* need more data */
386 		return FALSE;
387 	}
388 
389 	/* Validate the script argument */
390 	if (!managesieve_arg_get_string_stream(args,&ctx->input)) {
391 		client_send_command_error(cmd, "Invalid arguments.");
392 		return cmd_putscript_cancel(ctx, FALSE);
393 	}
394 
395 	if (i_stream_get_size(ctx->input, FALSE, &ctx->script_size) > 0) {
396 		ctx->script_size_valid = TRUE;
397 
398 		/* Check quota */
399 		if (ctx->scriptname == NULL) {
400 			if (!managesieve_quota_check_validsize(
401 				cmd, ctx->script_size))
402 				return cmd_putscript_cancel(ctx, TRUE);
403 		} else {
404 			if (!managesieve_quota_check_all(
405 				cmd, ctx->scriptname, ctx->script_size))
406 				return cmd_putscript_cancel(ctx, TRUE);
407 		}
408 
409 	} else {
410 		ctx->max_script_size =
411 			managesieve_quota_max_script_size(client);
412 	}
413 
414 	/* save the script */
415 	ctx->save_ctx = sieve_storage_save_init(ctx->storage, ctx->scriptname,
416 						ctx->input);
417 
418 	if (ctx->save_ctx == NULL) {
419 		/* save initialization failed */
420 		cmd_putscript_storage_error(ctx);
421 		return cmd_putscript_cancel(ctx, TRUE);
422 	}
423 
424 	/* after literal comes CRLF, if we fail make sure we eat it away */
425 	client->input_skip_line = TRUE;
426 
427 	client->command_pending = TRUE;
428 	cmd->func = cmd_putscript_continue_script;
429 	return cmd_putscript_continue_script(cmd);
430 }
431 
cmd_putscript_continue_script(struct client_command_context * cmd)432 static bool cmd_putscript_continue_script(struct client_command_context *cmd)
433 {
434 	struct client *client = cmd->client;
435 	struct cmd_putscript_context *ctx = cmd->context;
436 	size_t size;
437 	int ret;
438 
439 	if (ctx->save_ctx != NULL) {
440 		for (;;) {
441 			i_assert(!ctx->script_size_valid ||
442 				 ctx->input->v_offset <= ctx->script_size);
443 			if (ctx->max_script_size > 0 &&
444 			    ctx->input->v_offset > ctx->max_script_size) {
445 				(void)managesieve_quota_check_validsize(
446 					cmd, ctx->input->v_offset);
447 				cmd_putscript_finish(ctx);
448 				return TRUE;
449 			}
450 
451 			ret = i_stream_read(ctx->input);
452 			if ((ret != -1 || ctx->input->stream_errno != EINVAL ||
453 			     client->input->eof) &&
454 			    sieve_storage_save_continue(ctx->save_ctx) < 0) {
455 				/* we still have to finish reading the script
456 			   	  from client */
457 				sieve_storage_save_cancel(&ctx->save_ctx);
458 				break;
459 			}
460 			if (ret == -1 || ret == 0)
461 				break;
462 		}
463 	}
464 
465 	if (ctx->save_ctx == NULL) {
466 		(void)i_stream_read(ctx->input);
467 		(void)i_stream_get_data(ctx->input, &size);
468 		i_stream_skip(ctx->input, size);
469 	}
470 
471 	if (ctx->input->eof || client->input->closed) {
472 		bool failed = FALSE;
473 		bool all_written = FALSE;
474 
475 		if (!ctx->script_size_valid) {
476 			if (!client->input->eof &&
477 			    ctx->input->stream_errno == EINVAL) {
478 				client_send_command_error(
479 					cmd, t_strdup_printf(
480 						"Invalid input: %s",
481 						i_stream_get_error(ctx->input)));
482 				client->input_skip_line = TRUE;
483 				failed = TRUE;
484 			}
485 			all_written = (ctx->input->eof &&
486 				       ctx->input->stream_errno == 0);
487 
488 		} else {
489 			all_written = (ctx->input->v_offset == ctx->script_size);
490 		}
491 
492 		/* finished */
493 		ctx->input = NULL;
494 
495 		if (!failed) {
496 			if (ctx->save_ctx == NULL) {
497 				/* failed above */
498 				cmd_putscript_storage_error(ctx);
499 				failed = TRUE;
500 			} else if (!all_written) {
501 				/* client disconnected before it finished sending the
502 					 whole script. */
503 				failed = TRUE;
504 				sieve_storage_save_cancel(&ctx->save_ctx);
505 				const char *reason = t_strdup_printf(
506 					"%s (While appending in PUTSCRIPT/CHECKSCRIPT)",
507 					io_stream_get_disconnect_reason(client->input,
508 									client->output));
509 				client_disconnect(client, reason);
510 			} else if (sieve_storage_save_finish(ctx->save_ctx) < 0) {
511 				failed = TRUE;
512 				cmd_putscript_storage_error(ctx);
513 			} else {
514 				failed = client->input->closed;
515 			}
516 		}
517 
518 		if (failed) {
519 			cmd_putscript_finish(ctx);
520 			return TRUE;
521 		}
522 
523 		/* finish */
524 		client->command_pending = FALSE;
525 		managesieve_parser_reset(ctx->save_parser);
526 		cmd->func = cmd_putscript_finish_parsing;
527 		return cmd_putscript_finish_parsing(cmd);
528 	}
529 
530 	return FALSE;
531 }
532 
533 static bool
cmd_putscript_start(struct client_command_context * cmd,const char * scriptname)534 cmd_putscript_start(struct client_command_context *cmd, const char *scriptname)
535 {
536 	struct cmd_putscript_context *ctx;
537 	struct client *client = cmd->client;
538 
539 	ctx = p_new(cmd->pool, struct cmd_putscript_context, 1);
540 	ctx->cmd = cmd;
541 	ctx->client = client;
542 	ctx->storage = client->storage;
543 	ctx->scriptname = scriptname;
544 
545 	io_remove(&client->io);
546 	client->io = io_add(i_stream_get_fd(client->input), IO_READ,
547 			    client_input_putscript, client);
548 	/* putscript is special because we're only waiting on client input, not
549 	   client output, so disable the standard output handler until we're
550 	   finished */
551 	o_stream_unset_flush_callback(client->output);
552 
553 	ctx->save_parser = managesieve_parser_create(
554 		client->input, client->set->managesieve_max_line_length);
555 
556 	cmd->func = cmd_putscript_continue_parsing;
557 	cmd->context = ctx;
558 	return cmd_putscript_continue_parsing(cmd);
559 
560 }
561 
cmd_putscript(struct client_command_context * cmd)562 bool cmd_putscript(struct client_command_context *cmd)
563 {
564 	const char *scriptname;
565 
566 	/* <scriptname> */
567 	if (!client_read_string_args(cmd, FALSE, 1, &scriptname))
568 		return FALSE;
569 
570 	event_add_str(cmd->event, "script_name", scriptname);
571 
572 	return cmd_putscript_start(cmd, scriptname);
573 }
574 
cmd_checkscript(struct client_command_context * cmd)575 bool cmd_checkscript(struct client_command_context *cmd)
576 {
577 	return cmd_putscript_start(cmd, NULL);
578 }
579