/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "imap-common.h" #include "ioloop.h" #include "istream.h" #include "istream-chain.h" #include "ostream.h" #include "str.h" #include "imap-resp-code.h" #include "istream-binary-converter.h" #include "mail-storage-private.h" #include "imap-parser.h" #include "imap-date.h" #include "imap-util.h" #include "imap-commands.h" #include "imap-msgpart-url.h" #include /* Don't allow internaldates to be too far in the future. At least with Maildir they can cause problems with incremental backups since internaldate is stored in file's mtime. But perhaps there are also some other reasons why it might not be wanted. */ #define INTERNALDATE_MAX_FUTURE_SECS (2*3600) struct cmd_append_context { struct client *client; struct client_command_context *cmd; struct mailbox *box; struct mailbox_transaction_context *t; time_t started; struct istream_chain *catchain; uoff_t cat_msg_size; struct istream *input; struct istream *litinput; uoff_t literal_size; struct imap_parser *save_parser; struct mail_save_context *save_ctx; unsigned int count; bool message_input:1; bool binary_input:1; bool catenate:1; bool cmd_args_set:1; bool failed:1; }; static void cmd_append_finish(struct cmd_append_context *ctx); static bool cmd_append_continue_message(struct client_command_context *cmd); static bool cmd_append_parse_new_msg(struct client_command_context *cmd); static const char * get_disconnect_reason(struct cmd_append_context *ctx, uoff_t lit_offset) { string_t *str = t_str_new(128); unsigned int secs = ioloop_time - ctx->started; str_printfa(str, "%s (While APPENDing: %u msgs, %u secs", i_stream_get_disconnect_reason(ctx->input), ctx->count, secs); if (ctx->literal_size > 0) { str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes", lit_offset, ctx->literal_size); } str_append_c(str, ')'); return str_c(str); } static void client_input_append(struct client_command_context *cmd) { struct cmd_append_context *ctx = cmd->context; struct client *client = cmd->client; const char *reason; bool finished; uoff_t lit_offset; i_assert(!client->destroyed); client->last_input = ioloop_time; timeout_reset(client->to_idle); switch (i_stream_read(client->input)) { case -1: /* disconnected */ lit_offset = ctx->litinput == NULL ? 0 : ctx->litinput->v_offset; reason = get_disconnect_reason(ctx, lit_offset); cmd_append_finish(cmd->context); /* Reset command so that client_destroy() doesn't try to call cmd_append_continue_message() anymore. */ client_command_free(&cmd); client_destroy(client, reason); return; case -2: if (ctx->message_input) { /* message data, this is handled internally by mailbox_save_continue() */ break; } cmd_append_finish(cmd->context); /* 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; if (!ctx->failed) client_send_command_error(cmd, "Too long argument."); cmd->param_error = TRUE; client_command_free(&cmd); return; } o_stream_cork(client->output); finished = command_exec(cmd); if (!finished) (void)client_handle_unfinished_cmd(cmd); else client_command_free(&cmd); cmd_sync_delayed(client); o_stream_uncork(client->output); client_continue_pending_input(client); } static void cmd_append_finish(struct cmd_append_context *ctx) { if (ctx->save_parser != NULL) imap_parser_unref(&ctx->save_parser); i_assert(ctx->client->input_lock == ctx->cmd); io_remove(&ctx->client->io); /* we must put back the original flush callback before beginning to sync (the command is still unfinished at that point) */ o_stream_set_flush_callback(ctx->client->output, client_output, ctx->client); i_stream_unref(&ctx->litinput); i_stream_unref(&ctx->input); if (ctx->save_ctx != NULL) mailbox_save_cancel(&ctx->save_ctx); if (ctx->t != NULL) mailbox_transaction_rollback(&ctx->t); if (ctx->box != ctx->cmd->client->mailbox && ctx->box != NULL) mailbox_free(&ctx->box); } static bool cmd_append_send_literal_continue(struct cmd_append_context *ctx) { if (ctx->failed) { /* tagline was already sent, we can abort here */ return FALSE; } o_stream_nsend(ctx->client->output, "+ OK\r\n", 6); o_stream_uncork(ctx->client->output); o_stream_cork(ctx->client->output); return TRUE; } static int cmd_append_catenate_mpurl(struct client_command_context *cmd, const char *caturl, struct imap_msgpart_url *mpurl) { struct cmd_append_context *ctx = cmd->context; struct imap_msgpart_open_result mpresult; uoff_t newsize; const char *client_error; int ret; /* catenate URL */ ret = imap_msgpart_url_read_part(mpurl, &mpresult, &client_error); if (ret < 0) { client_send_box_error(cmd, ctx->box); return -1; } if (ret == 0) { /* invalid url, abort */ client_send_tagline(cmd, t_strdup_printf("NO [BADURL %s] %s.", caturl, client_error)); return -1; } if (mpresult.size == 0) { /* empty input */ return 0; } newsize = ctx->cat_msg_size + mpresult.size; if (newsize < ctx->cat_msg_size) { client_send_tagline(cmd, "NO [TOOBIG] Composed message grows too big."); return -1; } ctx->cat_msg_size = newsize; /* add this input stream to chain */ i_stream_chain_append(ctx->catchain, mpresult.input); /* save by reading the chain stream */ do { ret = i_stream_read(mpresult.input); i_assert(ret != 0); /* we can handle only blocking input here */ } while (mailbox_save_continue(ctx->save_ctx) == 0 && ret != -1); if (mpresult.input->stream_errno != 0) { mailbox_set_critical(ctx->box, "read(%s) failed: %s (for CATENATE URL %s)", i_stream_get_name(mpresult.input), i_stream_get_error(mpresult.input), caturl); client_send_box_error(cmd, ctx->box); ret = -1; } else if (!mpresult.input->eof) { /* save failed */ client_send_box_error(cmd, ctx->box); ret = -1; } else { /* all the input must be consumed, so istream-chain's read() unreferences the stream and we can free its parent mail */ i_assert(!i_stream_have_bytes_left(mpresult.input)); ret = 0; } return ret; } static int cmd_append_catenate_url(struct client_command_context *cmd, const char *caturl) { struct cmd_append_context *ctx = cmd->context; struct imap_msgpart_url *mpurl; const char *client_error; int ret; if (ctx->failed) return -1; ret = imap_msgpart_url_parse(cmd->client->user, cmd->client->mailbox, caturl, &mpurl, &client_error); if (ret < 0) { client_send_box_error(cmd, ctx->box); return -1; } if (ret == 0) { /* invalid url, abort */ client_send_tagline(cmd, t_strdup_printf("NO [BADURL %s] %s.", caturl, client_error)); return -1; } ret = cmd_append_catenate_mpurl(cmd, caturl, mpurl); imap_msgpart_url_free(&mpurl); return ret; } static void cmd_append_catenate_text(struct client_command_context *cmd) { struct cmd_append_context *ctx = cmd->context; if (ctx->literal_size > UOFF_T_MAX - ctx->cat_msg_size && !ctx->failed) { client_send_tagline(cmd, "NO [TOOBIG] Composed message grows too big."); ctx->failed = TRUE; } /* save the mail */ ctx->cat_msg_size += ctx->literal_size; if (ctx->literal_size == 0) { /* zero length literal. RFC doesn't explicitly specify what should be done with this, so we'll simply handle it by skipping the empty text part. */ ctx->litinput = i_stream_create_from_data("", 0); ctx->litinput->eof = TRUE; } else { ctx->litinput = i_stream_create_limit(cmd->client->input, ctx->literal_size); i_stream_chain_append(ctx->catchain, ctx->litinput); } } static int cmd_append_catenate(struct client_command_context *cmd, const struct imap_arg *args, bool *nonsync_r) { struct cmd_append_context *ctx = cmd->context; const char *catpart; *nonsync_r = FALSE; /* Handle URLs until a TEXT literal is encountered */ while (imap_arg_get_atom(args, &catpart)) { const char *caturl; if (strcasecmp(catpart, "URL") == 0 ) { /* URL */ args++; if (!imap_arg_get_astring(args, &caturl)) break; if (cmd_append_catenate_url(cmd, caturl) < 0) { /* delay failure until we can stop parsing input */ ctx->failed = TRUE; } } else if (strcasecmp(catpart, "TEXT") == 0) { /* TEXT */ args++; if (!imap_arg_get_literal_size(args, &ctx->literal_size)) break; if (args->literal8 && !ctx->binary_input && !ctx->failed) { client_send_tagline(cmd, "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] " "Binary input allowed only when the first part is binary."); ctx->failed = TRUE; } *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC; cmd_append_catenate_text(cmd); return 1; } else { break; } args++; } if (IMAP_ARG_IS_EOL(args)) { /* ")" */ return 0; } if (!ctx->failed) client_send_command_error(cmd, "Invalid arguments."); return -1; } static void cmd_append_finish_catenate(struct client_command_context *cmd) { struct cmd_append_context *ctx = cmd->context; i_stream_chain_append_eof(ctx->catchain); i_stream_unref(&ctx->input); ctx->catenate = FALSE; ctx->catchain = NULL; if (ctx->failed) { /* APPEND has already failed */ if (ctx->save_ctx != NULL) mailbox_save_cancel(&ctx->save_ctx); } else { if (mailbox_save_finish(&ctx->save_ctx) < 0) { client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } } } static bool catenate_args_can_stop(struct cmd_append_context *ctx, const struct imap_arg *args) { /* eat away literal_sizes from URLs */ while (args->type != IMAP_ARG_EOL) { if (imap_arg_atom_equals(args, "TEXT")) return TRUE; if (!imap_arg_atom_equals(args, "URL")) { /* error - handle it later */ return TRUE; } args++; if (args->type == IMAP_ARG_LITERAL_SIZE || args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) { if (args->type == IMAP_ARG_LITERAL_SIZE) { if (!cmd_append_send_literal_continue(ctx)) return TRUE; } imap_parser_read_last_literal(ctx->save_parser); return FALSE; } args++; } return TRUE; } static bool cmd_append_continue_catenate(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; const struct imap_arg *args; const char *client_error; enum imap_parser_error parse_error; bool nonsync = FALSE; int ret; if (cmd->cancel) { /* cancel the command immediately (disconnection) */ cmd_append_finish(ctx); return TRUE; } /* we're parsing inside CATENATE (..) list after handling a TEXT part. it's fine that this would need to fully fit into input buffer (although clients attempting to DoS could simply insert an extra {1+} between the URLs) */ do { ret = imap_parser_read_args(ctx->save_parser, 0, IMAP_PARSE_FLAG_LITERAL_SIZE | IMAP_PARSE_FLAG_LITERAL8 | IMAP_PARSE_FLAG_INSIDE_LIST, &args); } while (ret > 0 && !catenate_args_can_stop(ctx, args)); if (ret == -1) { client_error = imap_parser_get_error(ctx->save_parser, &parse_error); switch (parse_error) { case IMAP_PARSE_ERROR_NONE: i_unreached(); case IMAP_PARSE_ERROR_LITERAL_TOO_BIG: client_send_line(client, t_strconcat("* BYE ", (client->set->imap_literal_minus ? "[TOOBIG] " : ""), client_error, NULL)); client_disconnect(client, client_error); break; default: if (!ctx->failed) client_send_command_error(cmd, client_error); } client->input_skip_line = TRUE; cmd_append_finish(ctx); return TRUE; } if (ret < 0) { /* need more data */ return FALSE; } if ((ret = cmd_append_catenate(cmd, args, &nonsync)) < 0) { /* invalid parameters, abort immediately */ cmd_append_finish(ctx); return TRUE; } if (ret == 0) { /* ")" */ cmd_append_finish_catenate(cmd); /* last catenate part */ imap_parser_reset(ctx->save_parser); cmd->func = cmd_append_parse_new_msg; return cmd_append_parse_new_msg(cmd); } /* TEXT */ if (!nonsync) { if (!cmd_append_send_literal_continue(ctx)) { cmd_append_finish(ctx); return TRUE; } } i_assert(ctx->litinput != NULL); ctx->message_input = TRUE; cmd->func = cmd_append_continue_message; return cmd_append_continue_message(cmd); } static int cmd_append_handle_args(struct client_command_context *cmd, const struct imap_arg *args, bool *nonsync_r) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; const struct imap_arg *flags_list; const struct imap_arg *cat_list = NULL; enum mail_flags flags; const char *const *keywords_list; struct mail_keywords *keywords; struct istream *input; const char *internal_date_str; time_t internal_date; int ret, timezone_offset; bool valid; if (!ctx->cmd_args_set) { ctx->cmd_args_set = TRUE; client_args_finished(cmd, args); } /* [] */ if (!imap_arg_get_list(args, &flags_list)) flags_list = NULL; else args++; /* [] */ if (args->type != IMAP_ARG_STRING) internal_date_str = NULL; else { internal_date_str = imap_arg_as_astring(args); args++; } /* | CATENATE (..) */ valid = FALSE; *nonsync_r = FALSE; ctx->catenate = FALSE; if (imap_arg_get_literal_size(args, &ctx->literal_size)) { *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC; ctx->binary_input = args->literal8; valid = TRUE; } else if (!imap_arg_atom_equals(args, "CATENATE")) { /* invalid */ } else if (!imap_arg_get_list(++args, &cat_list)) { /* invalid */ } else { valid = TRUE; ctx->catenate = TRUE; /* We'll do BINARY conversion only if the CATENATE's first part is a literal8. If it doesn't and a literal8 is seen later we'll abort the append with UNKNOWN-CTE. */ ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT") && cat_list[1].literal8; } if (!IMAP_ARG_IS_EOL(&args[1])) valid = FALSE; if (!valid) { client->input_skip_line = TRUE; if (!ctx->failed) client_send_command_error(cmd, "Invalid arguments."); return -1; } if (flags_list == NULL || ctx->failed) { flags = 0; keywords = NULL; } else { if (!client_parse_mail_flags(cmd, flags_list, &flags, &keywords_list)) return -1; if (keywords_list == NULL) keywords = NULL; else if (mailbox_keywords_create(ctx->box, keywords_list, &keywords) < 0) { /* invalid keywords - delay failure */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; keywords = NULL; } } if (internal_date_str == NULL || ctx->failed) { /* no time given, default to now. */ internal_date = (time_t)-1; timezone_offset = 0; } else if (!imap_parse_datetime(internal_date_str, &internal_date, &timezone_offset)) { client_send_command_error(cmd, "Invalid internal date."); if (keywords != NULL) mailbox_keywords_unref(&keywords); return -1; } if (internal_date != (time_t)-1 && internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) { /* the client specified a time in the future, set it to now. */ internal_date = (time_t)-1; timezone_offset = 0; } if (cat_list != NULL) { ctx->cat_msg_size = 0; ctx->input = i_stream_create_chain(&ctx->catchain, IO_BLOCK_SIZE); } else { if (ctx->literal_size == 0) { /* no message data, abort */ if (!ctx->failed) { client_send_tagline(cmd, "NO Can't save a zero byte message."); ctx->failed = TRUE; } if (!*nonsync_r) { if (keywords != NULL) mailbox_keywords_unref(&keywords); return -1; } /* {0+} used. although there isn't any point in using MULTIAPPEND here and adding more messages, it is technically valid so we'll continue parsing.. */ } ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size); ctx->input = ctx->litinput; i_stream_ref(ctx->input); } if (ctx->binary_input) { input = i_stream_create_binary_converter(ctx->input); i_stream_unref(&ctx->input); ctx->input = input; } if (!ctx->failed) { /* save the mail */ ctx->save_ctx = mailbox_save_alloc(ctx->t); mailbox_save_set_flags(ctx->save_ctx, flags, keywords); mailbox_save_set_received_date(ctx->save_ctx, internal_date, timezone_offset); if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) { /* save initialization failed */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } } if (keywords != NULL) mailbox_keywords_unref(&keywords); ctx->count++; if (cat_list == NULL) { /* normal APPEND */ return 1; } else if (cat_list->type == IMAP_ARG_EOL) { /* zero parts */ if (!ctx->failed) client_send_command_error(cmd, "Empty CATENATE list."); client->input_skip_line = TRUE; return -1; } else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) { /* invalid parameters, abort immediately */ return -1; } else if (ret == 0) { /* CATENATE consisted only of URLs */ return 0; } else { /* TEXT part found from CATENATE */ return 1; } } static bool cmd_append_finish_parsing(struct client_command_context *cmd) { struct cmd_append_context *ctx = cmd->context; enum mailbox_sync_flags sync_flags; enum imap_sync_flags imap_flags; struct mail_transaction_commit_changes changes; unsigned int save_count; string_t *msg; int ret; /* eat away the trailing CRLF */ cmd->client->input_skip_line = TRUE; if (ctx->failed) { /* we failed earlier, error message is sent */ cmd_append_finish(ctx); return TRUE; } if (ctx->count == 0) { client_send_command_error(cmd, "Missing message size."); cmd_append_finish(ctx); return TRUE; } ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes); if (ret < 0) { client_send_box_error(cmd, ctx->box); cmd_append_finish(ctx); return TRUE; } msg = t_str_new(256); save_count = seq_range_count(&changes.saved_uids); if (save_count == 0 || changes.no_read_perm) { /* not supported by backend (virtual) */ str_append(msg, "OK Append completed."); } else { i_assert(ctx->count == save_count); str_printfa(msg, "OK [APPENDUID %u ", changes.uid_validity); imap_write_seq_range(msg, &changes.saved_uids); str_append(msg, "] Append completed."); } ctx->client->append_count += save_count; pool_unref(&changes.pool); if (ctx->box == cmd->client->mailbox) { sync_flags = 0; imap_flags = IMAP_SYNC_FLAG_SAFE; } else { sync_flags = MAILBOX_SYNC_FLAG_FAST; imap_flags = 0; } cmd_append_finish(ctx); return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg)); } static bool cmd_append_args_can_stop(struct cmd_append_context *ctx, const struct imap_arg *args, bool *last_literal_r) { const struct imap_arg *cat_list; *last_literal_r = FALSE; if (args->type == IMAP_ARG_EOL) return TRUE; /* [(flags)] ["internal date"] | CATENATE (..) */ if (args->type == IMAP_ARG_LIST) args++; if (args->type == IMAP_ARG_STRING) args++; if (args->type == IMAP_ARG_LITERAL_SIZE || args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) return TRUE; if (imap_arg_atom_equals(args, "CATENATE") && imap_arg_get_list(&args[1], &cat_list)) { if (catenate_args_can_stop(ctx, cat_list)) return TRUE; *last_literal_r = TRUE; } return FALSE; } static bool cmd_append_parse_new_msg(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; const struct imap_arg *args; const char *client_error; enum imap_parser_error parse_error; unsigned int arg_min_count; bool nonsync, last_literal; int ret; /* this function gets called 1) after parsing APPEND and 2) with MULTIAPPEND extension after already saving one or more mails. */ if (cmd->cancel) { /* cancel the command immediately (disconnection) */ cmd_append_finish(ctx); return TRUE; } /* if error occurs, the CRLF is already read. */ client->input_skip_line = FALSE; /* parse the entire line up to the first message literal, or in case the input buffer is full of MULTIAPPEND CATENATE URLs, parse at least until the beginning of the next message */ arg_min_count = 0; last_literal = FALSE; do { if (!last_literal) arg_min_count++; else { /* we only read the literal size. now we read the literal itself. */ } ret = imap_parser_read_args(ctx->save_parser, arg_min_count, IMAP_PARSE_FLAG_LITERAL_SIZE | IMAP_PARSE_FLAG_LITERAL8, &args); } while (ret >= (int)arg_min_count && !cmd_append_args_can_stop(ctx, args, &last_literal)); if (ret == -1) { if (!ctx->failed) { client_error = imap_parser_get_error(ctx->save_parser, &parse_error); switch (parse_error) { case IMAP_PARSE_ERROR_NONE: i_unreached(); case IMAP_PARSE_ERROR_LITERAL_TOO_BIG: client_send_line(client, t_strconcat("* BYE ", (client->set->imap_literal_minus ? "[TOOBIG] " : ""), client_error, NULL)); client_disconnect(client, client_error); break; default: client_send_command_error(cmd, client_error); } } cmd_append_finish(ctx); return TRUE; } if (ret < 0) { /* need more data */ return FALSE; } if (IMAP_ARG_IS_EOL(args)) { /* last message */ return cmd_append_finish_parsing(cmd); } ret = cmd_append_handle_args(cmd, args, &nonsync); if (ret < 0) { /* invalid parameters, abort immediately */ cmd_append_finish(ctx); return TRUE; } if (ret == 0) { /* CATENATE contained only URLs. Finish it and see if there are more messages. */ cmd_append_finish_catenate(cmd); imap_parser_reset(ctx->save_parser); return cmd_append_parse_new_msg(cmd); } if (!nonsync) { if (!cmd_append_send_literal_continue(ctx)) { cmd_append_finish(ctx); return TRUE; } } i_assert(ctx->litinput != NULL); ctx->message_input = TRUE; cmd->func = cmd_append_continue_message; return cmd_append_continue_message(cmd); } static bool cmd_append_continue_message(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_append_context *ctx = cmd->context; int ret = 0; if (cmd->cancel) { /* cancel the command immediately (disconnection) */ cmd_append_finish(ctx); return TRUE; } if (ctx->save_ctx != NULL) { while (ctx->litinput->v_offset != ctx->literal_size) { ret = i_stream_read(ctx->litinput); if (mailbox_save_continue(ctx->save_ctx) < 0) { /* we still have to finish reading the message from client */ mailbox_save_cancel(&ctx->save_ctx); break; } if (ret == -1 || ret == 0) break; } } if (ctx->save_ctx == NULL) { /* saving has already failed, we're just eating away the literal */ (void)i_stream_read(ctx->litinput); i_stream_skip(ctx->litinput, i_stream_get_data_size(ctx->litinput)); } if (ctx->litinput->eof || client->input->closed) { uoff_t lit_offset = ctx->litinput->v_offset; /* finished - do one more read, to make sure istream-chain unreferences its stream, which is needed for litinput's unreferencing to seek the client->input to correct position. the seek is needed to avoid trying to seek backwards in the ctx->input's parent stream. */ i_stream_seek(ctx->input, ctx->input->v_offset); (void)i_stream_read(ctx->input); i_stream_unref(&ctx->litinput); if (ctx->failed) { if (ctx->save_ctx != NULL) mailbox_save_cancel(&ctx->save_ctx); } else if (ctx->save_ctx == NULL) { /* failed above */ client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } else if (lit_offset != ctx->literal_size) { /* client disconnected before it finished sending the whole message. */ ctx->failed = TRUE; mailbox_save_cancel(&ctx->save_ctx); client_disconnect(client, get_disconnect_reason(ctx, lit_offset)); } else if (ctx->catenate) { /* CATENATE isn't finished yet */ } else if (mailbox_save_finish(&ctx->save_ctx) < 0) { client_send_box_error(cmd, ctx->box); ctx->failed = TRUE; } if (client->input->closed) { cmd_append_finish(ctx); return TRUE; } /* prepare for the next message (or its part with catenate) */ ctx->message_input = FALSE; imap_parser_reset(ctx->save_parser); if (ctx->catenate) { cmd->func = cmd_append_continue_catenate; return cmd_append_continue_catenate(cmd); } i_stream_unref(&ctx->input); cmd->func = cmd_append_parse_new_msg; return cmd_append_parse_new_msg(cmd); } return FALSE; } bool cmd_append(struct client_command_context *cmd) { struct client *client = cmd->client; struct cmd_append_context *ctx; const char *mailbox; if (client->syncing) { /* if transaction is created while its view is synced, appends aren't allowed for it. */ cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY; return FALSE; } /* */ if (!client_read_string_args(cmd, 1, &mailbox)) return FALSE; /* we keep the input locked all the time */ client->input_lock = cmd; ctx = p_new(cmd->pool, struct cmd_append_context, 1); ctx->cmd = cmd; ctx->client = client; ctx->started = ioloop_time; if (client_open_save_dest_box(cmd, mailbox, &ctx->box) < 0) ctx->failed = TRUE; else { event_add_str(cmd->global_event, "mailbox", mailbox_get_vname(ctx->box)); ctx->t = mailbox_transaction_begin(ctx->box, MAILBOX_TRANSACTION_FLAG_EXTERNAL | MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS, imap_client_command_get_reason(cmd)); } io_remove(&client->io); client->io = io_add_istream(client->input, client_input_append, cmd); /* append 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 = imap_parser_create(client->input, client->output, client->set->imap_max_line_length); if (client->set->imap_literal_minus) imap_parser_enable_literal_minus(ctx->save_parser); cmd->func = cmd_append_parse_new_msg; cmd->context = ctx; return cmd_append_parse_new_msg(cmd); }