#include "imap-common.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "str.h"
#include "imap-url.h"
#include "imap-fetch.h"
#include "imap-search-args.h"
#include "fd-set-nonblock.h"
#include "imap-parser.h"
#include "imap-date.h"
#include "imap-util.h"
#include "imap-commands.h"
#include <sys/time.h>
#include <stdlib.h>
#define INTERNALDATE_MAX_FUTURE_SECS (2*3600)
struct cmd_append_context {
struct client *client;
struct client_command_context *cmd;
struct mail_storage *storage;
struct mailbox *box;
struct mailbox_transaction_context *t;
struct istream *input;
uoff_t msg_size;
struct imap_parser *save_parser;
struct mail_save_context *save_ctx;
unsigned int count;
struct {
struct ostream *output;
struct istream *literal_input;
uoff_t literal_size;
string_t *literal_url;
unsigned int parts;
} cat;
const struct imap_arg *args;
unsigned int message_input:1;
unsigned int 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_continue_parsing(struct client_command_context *cmd);
static bool args_indicate_catenate(const struct imap_arg *args);
static bool catenate_begin_parsing(struct client_command_context *cmd,
const struct imap_arg *args);
static bool catenate_begin_cancel(struct client_command_context *cmd,
const struct imap_arg *args);
static void client_input_append(struct client_command_context *cmd)
{
struct cmd_append_context *ctx = cmd->context;
struct client *client = cmd->client;
bool finished;
i_assert(!client->destroyed);
client->last_input = ioloop_time;
timeout_reset(client->to_idle);
switch (i_stream_read(client->input)) {
case -1:
cmd_append_finish(cmd->context);
client_command_free(&cmd);
client_destroy(client, "Disconnected in APPEND");
return;
case -2:
if (ctx->message_input) {
break;
}
cmd_append_finish(cmd->context);
client->input_skip_line = TRUE;
client_send_command_error(cmd, "Too long argument.");
cmd->param_error = TRUE;
client_command_free(&cmd);
return;
}
o_stream_cork(client->output);
finished = cmd->func(cmd);
if (!finished && cmd->state != CLIENT_COMMAND_STATE_DONE)
(void)client_handle_unfinished_cmd(cmd);
else
client_command_free(&cmd);
(void)cmd_sync_delayed(client);
o_stream_uncork(client->output);
if (client->disconnected)
client_destroy(client, NULL);
else
client_continue_pending_input(client);
}
static int validate_args(const struct imap_arg *args,
const struct imap_arg **flags_r,
const char **internal_date_r, uoff_t *msg_size_r,
bool *nonsync_r)
{
if (!imap_arg_get_list(args, flags_r))
*flags_r = NULL;
else
args++;
if (args->type != IMAP_ARG_STRING)
*internal_date_r = NULL;
else {
*internal_date_r = imap_arg_as_astring(args);
args++;
}
if (!imap_arg_get_literal_size(args, msg_size_r)) {
*nonsync_r = FALSE;
return FALSE;
}
*nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
return TRUE;
}
static void cmd_append_finish(struct cmd_append_context *ctx)
{
imap_parser_destroy(&ctx->save_parser);
i_assert(ctx->client->input_lock == ctx->cmd);
io_remove(&ctx->client->io);
o_stream_set_flush_callback(ctx->client->output,
client_output, ctx->client);
if (ctx->input != NULL)
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_continue_cancel(struct client_command_context *cmd)
{
struct cmd_append_context *ctx = cmd->context;
size_t size;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
(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) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->input->v_offset == ctx->msg_size) {
i_stream_unref(&ctx->input);
ctx->input = NULL;
ctx->message_input = FALSE;
imap_parser_reset(ctx->save_parser);
cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(cmd);
}
return FALSE;
}
static bool cmd_append_cancel(struct cmd_append_context *ctx, bool nonsync)
{
ctx->failed = TRUE;
if (!nonsync) {
cmd_append_finish(ctx);
return TRUE;
}
ctx->input = i_stream_create_limit(ctx->client->input, ctx->msg_size);
ctx->message_input = TRUE;
ctx->cmd->func = cmd_append_continue_cancel;
ctx->cmd->context = ctx;
return cmd_append_continue_cancel(ctx->cmd);
}
static bool cmd_append_continue_parsing(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *args;
const struct imap_arg *flags_list;
enum mail_flags flags;
const char *const *keywords_list;
struct mail_keywords *keywords;
const char *internal_date_str;
time_t internal_date;
int ret, timezone_offset;
unsigned int save_count;
bool nonsync;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
client->input_skip_line = FALSE;
if (ctx->args != NULL) {
args = ctx->args;
ctx->args = NULL;
} else {
ret = imap_parser_read_args(ctx->save_parser, 0,
IMAP_PARSE_FLAG_LITERAL_SIZE, &args);
if (ret == -1) {
if (!ctx->failed)
client_send_command_error(cmd, NULL);
cmd_append_finish(ctx);
return TRUE;
}
if (ret < 0) {
return FALSE;
}
}
if (IMAP_ARG_IS_EOL(args)) {
enum mailbox_sync_flags sync_flags;
enum imap_sync_flags imap_flags;
struct mail_transaction_commit_changes changes;
string_t *msg;
client->input_skip_line = TRUE;
if (ctx->failed) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->count == 0) {
client_send_tagline(cmd, "BAD Missing message size.");
cmd_append_finish(ctx);
return TRUE;
}
ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
cmd_append_finish(ctx);
return TRUE;
}
msg = t_str_new(256);
save_count = seq_range_count(&changes.saved_uids);
if (save_count == 0) {
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.");
}
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));
}
if (args_indicate_catenate(args))
return ctx->failed ? catenate_begin_cancel(cmd, args) :
catenate_begin_parsing(cmd, args);
if (!validate_args(args, &flags_list, &internal_date_str,
&ctx->msg_size, &nonsync)) {
client_send_command_error(cmd, "Invalid arguments.");
return cmd_append_cancel(ctx, nonsync);
}
if (ctx->failed) {
return cmd_append_cancel(ctx, nonsync);
}
if (flags_list != NULL) {
if (!client_parse_mail_flags(cmd, flags_list,
&flags, &keywords_list))
return cmd_append_cancel(ctx, nonsync);
if (keywords_list == NULL)
keywords = NULL;
else if (mailbox_keywords_create(ctx->box, keywords_list,
&keywords) < 0) {
client_send_storage_error(cmd, ctx->storage);
return cmd_append_cancel(ctx, nonsync);
}
} else {
flags = 0;
keywords = NULL;
}
if (internal_date_str == NULL) {
internal_date = (time_t)-1;
timezone_offset = 0;
} else if (!imap_parse_datetime(internal_date_str,
&internal_date, &timezone_offset)) {
client_send_tagline(cmd, "BAD Invalid internal date.");
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return cmd_append_cancel(ctx, nonsync);
}
if (internal_date != (time_t)-1 &&
internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) {
internal_date = (time_t)-1;
timezone_offset = 0;
}
if (ctx->msg_size == 0) {
client_send_tagline(cmd, "NO Can't save a zero byte message.");
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return cmd_append_cancel(ctx, nonsync);
}
ctx->input = i_stream_create_limit(client->input, ctx->msg_size);
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);
ret = mailbox_save_begin(&ctx->save_ctx, ctx->input);
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
return cmd_append_cancel(ctx, nonsync);
}
client->input_skip_line = TRUE;
if (!nonsync) {
o_stream_send(client->output, "+ OK\r\n", 6);
o_stream_flush(client->output);
o_stream_uncork(client->output);
o_stream_cork(client->output);
}
ctx->count++;
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;
size_t size;
int ret;
if (cmd->cancel) {
cmd_append_finish(ctx);
return TRUE;
}
if (ctx->save_ctx != NULL) {
while (ctx->input->v_offset != ctx->msg_size) {
ret = i_stream_read(ctx->input);
if (mailbox_save_continue(ctx->save_ctx) < 0) {
mailbox_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 all_written = ctx->input->v_offset == ctx->msg_size;
i_stream_unref(&ctx->input);
ctx->input = NULL;
if (ctx->save_ctx == NULL) {
client_send_storage_error(cmd, ctx->storage);
ctx->failed = TRUE;
} else if (!all_written) {
ctx->failed = TRUE;
mailbox_save_cancel(&ctx->save_ctx);
client_disconnect(client, "EOF while appending");
} else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
ctx->failed = TRUE;
client_send_storage_error(cmd, ctx->storage);
}
ctx->save_ctx = NULL;
if (client->input->closed) {
cmd_append_finish(ctx);
return TRUE;
}
ctx->message_input = FALSE;
imap_parser_reset(ctx->save_parser);
cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(cmd);
}
return FALSE;
}
static struct mailbox *
get_mailbox(struct client_command_context *cmd, const char *name)
{
struct mail_namespace *ns;
struct mailbox *box;
enum mailbox_name_status status;
const char *storage_name;
ns = client_find_namespace(cmd, name, &storage_name, &status);
if (ns == NULL)
return NULL;
switch (status) {
case MAILBOX_NAME_EXISTS_MAILBOX:
break;
case MAILBOX_NAME_EXISTS_DIR:
status = MAILBOX_NAME_VALID;
case MAILBOX_NAME_VALID:
case MAILBOX_NAME_INVALID:
case MAILBOX_NAME_NOINFERIORS:
client_fail_mailbox_name_status(cmd, name, "TRYCREATE", status);
return NULL;
}
if (cmd->client->mailbox != NULL &&
mailbox_equals(cmd->client->mailbox, ns, storage_name))
return cmd->client->mailbox;
box = mailbox_alloc(ns->list, storage_name, MAILBOX_FLAG_SAVEONLY |
MAILBOX_FLAG_KEEP_RECENT);
if (mailbox_open(box) < 0) {
client_send_storage_error(cmd, mailbox_get_storage(box));
mailbox_free(&box);
return NULL;
}
if (cmd->client->enabled_features != 0)
mailbox_enable(box, cmd->client->enabled_features);
return box;
}
bool cmd_append(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx;
const char *mailbox;
if (client->syncing) {
cmd->state = CLIENT_COMMAND_STATE_WAIT_UNAMBIGUITY;
return FALSE;
}
if (!client_read_string_args(cmd, 1, &mailbox))
return FALSE;
client->input_lock = cmd;
ctx = p_new(cmd->pool, struct cmd_append_context, 1);
ctx->cmd = cmd;
ctx->client = client;
ctx->box = get_mailbox(cmd, mailbox);
if (ctx->box == NULL)
ctx->failed = TRUE;
else {
ctx->storage = mailbox_get_storage(ctx->box);
ctx->t = mailbox_transaction_begin(ctx->box,
MAILBOX_TRANSACTION_FLAG_EXTERNAL |
MAILBOX_TRANSACTION_FLAG_ASSIGN_UIDS);
}
io_remove(&client->io);
client->io = io_add(i_stream_get_fd(client->input), IO_READ,
client_input_append, cmd);
o_stream_unset_flush_callback(client->output);
ctx->save_parser = imap_parser_create(client->input, client->output,
client->set->imap_max_line_length);
cmd->func = cmd_append_continue_parsing;
cmd->context = ctx;
return cmd_append_continue_parsing(cmd);
}
#define MAX_URL_LITERAL_SIZE 8000
#define MAX_CATENATE_MSG_SIZE ((uint32_t) -1)
#define MAX_CATENATE_PARTS 50
#ifndef PIPE_MAX
# define PIPE_MAX 5120
#endif
static bool catenate_process_args(struct client_command_context *cmd,
const struct imap_arg *args, bool nonsync);
static bool catenate_cancel_args(struct client_command_context *cmd,
const struct imap_arg *args, bool nonsync);
static bool catenate_cancel(struct cmd_append_context *ctx, bool nonsync);
static bool catenate_finish(struct cmd_append_context *ctx, bool cancel);
static bool catenate_finish_literal(struct client_command_context *cmd,
bool cancel);
static bool args_indicate_catenate(const struct imap_arg *args)
{
while (!IMAP_ARG_IS_EOL(args)) {
if (imap_arg_atom_equals(args, "CATENATE"))
return TRUE;
args++;
}
return FALSE;
}
static void catenate_solicit(struct cmd_append_context *ctx, bool nonsync)
{
if (!nonsync) {
o_stream_send(ctx->client->output, "+ OK\r\n", 6);
o_stream_flush(ctx->client->output);
o_stream_uncork(ctx->client->output);
o_stream_cork(ctx->client->output);
}
}
static bool catenate_url_validate(const struct imap_url_parts *parts,
const char **error)
{
if (parts->user != NULL) {
*error = "user ID present; need relative URL";
return FALSE;
}
if (parts->auth_type != NULL) {
*error = "auth type present; need relative URL";
return FALSE;
}
if (parts->hostport != NULL) {
*error = "server present; need relative URL";
return FALSE;
}
if (parts->mailbox != NULL &&
!imap_url_mailbox_validate(parts->mailbox)) {
*error = "invalid mailbox";
return FALSE;
}
if (parts->uidvalidity != NULL &&
!imap_url_nz_number_validate(parts->uidvalidity)) {
*error = "invalid uidvalidity";
return FALSE;
}
if (parts->uid == NULL ||
!imap_url_nz_number_validate(parts->uid)) {
*error = "missing or invalid uid";
return FALSE;
}
if (parts->section != NULL &&
!imap_url_section_validate(parts->section)) {
*error = "invalid section";
return FALSE;
}
if (parts->expiration != NULL) {
*error = "expiration present";
return FALSE;
}
if (parts->access != NULL) {
*error = "missing or invalid access ID";
return FALSE;
}
if (parts->mechanism != NULL) {
*error = "mechanism present";
return FALSE;
}
if (parts->urlauth != NULL) {
*error = "access token present";
return FALSE;
}
return TRUE;
}
struct catenate_fetch_context {
struct cmd_append_context *ctx;
struct ostream *client_output;
const char *url;
const char *mailbox;
struct mailbox *box;
struct istream *input;
struct imap_parser *parser;
struct mail_search_args *search_args;
struct imap_fetch_context *fetch_ctx;
};
static bool catenate_fetch_cleanup(struct catenate_fetch_context *cfctx,
bool toobig, const char *error)
{
if (cfctx->fetch_ctx != NULL) {
if (!cfctx->fetch_ctx->urlfetched) {
if (error == NULL && !toobig)
error = "message not found";
}
if (imap_fetch_deinit(cfctx->fetch_ctx) < 0)
cfctx->fetch_ctx->failed = TRUE;
if (cfctx->fetch_ctx->failed) {
if (error == NULL && !toobig)
error = "message fetch failed";
}
cfctx->fetch_ctx->urlfetch = FALSE;
}
if (cfctx->search_args != NULL)
mail_search_args_unref(&cfctx->search_args);
if (cfctx->parser != NULL)
imap_parser_destroy(&cfctx->parser);
if (cfctx->input != NULL)
i_stream_unref(&cfctx->input);
if (cfctx->box != NULL)
mailbox_free(&cfctx->box);
if (cfctx->client_output != NULL)
cfctx->ctx->client->output = cfctx->client_output;
cfctx->ctx->client->output_squelch = FALSE;
if (toobig)
client_send_tagline(cfctx->ctx->cmd,
"NO [TOOBIG] Resulting message too large");
else if (error != NULL)
client_send_tagline(cfctx->ctx->cmd,
t_strconcat("NO [BADURL ",
imap_url_sanitize(cfctx->url),
"] ", error, NULL));
return error == NULL && !toobig;
}
static bool catenate_url(struct cmd_append_context *ctx, const char *url)
{
struct imap_url_parts enc_parts, dec_parts;
const char *error = NULL;
struct catenate_fetch_context cfctx;
struct mail_namespace *ns;
struct mailbox_status status;
string_t *fetch_text;
const struct imap_arg *fetch_args = NULL, *next_arg = NULL;
int fetch_ret, flush_ret, ret;
bool header, toobig;
uoff_t size;
memset(&enc_parts, 0, sizeof enc_parts);
memset(&dec_parts, 0, sizeof dec_parts);
imap_url_parse(url, &enc_parts);
if (!imap_url_decode(&enc_parts, &dec_parts, &error) ||
!catenate_url_validate(&dec_parts, &error)) {
client_send_tagline(ctx->cmd,
t_strconcat("NO [BADURL ",
imap_url_sanitize(url),
"] ", error, NULL));
return FALSE;
}
memset(&cfctx, 0, sizeof cfctx);
cfctx.ctx = ctx;
cfctx.url = url;
ctx->cmd->client->output_squelch = TRUE;
if (dec_parts.mailbox) {
ns = client_find_namespace(ctx->cmd, dec_parts.mailbox,
&cfctx.mailbox, NULL);
if (ns == NULL)
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't find storage");
} else if (ctx->cmd->client->mailbox != NULL) {
cfctx.mailbox = mailbox_get_name(ctx->cmd->client->mailbox);
ns = mailbox_get_namespace(ctx->cmd->client->mailbox);
} else
return catenate_fetch_cleanup(&cfctx, FALSE,
"no mailbox specified or selected");
cfctx.box = mailbox_alloc(ns->list, cfctx.mailbox,
MAILBOX_FLAG_READONLY | MAILBOX_FLAG_KEEP_RECENT);
if (mailbox_open(cfctx.box) < 0) {
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't open mailbox");
}
if (mailbox_sync(cfctx.box, MAILBOX_SYNC_FLAG_FULL_READ |
MAILBOX_SYNC_FLAG_FAST) < 0) {
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't sync mailbox");
}
mailbox_get_status(cfctx.box, STATUS_UIDVALIDITY, &status);
if (dec_parts.uidvalidity != NULL &&
strtoul(dec_parts.uidvalidity, NULL, 10) != status.uidvalidity)
return catenate_fetch_cleanup(&cfctx, FALSE,
"uidvalidity mismatch");
fetch_text = t_str_new(11 + (dec_parts.section ?
strlen(dec_parts.section) : 0));
str_append(fetch_text, "BODY.PEEK[");
if (dec_parts.section)
str_append(fetch_text, dec_parts.section);
str_append(fetch_text, "]");
cfctx.input = i_stream_create_from_data(str_data(fetch_text),
str_len(fetch_text));
(void) i_stream_read(cfctx.input);
cfctx.parser = imap_parser_create(cfctx.input, NULL, (size_t) -1);
if (imap_parser_finish_line(cfctx.parser, 0, 0, &fetch_args) < 0)
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't parse fetch string");
ctx->cmd->uid = TRUE;
if (imap_search_get_uidset_arg(dec_parts.uid,
&cfctx.search_args, &error) < 0)
return catenate_fetch_cleanup(&cfctx, FALSE, error);
cfctx.fetch_ctx = imap_fetch_init(ctx->cmd, cfctx.box);
if (cfctx.fetch_ctx == NULL)
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't init fetch");
cfctx.fetch_ctx->search_args = cfctx.search_args;
mail_search_args_ref(cfctx.search_args);
cfctx.fetch_ctx->urlfetch = TRUE;
if (!fetch_parse_args(cfctx.fetch_ctx, fetch_args, &next_arg))
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't parse fetch args");
if (imap_fetch_begin(cfctx.fetch_ctx) != 0)
return catenate_fetch_cleanup(&cfctx, FALSE,
"can't begin fetch");
cfctx.client_output = ctx->client->output;
ctx->client->output = ctx->cat.output;
fetch_ret = 0;
flush_ret = 0;
error = NULL;
header = TRUE;
toobig = FALSE;
do {
if (fetch_ret == 0)
fetch_ret = imap_fetch_more(cfctx.fetch_ctx);
else
flush_ret = o_stream_flush(ctx->client->output);
while ((ret = i_stream_read(ctx->input)) != 0 && ret != -1) {
if (header) {
char *line = i_stream_next_line(ctx->input);
if (line == NULL)
continue;
header = FALSE;
size = 0;
line = strchr(line, '{');
if (line != NULL)
size = strtoul(line + 1, NULL, 10);
ctx->msg_size += size;
if (ctx->msg_size > MAX_CATENATE_MSG_SIZE) {
toobig = TRUE;
ret = -1;
break;
}
}
if (mailbox_save_continue(ctx->save_ctx) < 0) {
mailbox_save_cancel(&ctx->save_ctx);
ret = -1;
break;
}
}
if (ret == -1) {
if (!toobig)
error = "fetch/save failed";
break;
}
} while (fetch_ret == 0 || flush_ret == 0);
return catenate_fetch_cleanup(&cfctx, toobig, error);
}
static bool catenate_continue_cancel_literal(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
size_t size;
if (cmd->cancel)
return catenate_finish(ctx, TRUE);
(void) i_stream_read(ctx->cat.literal_input);
(void) i_stream_get_data(ctx->cat.literal_input, &size);
i_stream_skip(ctx->cat.literal_input, size);
if (ctx->cat.literal_input->eof || client->input->closed) {
i_stream_unref(&ctx->cat.literal_input);
ctx->cat.literal_input = NULL;
if (cmd->client->input->closed || ctx->args != NULL)
return catenate_finish(ctx, TRUE);
return catenate_finish_literal(cmd, TRUE);
}
return FALSE;
}
static bool catenate_cancel(struct cmd_append_context *ctx, bool nonsync)
{
ctx->failed = TRUE;
if (!nonsync || ctx->args != NULL)
return catenate_finish(ctx, TRUE);
i_assert(ctx->cat.literal_input == NULL);
ctx->cat.literal_input = i_stream_create_limit(ctx->client->input,
ctx->cat.literal_size);
ctx->message_input = TRUE;
ctx->cmd->func = catenate_continue_cancel_literal;
ctx->cmd->context = ctx;
return catenate_continue_cancel_literal(ctx->cmd);
}
static bool catenate_finish(struct cmd_append_context *ctx, bool cancel)
{
if (!cancel) {
o_stream_close(ctx->cat.output);
if (mailbox_save_continue(ctx->save_ctx) < 0 ||
mailbox_save_finish(&ctx->save_ctx) < 0) {
ctx->failed = TRUE;
client_send_storage_error(ctx->cmd, ctx->storage);
cancel = TRUE;
} else {
ctx->save_ctx = NULL;
++ctx->count;
}
if (ctx->args == NULL) {
ctx->cmd->client->input_skip_line = TRUE;
}
}
if (ctx->cat.output != NULL)
o_stream_unref(&ctx->cat.output);
if (ctx->cat.literal_input != NULL)
i_stream_unref(&ctx->cat.literal_input);
if (ctx->cat.literal_url != NULL)
str_free(&ctx->cat.literal_url);
if (ctx->input != NULL)
i_stream_unref(&ctx->input);
ctx->cat.parts = 0;
if (ctx->msg_size == 0 || ctx->count == 0) {
if (!ctx->failed)
client_send_tagline(ctx->cmd,
"NO Can't save a zero byte message.");
ctx->failed = TRUE;
cancel = TRUE;
}
ctx->message_input = FALSE;
if (cancel) {
if (ctx->args != NULL) {
ctx->cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(ctx->cmd);
}
cmd_append_finish(ctx);
return TRUE;
} else {
if (ctx->args == NULL)
imap_parser_reset(ctx->save_parser);
ctx->cmd->func = cmd_append_continue_parsing;
return cmd_append_continue_parsing(ctx->cmd);
}
}
static bool catenate_continue_parsing(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *args, *listargs;
int ret;
bool literal, nonsync;
if (cmd->cancel)
return catenate_finish(ctx, TRUE);
if (client->input_skip_line && !client_skip_line(client))
return FALSE;
ret = imap_parser_read_args(ctx->save_parser, 0,
IMAP_PARSE_FLAG_LITERAL_SIZE, &args);
if (ret == -1) {
if (!ctx->failed)
client_send_command_error(cmd, NULL);
ctx->failed = TRUE;
return catenate_finish(ctx, TRUE);
}
if (ret < 0)
return FALSE;
if (!imap_arg_get_list(args, &listargs))
i_unreached();
if (!IMAP_ARG_IS_EOL(&args[1])) {
i_assert(ctx->args == NULL);
ctx->args = &args[1]; }
args = listargs;
if (IMAP_ARG_IS_EOL(args)) {
client->input_skip_line = ctx->args == NULL;
return catenate_finish(ctx, client->input->closed ||
ctx->failed);
}
literal = imap_parser_get_literal_size(ctx->save_parser,
&ctx->cat.literal_size);
client->input_skip_line = !literal;
nonsync = imap_parser_has_nonsync_literal(ctx->save_parser);
return ctx->failed ? catenate_cancel_args(cmd, args, nonsync) :
catenate_process_args(cmd, args, nonsync);
}
static bool catenate_finish_literal(struct client_command_context *cmd,
bool cancel)
{
struct cmd_append_context *ctx = cmd->context;
ctx->message_input = FALSE;
i_assert(ctx->args == NULL);
imap_parser_reset(ctx->save_parser);
imap_parser_open_list(ctx->save_parser);
i_assert(!cancel || ctx->failed);
cmd->func = catenate_continue_parsing;
return catenate_continue_parsing(cmd);
}
static bool catenate_continue_url(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
int ret;
const unsigned char *data;
size_t size;
if (cmd->cancel)
return catenate_finish(ctx, TRUE);
if (ctx->cat.literal_input == NULL) {
ctx->cat.literal_input =
i_stream_create_limit(client->input,
ctx->cat.literal_size);
i_assert(ctx->cat.literal_url == NULL);
ctx->cat.literal_url = str_new(cmd->pool,
ctx->cat.literal_size + 1);
ctx->message_input = TRUE;
}
if (ctx->save_ctx != NULL) {
while (ctx->cat.literal_input->v_offset !=
ctx->cat.literal_size) {
ret = i_stream_read(ctx->cat.literal_input);
data = i_stream_get_data(ctx->cat.literal_input,
&size);
buffer_append(ctx->cat.literal_url, data, size);
i_stream_skip(ctx->cat.literal_input, size);
if (ret == -1 || ret == 0)
break;
}
}
if (ctx->save_ctx == NULL) {
(void) i_stream_read(ctx->cat.literal_input);
(void) i_stream_get_data(ctx->cat.literal_input, &size);
i_stream_skip(ctx->cat.literal_input, size);
}
if (ctx->cat.literal_input->eof || client->input->closed) {
bool all_written = ctx->cat.literal_input->v_offset ==
ctx->cat.literal_size;
i_stream_unref(&ctx->cat.literal_input);
ctx->cat.literal_input = NULL;
if (ctx->save_ctx == NULL) {
ctx->failed = TRUE;
} else if (!all_written) {
ctx->failed = TRUE;
mailbox_save_cancel(&ctx->save_ctx);
client_disconnect(client,
"EOF while sending URL literal");
} else if (!catenate_url(ctx, str_c(ctx->cat.literal_url)))
ctx->failed = TRUE;
str_free(&ctx->cat.literal_url);
ctx->cat.literal_url = NULL;
if (client->input->closed)
return catenate_finish(ctx, TRUE);
return catenate_finish_literal(cmd, ctx->failed);
}
return FALSE;
}
static bool catenate_continue_text(struct client_command_context *cmd)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
int ret;
size_t size;
if (cmd->cancel)
return catenate_finish(ctx, TRUE);
if (ctx->cat.literal_input == NULL) {
ctx->cat.literal_input =
i_stream_create_limit(client->input,
ctx->cat.literal_size);
ctx->message_input = TRUE;
}
if (ctx->save_ctx != NULL) {
off_t send_ret = 1;
int flush_ret = 0;
do {
if (send_ret > 0)
send_ret = o_stream_send_istream(
ctx->cat.output,
ctx->cat.literal_input);
else
flush_ret = o_stream_flush(ctx->cat.output);
while ((ret = i_stream_read(ctx->input)) != 0 &&
ret != -1) {
if (mailbox_save_continue(ctx->save_ctx) < 0) {
mailbox_save_cancel(&ctx->save_ctx);
ret = -1;
break;
}
}
if (ret == -1)
break;
} while (send_ret > 0 || flush_ret == 0);
}
if (ctx->save_ctx == NULL) {
(void) i_stream_read(ctx->cat.literal_input);
(void) i_stream_get_data(ctx->cat.literal_input, &size);
i_stream_skip(ctx->cat.literal_input, size);
}
if (ctx->cat.literal_input->eof || client->input->closed) {
bool all_written = ctx->cat.literal_input->v_offset ==
ctx->cat.literal_size;
i_stream_unref(&ctx->cat.literal_input);
ctx->cat.literal_input = NULL;
if (ctx->save_ctx == NULL) {
client_send_storage_error(cmd, ctx->storage);
ctx->failed = TRUE;
} else if (!all_written) {
ctx->failed = TRUE;
mailbox_save_cancel(&ctx->save_ctx);
client_disconnect(client, "EOF while appending");
}
if (client->input->closed)
return catenate_finish(ctx, TRUE);
return catenate_finish_literal(cmd, ctx->failed);
}
return FALSE;
}
static bool catenate_begin_parsing(struct client_command_context *cmd,
const struct imap_arg *args)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *flags_list, *listargs;
bool literal, nonsync;
enum mail_flags flags = 0;
const char *str;
struct mail_keywords *keywords = NULL;
time_t internal_date = (time_t) -1;
int ret, timezone_offset = 0;
int fds[2];
literal = imap_parser_get_literal_size(ctx->save_parser,
&ctx->cat.literal_size);
client->input_skip_line = !literal;
nonsync = imap_parser_has_nonsync_literal(ctx->save_parser);
if (ctx->failed)
return catenate_cancel(ctx, nonsync);
if (imap_arg_get_list(args, &flags_list)) {
const char *const *keywords_list = NULL;
++args;
if (!client_parse_mail_flags(cmd, flags_list,
&flags, &keywords_list))
return catenate_cancel(ctx, nonsync);
if (keywords_list != NULL &&
mailbox_keywords_create(ctx->box, keywords_list,
&keywords) < 0) {
client_send_storage_error(cmd, ctx->storage);
return catenate_cancel(ctx, nonsync);
}
}
if (args->type == IMAP_ARG_STRING && imap_arg_get_string(args, &str)) {
++args;
if (!imap_parse_datetime(str, &internal_date,
&timezone_offset)) {
client_send_tagline(cmd, "BAD Invalid internal date.");
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return catenate_cancel(ctx, nonsync);
}
if (internal_date != (time_t)-1 &&
internal_date > ioloop_time + INTERNALDATE_MAX_FUTURE_SECS) {
internal_date = (time_t)-1;
timezone_offset = 0;
}
}
if (!imap_arg_atom_equals(args, "CATENATE") ||
!imap_arg_get_list(&args[1], &listargs)) {
client_send_tagline(cmd, "BAD Invalid arguments.");
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return catenate_cancel(ctx, nonsync);
}
if (!IMAP_ARG_IS_EOL(&args[2]))
ctx->args = &args[2];
args = listargs;
if (IMAP_ARG_IS_EOL(args)) { client_send_tagline(cmd, "BAD Invalid arguments.");
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return catenate_cancel(ctx, nonsync);
}
if (pipe(fds) < 0) {
i_error("catenate_begin_parsing: pipe: %m");
client_send_tagline(cmd, "NO "MAIL_ERRSTR_CRITICAL_MSG);
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
return catenate_cancel(ctx, nonsync);
}
fd_set_nonblock(fds[0], TRUE);
fd_set_nonblock(fds[1], TRUE);
ctx->cat.output = o_stream_create_fd(fds[1], PIPE_MAX, TRUE);
ctx->input = i_stream_create_fd(fds[0], PIPE_MAX, TRUE);
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);
ret = mailbox_save_begin(&ctx->save_ctx, ctx->input);
if (keywords != NULL)
mailbox_keywords_unref(ctx->box, &keywords);
if (ret < 0) {
client_send_storage_error(cmd, ctx->storage);
return catenate_cancel(ctx, nonsync);
}
ctx->msg_size = 0;
return catenate_process_args(cmd, args, nonsync);
}
static bool catenate_begin_cancel(struct client_command_context *cmd,
const struct imap_arg *args)
{
struct client *client = cmd->client;
struct cmd_append_context *ctx = cmd->context;
const struct imap_arg *listargs;
bool literal, nonsync;
literal = imap_parser_get_literal_size(ctx->save_parser,
&ctx->cat.literal_size);
client->input_skip_line = !literal;
nonsync = imap_parser_has_nonsync_literal(ctx->save_parser);
if (args->type == IMAP_ARG_LIST)
++args;
if (args->type == IMAP_ARG_STRING)
++args;
if (!imap_arg_atom_equals(args, "CATENATE") ||
!imap_arg_get_list(&args[1], &listargs))
return catenate_cancel(ctx, nonsync);
if (!IMAP_ARG_IS_EOL(&args[2]))
ctx->args = &args[2];
args = listargs;
if (IMAP_ARG_IS_EOL(args)) return catenate_cancel(ctx, nonsync);
return catenate_cancel_args(cmd, args, nonsync);
}
static bool catenate_process_args(struct client_command_context *cmd,
const struct imap_arg *args, bool nonsync)
{
struct cmd_append_context *ctx = cmd->context;
while (imap_arg_atom_equals(args, "URL")) {
if (++ctx->cat.parts > MAX_CATENATE_PARTS) {
client_send_tagline(cmd,
"BAD Too many message parts.");
return catenate_cancel(ctx, nonsync);
}
++args;
if (args->type == IMAP_ARG_ATOM ||
args->type == IMAP_ARG_STRING) {
if (!catenate_url(ctx, args->_data.str))
return catenate_cancel(ctx, nonsync);
++args;
} else if (args->type == IMAP_ARG_LITERAL_SIZE ||
args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) {
if (ctx->cat.literal_size > MAX_URL_LITERAL_SIZE) {
client_send_tagline(cmd,
"BAD URL literal too large");
return catenate_cancel(ctx, nonsync);
}
catenate_solicit(ctx, nonsync);
cmd->func = catenate_continue_url;
return catenate_continue_url(cmd);
} else {
client_send_tagline(cmd, "BAD Invalid arguments.");
return catenate_cancel(ctx, nonsync);
}
}
if (imap_arg_atom_equals(args, "TEXT") &&
(args[1].type == IMAP_ARG_LITERAL_SIZE ||
args[1].type == IMAP_ARG_LITERAL_SIZE_NONSYNC)) {
if (++ctx->cat.parts > MAX_CATENATE_PARTS) {
client_send_tagline(cmd,
"BAD Too many message parts.");
return catenate_cancel(ctx, nonsync);
}
ctx->msg_size += ctx->cat.literal_size;
if (ctx->msg_size > MAX_CATENATE_MSG_SIZE) {
client_send_tagline(cmd,
"NO [TOOBIG] Resulting message too large");
return catenate_cancel(ctx, nonsync);
}
catenate_solicit(ctx, nonsync);
cmd->func = catenate_continue_text;
return catenate_continue_text(cmd);
} else if (!IMAP_ARG_IS_EOL(args)) {
client_send_tagline(cmd, "BAD Invalid arguments.");
return catenate_cancel(ctx, nonsync);
}
return catenate_finish(ctx, FALSE);
}
static bool catenate_cancel_args(struct client_command_context *cmd,
const struct imap_arg *args, bool nonsync)
{
struct cmd_append_context *ctx = cmd->context;
while (imap_arg_atom_equals(args, "URL")) {
++args;
if (args->type == IMAP_ARG_ATOM ||
args->type == IMAP_ARG_STRING)
++args;
else if (args->type == IMAP_ARG_LITERAL_SIZE ||
args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
return catenate_cancel(ctx, nonsync);
}
if (imap_arg_atom_equals(args, "TEXT") &&
(args[1].type == IMAP_ARG_LITERAL_SIZE ||
args[1].type == IMAP_ARG_LITERAL_SIZE_NONSYNC))
return catenate_cancel(ctx, nonsync);
else if (!IMAP_ARG_IS_EOL(args))
return catenate_cancel(ctx, nonsync);
return catenate_finish(ctx, TRUE);
}