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