summaryrefslogtreecommitdiffstats
path: root/pigeonhole/src/managesieve/cmd-putscript.c
diff options
context:
space:
mode:
Diffstat (limited to 'pigeonhole/src/managesieve/cmd-putscript.c')
-rw-r--r--pigeonhole/src/managesieve/cmd-putscript.c578
1 files changed, 578 insertions, 0 deletions
diff --git a/pigeonhole/src/managesieve/cmd-putscript.c b/pigeonhole/src/managesieve/cmd-putscript.c
new file mode 100644
index 0000000..1998cd9
--- /dev/null
+++ b/pigeonhole/src/managesieve/cmd-putscript.c
@@ -0,0 +1,578 @@
+/* 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 <sys/time.h>
+
+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;
+
+ /* <script literal> */
+ ret = managesieve_parser_read_args(ctx->save_parser, 0, 0, &args);
+ if (ret == -1 || client->output->closed) {
+ if (ctx->storage != NULL) {
+ const char *msg;
+ bool fatal ATTR_UNUSED;
+
+ msg = managesieve_parser_get_error(
+ ctx->save_parser, &fatal);
+ client_send_command_error(cmd, msg);
+ }
+ cmd_putscript_finish(ctx);
+ return TRUE;
+ }
+ if (ret < 0) {
+ /* need more data */
+ return FALSE;
+ }
+
+ if (MANAGESIEVE_ARG_IS_EOL(&args[0])) {
+ /* Eat away the trailing CRLF */
+ client->input_skip_line = TRUE;
+
+ cmd_putscript_handle_script(ctx);
+ return TRUE;
+ }
+
+ client_send_command_error(cmd, "Too many command arguments.");
+ cmd_putscript_finish(ctx);
+ return TRUE;
+}
+
+static bool cmd_putscript_continue_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;
+
+ /* <script literal> */
+ ret = managesieve_parser_read_args(
+ ctx->save_parser, 0, MANAGESIEVE_PARSE_FLAG_STRING_STREAM,
+ &args);
+ if (ret == -1 || client->output->closed) {
+ cmd_putscript_finish(ctx);
+ client_send_command_error(cmd, "Invalid arguments.");
+ client->input_skip_line = TRUE;
+ return TRUE;
+ }
+ if (ret < 0) {
+ /* need more data */
+ return FALSE;
+ }
+
+ /* Validate the script argument */
+ if (!managesieve_arg_get_string_stream(args,&ctx->input)) {
+ client_send_command_error(cmd, "Invalid arguments.");
+ return cmd_putscript_cancel(ctx, FALSE);
+ }
+
+ if (i_stream_get_size(ctx->input, FALSE, &ctx->script_size) > 0) {
+ ctx->script_size_valid = TRUE;
+
+ /* Check quota */
+ if (ctx->scriptname == NULL) {
+ if (!managesieve_quota_check_validsize(
+ cmd, ctx->script_size))
+ return cmd_putscript_cancel(ctx, TRUE);
+ } else {
+ if (!managesieve_quota_check_all(
+ cmd, ctx->scriptname, ctx->script_size))
+ return cmd_putscript_cancel(ctx, TRUE);
+ }
+
+ } else {
+ ctx->max_script_size =
+ managesieve_quota_max_script_size(client);
+ }
+
+ /* save the script */
+ ctx->save_ctx = sieve_storage_save_init(ctx->storage, ctx->scriptname,
+ ctx->input);
+
+ if (ctx->save_ctx == NULL) {
+ /* save initialization failed */
+ cmd_putscript_storage_error(ctx);
+ return cmd_putscript_cancel(ctx, TRUE);
+ }
+
+ /* after literal comes CRLF, if we fail make sure we eat it away */
+ client->input_skip_line = TRUE;
+
+ client->command_pending = TRUE;
+ cmd->func = cmd_putscript_continue_script;
+ return cmd_putscript_continue_script(cmd);
+}
+
+static bool cmd_putscript_continue_script(struct client_command_context *cmd)
+{
+ struct client *client = cmd->client;
+ struct cmd_putscript_context *ctx = cmd->context;
+ size_t size;
+ int ret;
+
+ if (ctx->save_ctx != NULL) {
+ for (;;) {
+ i_assert(!ctx->script_size_valid ||
+ ctx->input->v_offset <= ctx->script_size);
+ if (ctx->max_script_size > 0 &&
+ ctx->input->v_offset > ctx->max_script_size) {
+ (void)managesieve_quota_check_validsize(
+ cmd, ctx->input->v_offset);
+ cmd_putscript_finish(ctx);
+ return TRUE;
+ }
+
+ ret = i_stream_read(ctx->input);
+ if ((ret != -1 || ctx->input->stream_errno != EINVAL ||
+ client->input->eof) &&
+ sieve_storage_save_continue(ctx->save_ctx) < 0) {
+ /* we still have to finish reading the script
+ from client */
+ sieve_storage_save_cancel(&ctx->save_ctx);
+ break;
+ }
+ if (ret == -1 || ret == 0)
+ break;
+ }
+ }
+
+ if (ctx->save_ctx == NULL) {
+ (void)i_stream_read(ctx->input);
+ (void)i_stream_get_data(ctx->input, &size);
+ i_stream_skip(ctx->input, size);
+ }
+
+ if (ctx->input->eof || client->input->closed) {
+ bool failed = FALSE;
+ bool all_written = FALSE;
+
+ if (!ctx->script_size_valid) {
+ if (!client->input->eof &&
+ ctx->input->stream_errno == EINVAL) {
+ client_send_command_error(
+ cmd, t_strdup_printf(
+ "Invalid input: %s",
+ i_stream_get_error(ctx->input)));
+ client->input_skip_line = TRUE;
+ failed = TRUE;
+ }
+ all_written = (ctx->input->eof &&
+ ctx->input->stream_errno == 0);
+
+ } else {
+ all_written = (ctx->input->v_offset == ctx->script_size);
+ }
+
+ /* finished */
+ ctx->input = NULL;
+
+ if (!failed) {
+ if (ctx->save_ctx == NULL) {
+ /* failed above */
+ cmd_putscript_storage_error(ctx);
+ failed = TRUE;
+ } else if (!all_written) {
+ /* client disconnected before it finished sending the
+ whole script. */
+ failed = TRUE;
+ sieve_storage_save_cancel(&ctx->save_ctx);
+ const char *reason = t_strdup_printf(
+ "%s (While appending in PUTSCRIPT/CHECKSCRIPT)",
+ io_stream_get_disconnect_reason(client->input,
+ client->output));
+ client_disconnect(client, reason);
+ } else if (sieve_storage_save_finish(ctx->save_ctx) < 0) {
+ failed = TRUE;
+ cmd_putscript_storage_error(ctx);
+ } else {
+ failed = client->input->closed;
+ }
+ }
+
+ if (failed) {
+ cmd_putscript_finish(ctx);
+ return TRUE;
+ }
+
+ /* finish */
+ client->command_pending = FALSE;
+ managesieve_parser_reset(ctx->save_parser);
+ cmd->func = cmd_putscript_finish_parsing;
+ return cmd_putscript_finish_parsing(cmd);
+ }
+
+ return FALSE;
+}
+
+static bool
+cmd_putscript_start(struct client_command_context *cmd, const char *scriptname)
+{
+ struct cmd_putscript_context *ctx;
+ struct client *client = cmd->client;
+
+ ctx = p_new(cmd->pool, struct cmd_putscript_context, 1);
+ ctx->cmd = cmd;
+ ctx->client = client;
+ ctx->storage = client->storage;
+ ctx->scriptname = scriptname;
+
+ io_remove(&client->io);
+ client->io = io_add(i_stream_get_fd(client->input), IO_READ,
+ client_input_putscript, client);
+ /* putscript is special because we're only waiting on client input, not
+ client output, so disable the standard output handler until we're
+ finished */
+ o_stream_unset_flush_callback(client->output);
+
+ ctx->save_parser = managesieve_parser_create(
+ client->input, client->set->managesieve_max_line_length);
+
+ cmd->func = cmd_putscript_continue_parsing;
+ cmd->context = ctx;
+ return cmd_putscript_continue_parsing(cmd);
+
+}
+
+bool cmd_putscript(struct client_command_context *cmd)
+{
+ const char *scriptname;
+
+ /* <scriptname> */
+ if (!client_read_string_args(cmd, FALSE, 1, &scriptname))
+ return FALSE;
+
+ event_add_str(cmd->event, "script_name", scriptname);
+
+ return cmd_putscript_start(cmd, scriptname);
+}
+
+bool cmd_checkscript(struct client_command_context *cmd)
+{
+ return cmd_putscript_start(cmd, NULL);
+}