/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file */ /* NOTE: this file also contains the checkscript command due to its obvious * similarities. */ #include "lib.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "iostream.h" #include "str.h" #include "sieve.h" #include "sieve-script.h" #include "sieve-storage.h" #include "managesieve-parser.h" #include "managesieve-common.h" #include "managesieve-client.h" #include "managesieve-commands.h" #include "managesieve-quota.h" #include struct cmd_putscript_context { struct client *client; struct client_command_context *cmd; struct sieve_storage *storage; struct istream *input; const char *scriptname; uoff_t script_size, max_script_size; struct managesieve_parser *save_parser; struct sieve_storage_save_context *save_ctx; bool script_size_valid:1; }; static void cmd_putscript_finish(struct cmd_putscript_context *ctx); static bool cmd_putscript_continue_script(struct client_command_context *cmd); static void client_input_putscript(struct client *client) { struct client_command_context *cmd = &client->cmd; i_assert(!client->destroyed); client->last_input = ioloop_time; timeout_reset(client->to_idle); switch (i_stream_read(client->input)) { case -1: /* disconnected */ cmd_putscript_finish(cmd->context); /* Reset command so that client_destroy() doesn't try to call cmd_putscript_continue_script() anymore. */ _client_reset_command(client); client_destroy(client, "Disconnected in PUTSCRIPT/CHECKSCRIPT"); return; case -2: cmd_putscript_finish(cmd->context); if (client->command_pending) { /* uploaded script data, this is handled internally by mailbox_save_continue() */ break; } /* parameter word is longer than max. input buffer size. this is most likely an error, so skip the new data until newline is found. */ client->input_skip_line = TRUE; client_send_command_error(cmd, "Too long argument."); cmd->param_error = TRUE; _client_reset_command(client); return; } if (cmd->func(cmd)) { /* command execution was finished. Note that if cmd_sync() didn't finish, we didn't get here but the input handler has already been moved. So don't do anything important here.. reset command once again to reset cmd_sync()'s changes. */ _client_reset_command(client); if (client->input_pending) client_input(client); } } static void cmd_putscript_finish(struct cmd_putscript_context *ctx) { managesieve_parser_destroy(&ctx->save_parser); io_remove(&ctx->client->io); o_stream_set_flush_callback(ctx->client->output, client_output, ctx->client); if (ctx->save_ctx != NULL) { ctx->client->input_skip_line = TRUE; sieve_storage_save_cancel(&ctx->save_ctx); } } static bool cmd_putscript_continue_cancel(struct client_command_context *cmd) { struct cmd_putscript_context *ctx = cmd->context; size_t size; (void)i_stream_read(ctx->input); (void)i_stream_get_data(ctx->input, &size); i_stream_skip(ctx->input, size); if (cmd->client->input->closed || ctx->input->eof || ctx->input->v_offset == ctx->script_size) { cmd_putscript_finish(ctx); return TRUE; } return FALSE; } static bool cmd_putscript_cancel(struct cmd_putscript_context *ctx, bool skip) { ctx->client->input_skip_line = TRUE; if (!skip) { cmd_putscript_finish(ctx); return TRUE; } /* we have to read the nonsynced literal so we don't treat the uploaded script as commands. */ ctx->client->command_pending = TRUE; ctx->cmd->func = cmd_putscript_continue_cancel; ctx->cmd->context = ctx; return cmd_putscript_continue_cancel(ctx->cmd); } static void cmd_putscript_storage_error(struct cmd_putscript_context *ctx) { struct client_command_context *cmd = ctx->cmd; if (ctx->scriptname == NULL) { client_command_storage_error(cmd, "Failed to check script"); } else { client_command_storage_error(cmd, "Failed to store script `%s'", ctx->scriptname); } } static bool cmd_putscript_save(struct cmd_putscript_context *ctx) { /* Commit to save only when this is a putscript command */ if (ctx->scriptname == NULL) return TRUE; /* Check commit */ if (sieve_storage_save_commit(&ctx->save_ctx) < 0) { cmd_putscript_storage_error(ctx); return FALSE; } return TRUE; } static void cmd_putscript_finish_script(struct cmd_putscript_context *ctx, struct sieve_script *script) { struct client *client = ctx->client; struct client_command_context *cmd = ctx->cmd; struct sieve_error_handler *ehandler; enum sieve_compile_flags cpflags = SIEVE_COMPILE_FLAG_NOGLOBAL | SIEVE_COMPILE_FLAG_UPLOADED; struct sieve_binary *sbin; bool success = TRUE; enum sieve_error error; string_t *errors; /* Mark this as an activation when we are replacing the active script */ if (sieve_storage_save_will_activate(ctx->save_ctx)) cpflags |= SIEVE_COMPILE_FLAG_ACTIVATED; /* Prepare error handler */ errors = str_new(default_pool, 1024); ehandler = sieve_strbuf_ehandler_create( client->svinst, errors, TRUE, client->set->managesieve_max_compile_errors); /* Compile */ sbin = sieve_compile_script(script, ehandler, cpflags, &error); if (sbin == NULL) { const char *errormsg = NULL, *action; if (error != SIEVE_ERROR_NOT_VALID) { errormsg = sieve_script_get_last_error(script, &error); if (error == SIEVE_ERROR_NONE) errormsg = NULL; } action = (ctx->scriptname != NULL ? t_strdup_printf("store script `%s'", ctx->scriptname) : "check script"); if (errormsg == NULL) { struct event_passthrough *e = client_command_create_finish_event(cmd)-> add_str("error", "Compilation failed")-> add_int("compile_errors", sieve_get_errors(ehandler))-> add_int("compile_warnings", sieve_get_warnings(ehandler)); e_debug(e->event(), "Failed to %s: " "Compilation failed (%u errors, %u warnings)", action, sieve_get_errors(ehandler), sieve_get_warnings(ehandler)); client_send_no(client, str_c(errors)); } else { struct event_passthrough *e = client_command_create_finish_event(cmd)-> add_str("error", errormsg); e_debug(e->event(), "Failed to %s: %s", action, errormsg); client_send_no(client, errormsg); } success = FALSE; } else { sieve_close(&sbin); if (!cmd_putscript_save(ctx)) success = FALSE; } /* Finish up */ cmd_putscript_finish(ctx); /* Report result to user */ if (success) { if (ctx->scriptname != NULL) { client->put_count++; client->put_bytes += ctx->script_size; } else { client->check_count++; client->check_bytes += ctx->script_size; } struct event_passthrough *e = client_command_create_finish_event(cmd)-> add_int("compile_warnings", sieve_get_warnings(ehandler)); if (ctx->scriptname != NULL) { e_debug(e->event(), "Stored script `%s' successfully " "(%u warnings)", ctx->scriptname, sieve_get_warnings(ehandler)); } else { e_debug(e->event(), "Checked script successfully " "(%u warnings)", sieve_get_warnings(ehandler)); } if (sieve_get_warnings(ehandler) > 0) client_send_okresp(client, "WARNINGS", str_c(errors)); else if (ctx->scriptname != NULL) client_send_ok(client, "PUTSCRIPT completed."); else client_send_ok(client, "Script checked successfully."); } sieve_error_handler_unref(&ehandler); str_free(&errors); } static void cmd_putscript_handle_script(struct cmd_putscript_context *ctx) { struct client_command_context *cmd = ctx->cmd; struct sieve_script *script; /* Obtain script object for uploaded script */ script = sieve_storage_save_get_tempscript(ctx->save_ctx); /* Check result */ if (script == NULL) { cmd_putscript_storage_error(ctx); cmd_putscript_finish(ctx); return; } /* If quoted string, the size was not known until now */ if (!ctx->script_size_valid) { if (sieve_script_get_size(script, &ctx->script_size) < 0) { cmd_putscript_storage_error(ctx); cmd_putscript_finish(ctx); return; } ctx->script_size_valid = TRUE; /* Check quota; max size is already checked */ if (ctx->scriptname != NULL && !managesieve_quota_check_all(cmd, ctx->scriptname, ctx->script_size)) { cmd_putscript_finish(ctx); return; } } /* Try to compile and store the script */ T_BEGIN { cmd_putscript_finish_script(ctx, script); } T_END; } static bool cmd_putscript_finish_parsing(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_putscript_context *ctx = cmd->context; const struct managesieve_arg *args; int ret; /* if error occurs, the CRLF is already read. */ client->input_skip_line = FALSE; /*