diff options
Diffstat (limited to 'src/imap/cmd-urlfetch.c')
-rw-r--r-- | src/imap/cmd-urlfetch.c | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/src/imap/cmd-urlfetch.c b/src/imap/cmd-urlfetch.c new file mode 100644 index 0000000..6ea5ad5 --- /dev/null +++ b/src/imap/cmd-urlfetch.c @@ -0,0 +1,410 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "strfuncs.h" +#include "str.h" +#include "array.h" +#include "net.h" +#include "istream.h" +#include "istream-sized.h" +#include "ostream.h" +#include "imap-url.h" +#include "imap-quote.h" +#include "imap-common.h" +#include "imap-commands.h" +#include "imap-urlauth.h" +#include "imap-urlauth-fetch.h" + +struct cmd_urlfetch_context { + struct imap_urlauth_fetch *ufetch; + struct istream *input; + + bool failed:1; + bool finished:1; + bool extended:1; + bool in_io_handler:1; +}; + +struct cmd_urlfetch_url { + const char *url; + + enum imap_urlauth_fetch_flags flags; +}; + +static void cmd_urlfetch_finish(struct client_command_context *cmd) +{ + struct cmd_urlfetch_context *ctx = + (struct cmd_urlfetch_context *)cmd->context; + + if (ctx->finished) + return; + ctx->finished = TRUE; + + i_stream_unref(&ctx->input); + if (ctx->ufetch != NULL) + imap_urlauth_fetch_deinit(&ctx->ufetch); + + if (ctx->failed) { + if (cmd->client->output_cmd_lock == cmd) { + /* failed in the middle of a literal. + we need to disconnect. */ + cmd->client->output_cmd_lock = NULL; + client_disconnect(cmd->client, "URLFETCH failed"); + } else { + client_send_internal_error(cmd); + } + return; + } + + client_send_tagline(cmd, "OK URLFETCH completed."); +} + +static bool cmd_urlfetch_cancel(struct client_command_context *cmd) +{ + struct cmd_urlfetch_context *ctx = + (struct cmd_urlfetch_context *)cmd->context; + + if (!cmd->cancel) + return FALSE; + + if (ctx->ufetch != NULL) { + e_debug(cmd->client->event, + "URLFETCH: Canceling command; " + "aborting URLAUTH fetch requests prematurely"); + imap_urlauth_fetch_deinit(&ctx->ufetch); + } + return TRUE; +} + +static int cmd_urlfetch_transfer_literal(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct cmd_urlfetch_context *ctx = + (struct cmd_urlfetch_context *)cmd->context; + enum ostream_send_istream_result res; + int ret; + + /* are we in the middle of an urlfetch literal? */ + if (ctx->input == NULL) + return 1; + + /* flush output to client if buffer is filled above optimum */ + if (o_stream_get_buffer_used_size(client->output) >= + CLIENT_OUTPUT_OPTIMAL_SIZE) { + if ((ret = o_stream_flush(client->output)) <= 0) + return ret; + } + + /* transfer literal to client */ + o_stream_set_max_buffer_size(client->output, 0); + res = o_stream_send_istream(client->output, ctx->input); + o_stream_set_max_buffer_size(client->output, SIZE_MAX); + + switch (res) { + case OSTREAM_SEND_ISTREAM_RESULT_FINISHED: + i_stream_unref(&ctx->input); + return 1; + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_INPUT: + i_unreached(); + case OSTREAM_SEND_ISTREAM_RESULT_WAIT_OUTPUT: + return 0; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_INPUT: + e_error(client->event, "read(%s) failed: %s (URLFETCH)", + i_stream_get_name(ctx->input), + i_stream_get_error(ctx->input)); + client_disconnect(client, "URLFETCH failed"); + return -1; + case OSTREAM_SEND_ISTREAM_RESULT_ERROR_OUTPUT: + /* client disconnected */ + return -1; + } + i_unreached(); +} + +static bool cmd_urlfetch_continue(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct cmd_urlfetch_context *ctx = + (struct cmd_urlfetch_context *)cmd->context; + bool urls_pending; + int ret = 1; + + if (cmd->cancel) + return cmd_urlfetch_cancel(cmd); + + i_assert(client->output_cmd_lock == NULL || + client->output_cmd_lock == cmd); + + /* finish a pending literal transfer */ + ret = cmd_urlfetch_transfer_literal(cmd); + if (ret < 0) { + ctx->failed = TRUE; + cmd_urlfetch_finish(cmd); + return TRUE; + } + if (ret == 0) { + /* not finished; apparently output blocked again */ + return FALSE; + } + + if (ctx->extended) + client_send_line(client, ")"); + else + client_send_line(client, ""); + client->output_cmd_lock = NULL; + + ctx->in_io_handler = TRUE; + urls_pending = imap_urlauth_fetch_continue(ctx->ufetch); + ctx->in_io_handler = FALSE; + + if (urls_pending) { + /* waiting for imap urlauth service */ + cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL; + cmd->func = cmd_urlfetch_cancel; + + /* retrieve next url */ + return FALSE; + } + + /* finished */ + cmd_urlfetch_finish(cmd); + return TRUE; +} + +static int cmd_urlfetch_url_success(struct client_command_context *cmd, + struct imap_urlauth_fetch_reply *reply) +{ + struct cmd_urlfetch_context *ctx = cmd->context; + string_t *response = t_str_new(256); + int ret; + + str_append(response, "* URLFETCH "); + imap_append_astring(response, reply->url); + + if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_EXTENDED) == 0) { + /* simple */ + ctx->extended = FALSE; + + str_printfa(response, " {%"PRIuUOFF_T"}", reply->size); + client_send_line(cmd->client, str_c(response)); + i_assert(reply->size == 0 || reply->input != NULL); + } else { + bool metadata = FALSE; + + /* extended */ + ctx->extended = TRUE; + + str_append(response, " ("); + if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE) != 0 && + reply->bodypartstruct != NULL) { + str_append(response, "BODYPARTSTRUCTURE ("); + str_append(response, reply->bodypartstruct); + str_append_c(response, ')'); + metadata = TRUE; + } + if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 || + (reply->flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) { + if (metadata) + str_append_c(response, ' '); + if ((reply->flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0) { + str_append(response, "BODY "); + } else { + str_append(response, "BINARY "); + if (reply->binary_has_nuls) + str_append_c(response, '~'); + } + str_printfa(response, "{%"PRIuUOFF_T"}", reply->size); + i_assert(reply->size == 0 || reply->input != NULL); + } else { + str_append_c(response, ')'); + ctx->extended = FALSE; + } + + client_send_line(cmd->client, str_c(response)); + } + + if (reply->input != NULL) { + ctx->input = i_stream_create_sized(reply->input, reply->size); + + ret = cmd_urlfetch_transfer_literal(cmd); + if (ret < 0) { + ctx->failed = TRUE; + return -1; + } + if (ret == 0) { + /* not finished; apparently output blocked */ + cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT; + cmd->func = cmd_urlfetch_continue; + cmd->client->output_cmd_lock = cmd; + return 0; + } + + if (ctx->extended) + client_send_line(cmd->client, ")"); + else + client_send_line(cmd->client, ""); + } + return 1; +} + +static int +cmd_urlfetch_url_callback(struct imap_urlauth_fetch_reply *reply, + bool last, void *context) +{ + struct client_command_context *cmd = context; + struct client *client = cmd->client; + struct cmd_urlfetch_context *ctx = cmd->context; + bool in_io_handler = ctx->in_io_handler; + int ret; + + if (!in_io_handler) + o_stream_cork(client->output); + if (reply == NULL) { + /* fatal failure */ + ctx->failed = TRUE; + ret = -1; + } else if (reply->succeeded) { + /* URL fetch succeeded */ + ret = cmd_urlfetch_url_success(cmd, reply); + } else { + /* URL fetch failed */ + string_t *response = t_str_new(128); + + str_append(response, "* URLFETCH "); + imap_append_astring(response, reply->url); + str_append(response, " NIL"); + client_send_line(client, str_c(response)); + if (reply->error != NULL) { + client_send_line(client, t_strdup_printf( + "* NO %s.", reply->error)); + } + ret = 1; + } + + if ((last && cmd->state == CLIENT_COMMAND_STATE_WAIT_EXTERNAL) || + ret < 0) { + cmd_urlfetch_finish(cmd); + client_command_free(&cmd); + } + if (!in_io_handler) + o_stream_uncork(client->output); + return ret; +} + +static int +cmd_urlfetch_parse_arg(struct client_command_context *cmd, + const struct imap_arg *arg, + struct cmd_urlfetch_url *ufurl_r) +{ + enum imap_urlauth_fetch_flags url_flags = 0; + const struct imap_arg *params; + const char *url_text; + + if (imap_arg_get_list(arg, ¶ms)) + url_flags |= IMAP_URLAUTH_FETCH_FLAG_EXTENDED; + else + params = arg; + + /* read url */ + if (!imap_arg_get_astring(params++, &url_text)) { + client_send_command_error(cmd, "Invalid arguments."); + return -1; + } + ufurl_r->url = t_strdup(url_text); + if (url_flags == 0) + return 0; + + while (!IMAP_ARG_IS_EOL(params)) { + const char *fetch_param; + + if (!imap_arg_get_atom(params++, &fetch_param)) { + client_send_command_error(cmd, + "Invalid URL fetch parameter."); + return -1; + } + + if (strcasecmp(fetch_param, "BODY") == 0) + url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY; + else if (strcasecmp(fetch_param, "BINARY") == 0) + url_flags |= IMAP_URLAUTH_FETCH_FLAG_BINARY; + else if (strcasecmp(fetch_param, "BODYPARTSTRUCTURE") == 0) + url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODYPARTSTRUCTURE; + else { + client_send_command_error(cmd, + t_strdup_printf("Unknown URL fetch parameter: %s", + fetch_param)); + return -1; + } + } + + if ((url_flags & IMAP_URLAUTH_FETCH_FLAG_BODY) != 0 && + (url_flags & IMAP_URLAUTH_FETCH_FLAG_BINARY) != 0) { + client_send_command_error(cmd, + "URL cannot have both BODY and BINARY fetch parameters."); + return -1; + } + + if (url_flags == IMAP_URLAUTH_FETCH_FLAG_EXTENDED) + url_flags |= IMAP_URLAUTH_FETCH_FLAG_BODY; + ufurl_r->flags = url_flags; + return 0; +} + +bool cmd_urlfetch(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + struct cmd_urlfetch_context *ctx; + ARRAY(struct cmd_urlfetch_url) urls; + const struct cmd_urlfetch_url *url; + const struct imap_arg *args; + struct cmd_urlfetch_url *ufurl; + + if (client->urlauth_ctx == NULL) { + client_send_command_error(cmd, "URLAUTH disabled."); + return TRUE; + } + + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + if (IMAP_ARG_IS_EOL(args)) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + + t_array_init(&urls, 32); + + /* parse url arguments and group them per userid */ + for (; !IMAP_ARG_IS_EOL(args); args++) { + ufurl = array_append_space(&urls); + if (cmd_urlfetch_parse_arg(cmd, args, ufurl) < 0) + return TRUE; + } + cmd->context = ctx = p_new(cmd->pool, struct cmd_urlfetch_context, 1); + cmd->func = cmd_urlfetch_cancel; + cmd->state = CLIENT_COMMAND_STATE_WAIT_INPUT; + + ctx->ufetch = imap_urlauth_fetch_init(client->urlauth_ctx, + cmd_urlfetch_url_callback, cmd); + + ctx->in_io_handler = TRUE; + array_foreach(&urls, url) { + if (imap_urlauth_fetch_url(ctx->ufetch, url->url, url->flags) < 0) { + /* fatal error */ + ctx->failed = TRUE; + break; + } + } + ctx->in_io_handler = FALSE; + + if ((ctx->failed || !imap_urlauth_fetch_is_pending(ctx->ufetch)) + && cmd->client->output_cmd_lock != cmd) { + /* finished */ + cmd_urlfetch_finish(cmd); + return TRUE; + } + + if (cmd->client->output_cmd_lock != cmd) + cmd->state = CLIENT_COMMAND_STATE_WAIT_EXTERNAL; + return FALSE; +} |