~ubuntu-branches/ubuntu/utopic/dovecot/utopic

« back to all changes in this revision

Viewing changes to src/imap/cmd-append.c

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-01-08 09:35:49 UTC
  • mfrom: (4.1.35 sid)
  • Revision ID: package-import@ubuntu.com-20140108093549-i72o93pux8p0dlaf
Tags: 1:2.2.9-1ubuntu1
* Merge from Debian unstable, remaining changes:
  + Add mail-stack-delivery package:
    - Update d/rules
    - d/control: convert existing dovecot-postfix package to a dummy
      package and add new mail-stack-delivery package.
    - Update maintainer scripts.
    - Rename d/dovecot-postfix.* to debian/mail-stack-delivery.*
    - d/mail-stack-delivery.preinst: Move previously installed backups and
      config files to a new package namespace.
    - d/mail-stack-delivery.prerm: Added to handle downgrades.
  + Use Snakeoil SSL certificates by default:
    - d/control: Depend on ssl-cert.
    - d/dovecot-core.postinst: Relax grep for SSL_* a bit.
  + Add autopkgtest to debian/tests/*.
  + Add ufw integration:
    - d/dovecot-core.ufw.profile: new ufw profile.
    - d/rules: install profile in dovecot-core.
    - d/control: dovecot-core - suggest ufw.
  + d/dovecot-core.dirs: Added usr/share/doc/dovecot-core
  + Add apport hook:
    - d/rules, d/source_dovecot.py
  + Add upstart job:
    - d/rules, d/dovecot-core.dovecot.upstart, d/control,
      d/dovecot-core.dirs, dovecot-imapd.{postrm, postinst, prerm},
      d/dovecot-pop3d.{postinst, postrm, prerm}.
      d/mail-stack-deliver.postinst: Convert init script to upstart.
  + Use the autotools-dev dh addon to update config.guess/config.sub for
    arm64.
* Dropped changes, included in Debian:
  - Update Dovecot name to reflect distribution in login greeting.
  - Update Drac plugin for >= 2.0.0 support.
* d/control: Drop dovecot-postfix package as its no longer required.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
 
1
/* Copyright (c) 2002-2013 Dovecot authors, see the included COPYING file */
2
2
 
3
3
#include "imap-common.h"
4
4
#include "ioloop.h"
5
5
#include "istream.h"
 
6
#include "istream-chain.h"
6
7
#include "ostream.h"
7
8
#include "str.h"
 
9
#include "imap-resp-code.h"
 
10
#include "istream-binary-converter.h"
 
11
#include "mail-storage-private.h"
8
12
#include "imap-parser.h"
9
13
#include "imap-date.h"
10
14
#include "imap-util.h"
11
15
#include "imap-commands.h"
 
16
#include "imap-msgpart-url.h"
12
17
 
13
18
#include <sys/time.h>
14
19
 
26
31
        struct mailbox_transaction_context *t;
27
32
        time_t started;
28
33
 
 
34
        struct istream_chain *catchain;
 
35
        uoff_t cat_msg_size;
 
36
 
29
37
        struct istream *input;
30
 
        uoff_t msg_size;
 
38
        struct istream *litinput;
 
39
        uoff_t literal_size;
31
40
 
32
41
        struct imap_parser *save_parser;
33
42
        struct mail_save_context *save_ctx;
34
43
        unsigned int count;
35
44
 
36
45
        unsigned int message_input:1;
 
46
        unsigned int binary_input:1;
 
47
        unsigned int catenate:1;
37
48
        unsigned int failed:1;
38
49
};
39
50
 
40
51
static void cmd_append_finish(struct cmd_append_context *ctx);
41
52
static bool cmd_append_continue_message(struct client_command_context *cmd);
42
 
static bool cmd_append_continue_parsing(struct client_command_context *cmd);
 
53
static bool cmd_append_parse_new_msg(struct client_command_context *cmd);
43
54
 
44
 
static const char *get_disconnect_reason(struct cmd_append_context *ctx)
 
55
static const char *
 
56
get_disconnect_reason(struct cmd_append_context *ctx, uoff_t lit_offset)
45
57
{
46
58
        string_t *str = t_str_new(128);
47
59
        unsigned int secs = ioloop_time - ctx->started;
48
60
 
49
61
        str_printfa(str, "Disconnected in APPEND (%u msgs, %u secs",
50
62
                    ctx->count, secs);
51
 
        if (ctx->input != NULL) {
 
63
        if (ctx->literal_size > 0) {
52
64
                str_printfa(str, ", %"PRIuUOFF_T"/%"PRIuUOFF_T" bytes",
53
 
                            ctx->input->v_offset, ctx->msg_size);
 
65
                            lit_offset, ctx->literal_size);
54
66
        }
55
67
        str_append_c(str, ')');
56
68
        return str_c(str);
62
74
        struct client *client = cmd->client;
63
75
        const char *reason;
64
76
        bool finished;
 
77
        uoff_t lit_offset;
65
78
 
66
79
        i_assert(!client->destroyed);
67
80
 
71
84
        switch (i_stream_read(client->input)) {
72
85
        case -1:
73
86
                /* disconnected */
74
 
                reason = get_disconnect_reason(ctx);
 
87
                lit_offset = ctx->litinput == NULL ? 0 :
 
88
                        ctx->litinput->v_offset;
 
89
                reason = get_disconnect_reason(ctx, lit_offset);
75
90
                cmd_append_finish(cmd->context);
76
91
                /* Reset command so that client_destroy() doesn't try to call
77
92
                   cmd_append_continue_message() anymore. */
91
106
                   until newline is found. */
92
107
                client->input_skip_line = TRUE;
93
108
 
94
 
                client_send_command_error(cmd, "Too long argument.");
 
109
                if (!ctx->failed)
 
110
                        client_send_command_error(cmd, "Too long argument.");
95
111
                cmd->param_error = TRUE;
96
112
                client_command_free(&cmd);
97
113
                return;
103
119
                (void)client_handle_unfinished_cmd(cmd);
104
120
        else
105
121
                client_command_free(&cmd);
106
 
        (void)cmd_sync_delayed(client);
 
122
        cmd_sync_delayed(client);
107
123
        o_stream_uncork(client->output);
108
124
 
109
125
        if (client->disconnected)
112
128
                client_continue_pending_input(client);
113
129
}
114
130
 
115
 
/* Returns -1 = error, 0 = need more data, 1 = successful. flags and
116
 
   internal_date may be NULL as a result, but mailbox and msg_size are always
117
 
   set when successful. */
118
 
static int validate_args(const struct imap_arg *args,
119
 
                         const struct imap_arg **flags_r,
120
 
                         const char **internal_date_r, uoff_t *msg_size_r,
121
 
                         bool *nonsync_r)
122
 
{
123
 
        /* [<flags>] */
124
 
        if (!imap_arg_get_list(args, flags_r))
125
 
                *flags_r = NULL;
126
 
        else
127
 
                args++;
128
 
 
129
 
        /* [<internal date>] */
130
 
        if (args->type != IMAP_ARG_STRING)
131
 
                *internal_date_r = NULL;
132
 
        else {
133
 
                *internal_date_r = imap_arg_as_astring(args);
134
 
                args++;
135
 
        }
136
 
 
137
 
        if (!imap_arg_get_literal_size(args, msg_size_r)) {
138
 
                *nonsync_r = FALSE;
139
 
                return FALSE;
140
 
        }
141
 
 
142
 
        *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
143
 
        return TRUE;
144
 
}
145
 
 
146
131
static void cmd_append_finish(struct cmd_append_context *ctx)
147
132
{
148
 
        imap_parser_unref(&ctx->save_parser);
 
133
        if (ctx->save_parser != NULL)
 
134
                imap_parser_unref(&ctx->save_parser);
149
135
 
150
136
        i_assert(ctx->client->input_lock == ctx->cmd);
151
137
 
152
 
        io_remove(&ctx->client->io);
 
138
        if (ctx->client->io != NULL)
 
139
                io_remove(&ctx->client->io);
153
140
        /* we must put back the original flush callback before beginning to
154
141
           sync (the command is still unfinished at that point) */
155
142
        o_stream_set_flush_callback(ctx->client->output,
156
143
                                    client_output, ctx->client);
157
144
 
 
145
        if (ctx->litinput != NULL)
 
146
                i_stream_unref(&ctx->litinput);
158
147
        if (ctx->input != NULL)
159
148
                i_stream_unref(&ctx->input);
160
149
        if (ctx->save_ctx != NULL)
165
154
                mailbox_free(&ctx->box);
166
155
}
167
156
 
168
 
static bool cmd_append_continue_cancel(struct client_command_context *cmd)
169
 
{
170
 
        struct cmd_append_context *ctx = cmd->context;
171
 
        size_t size;
 
157
static bool cmd_append_send_literal_continue(struct cmd_append_context *ctx)
 
158
{
 
159
        if (ctx->failed) {
 
160
                /* tagline was already sent, we can abort here */
 
161
                return FALSE;
 
162
        }
 
163
 
 
164
        o_stream_nsend(ctx->client->output, "+ OK\r\n", 6);
 
165
        o_stream_nflush(ctx->client->output);
 
166
        o_stream_uncork(ctx->client->output);
 
167
        o_stream_cork(ctx->client->output);
 
168
        return TRUE;
 
169
}
 
170
 
 
171
static int
 
172
cmd_append_catenate_mpurl(struct client_command_context *cmd,
 
173
                          const char *caturl, struct imap_msgpart_url *mpurl)
 
174
{
 
175
        struct cmd_append_context *ctx = cmd->context;
 
176
        struct imap_msgpart_open_result mpresult;
 
177
        uoff_t newsize;
 
178
        const char *error;
 
179
        int ret;
 
180
 
 
181
        /* catenate URL */
 
182
        ret = imap_msgpart_url_read_part(mpurl, &mpresult, &error);
 
183
        if (ret < 0) {
 
184
                client_send_storage_error(cmd, ctx->storage);
 
185
                return -1;
 
186
        }
 
187
        if (ret == 0) {
 
188
                /* invalid url, abort */
 
189
                client_send_tagline(cmd,
 
190
                        t_strdup_printf("NO [BADURL %s] %s.", caturl, error));
 
191
                return -1;
 
192
        }
 
193
        if (mpresult.size == 0) {
 
194
                /* empty input */
 
195
                return 0;
 
196
        }
 
197
 
 
198
        newsize = ctx->cat_msg_size + mpresult.size;
 
199
        if (newsize < ctx->cat_msg_size) {
 
200
                client_send_tagline(cmd,
 
201
                        "NO [TOOBIG] Composed message grows too big.");
 
202
                return -1;
 
203
        }
 
204
 
 
205
        ctx->cat_msg_size = newsize;
 
206
        /* add this input stream to chain */
 
207
        i_stream_chain_append(ctx->catchain, mpresult.input);
 
208
        /* save by reading the chain stream */
 
209
        while (!i_stream_is_eof(mpresult.input)) {
 
210
                ret = i_stream_read(mpresult.input);
 
211
                i_assert(ret != 0); /* we can handle only blocking input here */
 
212
                if (mailbox_save_continue(ctx->save_ctx) < 0 || ret == -1)
 
213
                        break;
 
214
        }
 
215
 
 
216
        if (mpresult.input->stream_errno != 0) {
 
217
                errno = mpresult.input->stream_errno;
 
218
                mail_storage_set_critical(ctx->box->storage,
 
219
                        "read(%s) failed: %s (for CATENATE URL %s)",
 
220
                        i_stream_get_name(mpresult.input),
 
221
                        i_stream_get_error(mpresult.input), caturl);
 
222
                client_send_storage_error(cmd, ctx->storage);
 
223
                ret = -1;
 
224
        } else if (!mpresult.input->eof) {
 
225
                /* save failed */
 
226
                client_send_storage_error(cmd, ctx->storage);
 
227
                ret = -1;
 
228
        } else {
 
229
                /* all the input must be consumed, so istream-chain's read()
 
230
                   unreferences the stream and we can free its parent mail */
 
231
                i_assert(!i_stream_have_bytes_left(mpresult.input));
 
232
                ret = 0;
 
233
        }
 
234
        return ret;
 
235
}
 
236
 
 
237
static int
 
238
cmd_append_catenate_url(struct client_command_context *cmd, const char *caturl)
 
239
{
 
240
        struct cmd_append_context *ctx = cmd->context;
 
241
        struct imap_msgpart_url *mpurl;
 
242
        const char *error;
 
243
        int ret;
 
244
 
 
245
        if (ctx->failed)
 
246
                return -1;
 
247
 
 
248
        ret = imap_msgpart_url_parse(cmd->client->user, cmd->client->mailbox,
 
249
                                     caturl, &mpurl, &error);
 
250
        if (ret < 0) {
 
251
                client_send_storage_error(cmd, ctx->storage);
 
252
                return -1;
 
253
        }
 
254
        if (ret == 0) {
 
255
                /* invalid url, abort */
 
256
                client_send_tagline(cmd,
 
257
                        t_strdup_printf("NO [BADURL %s] %s.", caturl, error));
 
258
                return -1;
 
259
        }
 
260
        ret = cmd_append_catenate_mpurl(cmd, caturl, mpurl);
 
261
        imap_msgpart_url_free(&mpurl);
 
262
        return ret;
 
263
}
 
264
 
 
265
static void cmd_append_catenate_text(struct client_command_context *cmd)
 
266
{
 
267
        struct cmd_append_context *ctx = cmd->context;
 
268
 
 
269
        if (ctx->literal_size > (uoff_t)-1 - ctx->cat_msg_size &&
 
270
            !ctx->failed) {
 
271
                client_send_tagline(cmd,
 
272
                        "NO [TOOBIG] Composed message grows too big.");
 
273
                ctx->failed = TRUE;
 
274
        }
 
275
 
 
276
        /* save the mail */
 
277
        ctx->cat_msg_size += ctx->literal_size;
 
278
        if (ctx->literal_size == 0) {
 
279
                /* zero length literal. RFC doesn't explicitly specify
 
280
                   what should be done with this, so we'll simply
 
281
                   handle it by skipping the empty text part. */
 
282
                ctx->litinput = i_stream_create_from_data("", 0);
 
283
                ctx->litinput->eof = TRUE;
 
284
        } else {
 
285
                ctx->litinput = i_stream_create_limit(cmd->client->input,
 
286
                                                      ctx->literal_size);
 
287
                i_stream_chain_append(ctx->catchain, ctx->litinput);
 
288
        }
 
289
}
 
290
 
 
291
static int
 
292
cmd_append_catenate(struct client_command_context *cmd,
 
293
                    const struct imap_arg *args, bool *nonsync_r)
 
294
{
 
295
        struct cmd_append_context *ctx = cmd->context;
 
296
        const char *catpart;
 
297
 
 
298
        *nonsync_r = FALSE;
 
299
 
 
300
        /* Handle URLs until a TEXT literal is encountered */
 
301
        while (imap_arg_get_atom(args, &catpart)) {
 
302
                const char *caturl;
 
303
 
 
304
                if (strcasecmp(catpart, "URL") == 0 ) {
 
305
                        /* URL <url> */ 
 
306
                        args++;
 
307
                        if (!imap_arg_get_astring(args, &caturl))
 
308
                                break;
 
309
                        if (cmd_append_catenate_url(cmd, caturl) < 0) {
 
310
                                /* delay failure until we can stop
 
311
                                   parsing input */
 
312
                                ctx->failed = TRUE;
 
313
                        }
 
314
                } else if (strcasecmp(catpart, "TEXT") == 0) {
 
315
                        /* TEXT <literal> */
 
316
                        args++;
 
317
                        if (!imap_arg_get_literal_size(args, &ctx->literal_size))
 
318
                                break;
 
319
                        if (args->literal8 && !ctx->binary_input &&
 
320
                            !ctx->failed) {
 
321
                                client_send_tagline(cmd,
 
322
                                        "NO ["IMAP_RESP_CODE_UNKNOWN_CTE"] "
 
323
                                        "Binary input allowed only when the first part is binary.");
 
324
                                ctx->failed = TRUE;
 
325
                        }
 
326
                        *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
 
327
                        cmd_append_catenate_text(cmd);
 
328
                        return 1;
 
329
                } else {
 
330
                        break;
 
331
                }
 
332
                args++;
 
333
        }
 
334
 
 
335
        if (IMAP_ARG_IS_EOL(args)) {
 
336
                /* ")" */
 
337
                return 0;
 
338
        }
 
339
        if (!ctx->failed)
 
340
                client_send_command_error(cmd, "Invalid arguments.");
 
341
        return -1;
 
342
}
 
343
 
 
344
static void cmd_append_finish_catenate(struct client_command_context *cmd)
 
345
{
 
346
        struct cmd_append_context *ctx = cmd->context;
 
347
 
 
348
        i_stream_chain_append_eof(ctx->catchain);
 
349
        i_stream_unref(&ctx->input);
 
350
        ctx->catenate = FALSE;
 
351
        ctx->catchain = NULL;
 
352
 
 
353
        if (ctx->failed) {
 
354
                /* APPEND has already failed */
 
355
                if (ctx->save_ctx != NULL)
 
356
                        mailbox_save_cancel(&ctx->save_ctx);
 
357
        } else {
 
358
                if (mailbox_save_finish(&ctx->save_ctx) < 0) {
 
359
                        client_send_storage_error(cmd, ctx->storage);
 
360
                        ctx->failed = TRUE;
 
361
                }
 
362
        }
 
363
}
 
364
 
 
365
static bool catenate_args_can_stop(struct cmd_append_context *ctx,
 
366
                                   const struct imap_arg *args)
 
367
{
 
368
        /* eat away literal_sizes from URLs */
 
369
        while (args->type != IMAP_ARG_EOL) {
 
370
                if (imap_arg_atom_equals(args, "TEXT"))
 
371
                        return TRUE;
 
372
                if (!imap_arg_atom_equals(args, "URL")) {
 
373
                        /* error - handle it later */
 
374
                        return TRUE;
 
375
                }
 
376
                args++;
 
377
                if (args->type == IMAP_ARG_LITERAL_SIZE ||
 
378
                    args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC) {
 
379
                        if (args->type == IMAP_ARG_LITERAL_SIZE) {
 
380
                                if (!cmd_append_send_literal_continue(ctx))
 
381
                                        return TRUE;
 
382
                        }
 
383
                        imap_parser_read_last_literal(ctx->save_parser);
 
384
                        return FALSE;
 
385
                }
 
386
                args++;
 
387
        }
 
388
        return TRUE;
 
389
}
 
390
 
 
391
static bool cmd_append_continue_catenate(struct client_command_context *cmd)
 
392
{
 
393
        struct client *client = cmd->client;
 
394
        struct cmd_append_context *ctx = cmd->context;
 
395
        const struct imap_arg *args;
 
396
        const char *msg;
 
397
        bool fatal, nonsync = FALSE;
 
398
        int ret;
172
399
 
173
400
        if (cmd->cancel) {
174
 
                cmd_append_finish(ctx);
175
 
                return TRUE;
176
 
        }
177
 
 
178
 
        (void)i_stream_read(ctx->input);
179
 
        (void)i_stream_get_data(ctx->input, &size);
180
 
        i_stream_skip(ctx->input, size);
181
 
 
182
 
        if (cmd->client->input->closed) {
183
 
                cmd_append_finish(ctx);
184
 
                return TRUE;
185
 
        }
186
 
 
187
 
        if (ctx->input->v_offset == ctx->msg_size) {
188
 
                /* finished, but with MULTIAPPEND and LITERAL+ we may get
189
 
                   more messages. */
190
 
                i_stream_unref(&ctx->input);
191
 
                ctx->input = NULL;
192
 
 
193
 
                ctx->message_input = FALSE;
 
401
                /* cancel the command immediately (disconnection) */
 
402
                cmd_append_finish(ctx);
 
403
                return TRUE;
 
404
        }
 
405
 
 
406
        /* we're parsing inside CATENATE (..) list after handling a TEXT part.
 
407
           it's fine that this would need to fully fit into input buffer
 
408
           (although clients attempting to DoS could simply insert an extra
 
409
           {1+} between the URLs) */
 
410
        do {
 
411
                ret = imap_parser_read_args(ctx->save_parser, 0,
 
412
                                            IMAP_PARSE_FLAG_LITERAL_SIZE |
 
413
                                            IMAP_PARSE_FLAG_LITERAL8 |
 
414
                                            IMAP_PARSE_FLAG_INSIDE_LIST, &args);
 
415
        } while (ret > 0 && !catenate_args_can_stop(ctx, args));
 
416
        if (ret == -1) {
 
417
                msg = imap_parser_get_error(ctx->save_parser, &fatal);
 
418
                if (fatal)
 
419
                        client_disconnect_with_error(client, msg);
 
420
                else if (!ctx->failed)
 
421
                        client_send_command_error(cmd, msg);
 
422
                client->input_skip_line = TRUE;
 
423
                cmd_append_finish(ctx);
 
424
                return TRUE;
 
425
        }
 
426
        if (ret < 0) {
 
427
                /* need more data */
 
428
                return FALSE;
 
429
        }
 
430
 
 
431
        if ((ret = cmd_append_catenate(cmd, args, &nonsync)) < 0) {
 
432
                /* invalid parameters, abort immediately */
 
433
                cmd_append_finish(ctx);
 
434
                return TRUE;
 
435
        }
 
436
 
 
437
        if (ret == 0) {
 
438
                /* ")" */
 
439
                cmd_append_finish_catenate(cmd);
 
440
 
 
441
                /* last catenate part */
194
442
                imap_parser_reset(ctx->save_parser);
195
 
                cmd->func = cmd_append_continue_parsing;
196
 
                return cmd_append_continue_parsing(cmd);
 
443
                cmd->func = cmd_append_parse_new_msg;
 
444
                return cmd_append_parse_new_msg(cmd);
197
445
        }
198
446
 
199
 
        return FALSE;
200
 
}
201
 
 
202
 
static bool cmd_append_cancel(struct cmd_append_context *ctx, bool nonsync)
203
 
{
204
 
        ctx->failed = TRUE;
 
447
        /* TEXT <literal> */
205
448
 
206
449
        if (!nonsync) {
207
 
                cmd_append_finish(ctx);
208
 
                return TRUE;
 
450
                if (!cmd_append_send_literal_continue(ctx)) {
 
451
                        cmd_append_finish(ctx);
 
452
                        return TRUE;
 
453
                }
209
454
        }
210
455
 
211
 
        /* we have to read the nonsynced literal so we don't treat the message
212
 
           data as commands. */
213
 
        ctx->input = i_stream_create_limit(ctx->client->input, ctx->msg_size);
214
 
 
 
456
        i_assert(ctx->litinput != NULL);
215
457
        ctx->message_input = TRUE;
216
 
        ctx->cmd->func = cmd_append_continue_cancel;
217
 
        ctx->cmd->context = ctx;
218
 
        return cmd_append_continue_cancel(ctx->cmd);
 
458
        cmd->func = cmd_append_continue_message;
 
459
        return cmd_append_continue_message(cmd);
219
460
}
220
461
 
221
 
static bool cmd_append_continue_parsing(struct client_command_context *cmd)
 
462
static int
 
463
cmd_append_handle_args(struct client_command_context *cmd,
 
464
                       const struct imap_arg *args, bool *nonsync_r)
222
465
{
223
466
        struct client *client = cmd->client;
224
467
        struct cmd_append_context *ctx = cmd->context;
225
 
        const struct imap_arg *args;
226
468
        const struct imap_arg *flags_list;
 
469
        const struct imap_arg *cat_list = NULL;
227
470
        enum mail_flags flags;
228
471
        const char *const *keywords_list;
229
472
        struct mail_keywords *keywords;
230
 
        const char *internal_date_str, *msg;
 
473
        struct istream *input;
 
474
        const char *internal_date_str;
231
475
        time_t internal_date;
232
476
        int ret, timezone_offset;
233
 
        unsigned int save_count;
234
 
        bool nonsync, fatal;
235
 
 
236
 
        if (cmd->cancel) {
237
 
                cmd_append_finish(ctx);
238
 
                return TRUE;
239
 
        }
240
 
 
241
 
        /* if error occurs, the CRLF is already read. */
242
 
        client->input_skip_line = FALSE;
243
 
 
244
 
        /* [<flags>] [<internal date>] <message literal> */
245
 
        ret = imap_parser_read_args(ctx->save_parser, 0,
246
 
                                    IMAP_PARSE_FLAG_LITERAL_SIZE, &args);
247
 
        if (ret == -1) {
248
 
                if (!ctx->failed) {
249
 
                        msg = imap_parser_get_error(ctx->save_parser, &fatal);
250
 
                        if (fatal)
251
 
                                client_disconnect_with_error(client, msg);
252
 
                        else
253
 
                                client_send_command_error(cmd, msg);
254
 
                }
255
 
                cmd_append_finish(ctx);
256
 
                return TRUE;
257
 
        }
258
 
        if (ret < 0) {
259
 
                /* need more data */
260
 
                return FALSE;
261
 
        }
262
 
 
263
 
        if (IMAP_ARG_IS_EOL(args)) {
264
 
                /* last message */
265
 
                enum mailbox_sync_flags sync_flags;
266
 
                enum imap_sync_flags imap_flags;
267
 
                struct mail_transaction_commit_changes changes;
268
 
                string_t *msg;
269
 
 
270
 
                /* eat away the trailing CRLF */
271
 
                client->input_skip_line = TRUE;
272
 
 
273
 
                if (ctx->failed) {
274
 
                        /* we failed earlier, error message is sent */
275
 
                        cmd_append_finish(ctx);
276
 
                        return TRUE;
277
 
                }
278
 
                if (ctx->count == 0) {
279
 
                        client_send_tagline(cmd, "BAD Missing message size.");
280
 
                        cmd_append_finish(ctx);
281
 
                        return TRUE;
282
 
                }
283
 
 
284
 
                ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
285
 
                if (ret < 0) {
286
 
                        client_send_storage_error(cmd, ctx->storage);
287
 
                        cmd_append_finish(ctx);
288
 
                        return TRUE;
289
 
                }
290
 
 
291
 
                msg = t_str_new(256);
292
 
                save_count = seq_range_count(&changes.saved_uids);
293
 
                if (save_count == 0) {
294
 
                        /* not supported by backend (virtual) */
295
 
                        str_append(msg, "OK Append completed.");
296
 
                } else {
297
 
                        i_assert(ctx->count == save_count);
298
 
                        str_printfa(msg, "OK [APPENDUID %u ",
299
 
                                    changes.uid_validity);
300
 
                        imap_write_seq_range(msg, &changes.saved_uids);
301
 
                        str_append(msg, "] Append completed.");
302
 
                }
303
 
                pool_unref(&changes.pool);
304
 
 
305
 
                if (ctx->box == cmd->client->mailbox) {
306
 
                        sync_flags = 0;
307
 
                        imap_flags = IMAP_SYNC_FLAG_SAFE;
308
 
                } else {
309
 
                        sync_flags = MAILBOX_SYNC_FLAG_FAST;
310
 
                        imap_flags = 0;
311
 
                }
312
 
 
313
 
                cmd_append_finish(ctx);
314
 
                return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
315
 
        }
316
 
 
317
 
        if (!validate_args(args, &flags_list, &internal_date_str,
318
 
                           &ctx->msg_size, &nonsync)) {
319
 
                client_send_command_error(cmd, "Invalid arguments.");
320
 
                client->input_skip_line = TRUE;
321
 
                return cmd_append_cancel(ctx, nonsync);
322
 
        }
323
 
 
324
 
        if (ctx->failed) {
325
 
                /* we failed earlier, make sure we just eat nonsync-literal
326
 
                   if it's given. */
327
 
                return cmd_append_cancel(ctx, nonsync);
328
 
        }
329
 
 
330
 
        if (flags_list != NULL) {
 
477
        bool valid;
 
478
 
 
479
        /* [<flags>] */
 
480
        if (!imap_arg_get_list(args, &flags_list))
 
481
                flags_list = NULL;
 
482
        else
 
483
                args++;
 
484
 
 
485
        /* [<internal date>] */
 
486
        if (args->type != IMAP_ARG_STRING)
 
487
                internal_date_str = NULL;
 
488
        else {
 
489
                internal_date_str = imap_arg_as_astring(args);
 
490
                args++;
 
491
        }
 
492
 
 
493
        /* <message literal> | CATENATE (..) */
 
494
        valid = FALSE;
 
495
        *nonsync_r = FALSE;
 
496
        ctx->catenate = FALSE;
 
497
        if (imap_arg_atom_equals(args, "CATENATE")) {
 
498
                args++;
 
499
                if (imap_arg_get_list(args, &cat_list)) {
 
500
                        valid = TRUE;
 
501
                        ctx->catenate = TRUE;
 
502
                }
 
503
                /* We'll do BINARY conversion only if the CATENATE's first
 
504
                   part is a literal8. If it doesn't and a literal8 is seen
 
505
                   later we'll abort the append with UNKNOWN-CTE. */
 
506
                ctx->binary_input = imap_arg_atom_equals(&cat_list[0], "TEXT") &&
 
507
                        cat_list[1].literal8;
 
508
 
 
509
        } else if (imap_arg_get_literal_size(args, &ctx->literal_size)) {
 
510
                *nonsync_r = args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC;
 
511
                ctx->binary_input = args->literal8;
 
512
                valid = TRUE;
 
513
        }
 
514
        if (!IMAP_ARG_IS_EOL(&args[1]))
 
515
                valid = FALSE;
 
516
        if (!valid) {
 
517
                client->input_skip_line = TRUE;
 
518
                if (!ctx->failed)
 
519
                        client_send_command_error(cmd, "Invalid arguments.");
 
520
                return -1;
 
521
        }
 
522
 
 
523
        if (flags_list == NULL || ctx->failed) {
 
524
                flags = 0;
 
525
                keywords = NULL;
 
526
        } else {
331
527
                if (!client_parse_mail_flags(cmd, flags_list,
332
528
                                             &flags, &keywords_list))
333
 
                        return cmd_append_cancel(ctx, nonsync);
 
529
                        return -1;
334
530
                if (keywords_list == NULL)
335
531
                        keywords = NULL;
336
532
                else if (mailbox_keywords_create(ctx->box, keywords_list,
337
533
                                                 &keywords) < 0) {
 
534
                        /* invalid keywords - delay failure */
338
535
                        client_send_storage_error(cmd, ctx->storage);
339
 
                        return cmd_append_cancel(ctx, nonsync);
 
536
                        ctx->failed = TRUE;
340
537
                }
341
 
        } else {
342
 
                flags = 0;
343
 
                keywords = NULL;
344
538
        }
345
539
 
346
 
        if (internal_date_str == NULL) {
 
540
        if (internal_date_str == NULL || ctx->failed) {
347
541
                /* no time given, default to now. */
348
542
                internal_date = (time_t)-1;
349
543
                timezone_offset = 0;
350
544
        } else if (!imap_parse_datetime(internal_date_str,
351
545
                                        &internal_date, &timezone_offset)) {
352
 
                client_send_tagline(cmd, "BAD Invalid internal date.");
353
 
                return cmd_append_cancel(ctx, nonsync);
 
546
                client_send_command_error(cmd, "Invalid internal date.");
 
547
                if (keywords != NULL)
 
548
                        mailbox_keywords_unref(&keywords);
 
549
                return -1;
354
550
        }
355
551
 
356
552
        if (internal_date != (time_t)-1 &&
360
556
                timezone_offset = 0;
361
557
        }
362
558
 
363
 
        if (ctx->msg_size == 0) {
364
 
                /* no message data, abort */
365
 
                client_send_tagline(cmd, "NO Can't save a zero byte message.");
366
 
                return cmd_append_cancel(ctx, nonsync);
367
 
        }
368
 
 
369
 
        /* save the mail */
370
 
        ctx->input = i_stream_create_limit(client->input, ctx->msg_size);
371
 
        ctx->save_ctx = mailbox_save_alloc(ctx->t);
372
 
        mailbox_save_set_flags(ctx->save_ctx, flags, keywords);
373
 
        mailbox_save_set_received_date(ctx->save_ctx,
374
 
                                       internal_date, timezone_offset);
375
 
        ret = mailbox_save_begin(&ctx->save_ctx, ctx->input);
376
 
 
 
559
        if (cat_list != NULL) {
 
560
                ctx->cat_msg_size = 0;
 
561
                ctx->input = i_stream_create_chain(&ctx->catchain);
 
562
        } else {
 
563
                if (ctx->literal_size == 0) {
 
564
                        /* no message data, abort */
 
565
                        if (!ctx->failed) {
 
566
                                client_send_tagline(cmd,
 
567
                                        "NO Can't save a zero byte message.");
 
568
                                ctx->failed = TRUE;
 
569
                        }
 
570
                        if (!*nonsync_r) {
 
571
                                if (keywords != NULL)
 
572
                                        mailbox_keywords_unref(&keywords);
 
573
                                return -1;
 
574
                        }
 
575
                        /* {0+} used. although there isn't any point in using
 
576
                           MULTIAPPEND here and adding more messages, it is
 
577
                           technically valid so we'll continue parsing.. */
 
578
                }
 
579
                ctx->litinput = i_stream_create_limit(client->input, ctx->literal_size);
 
580
                ctx->input = ctx->litinput;
 
581
                i_stream_ref(ctx->input);
 
582
        }
 
583
        if (ctx->binary_input) {
 
584
                input = i_stream_create_binary_converter(ctx->input);
 
585
                i_stream_unref(&ctx->input);
 
586
                ctx->input = input;
 
587
        }
 
588
 
 
589
        if (!ctx->failed) {
 
590
                /* save the mail */
 
591
                ctx->save_ctx = mailbox_save_alloc(ctx->t);
 
592
                mailbox_save_set_flags(ctx->save_ctx, flags, keywords);
 
593
                mailbox_save_set_received_date(ctx->save_ctx,
 
594
                                               internal_date, timezone_offset);
 
595
                if (mailbox_save_begin(&ctx->save_ctx, ctx->input) < 0) {
 
596
                        /* save initialization failed */
 
597
                        client_send_storage_error(cmd, ctx->storage);
 
598
                        ctx->failed = TRUE;
 
599
                }
 
600
        }
377
601
        if (keywords != NULL)
378
602
                mailbox_keywords_unref(&keywords);
379
 
 
380
 
        if (ret < 0) {
381
 
                /* save initialization failed */
382
 
                client_send_storage_error(cmd, ctx->storage);
383
 
                return cmd_append_cancel(ctx, nonsync);
384
 
        }
385
 
 
386
 
        /* after literal comes CRLF, if we fail make sure we eat it away */
387
 
        client->input_skip_line = TRUE;
388
 
 
389
 
        if (!nonsync) {
390
 
                o_stream_send(client->output, "+ OK\r\n", 6);
391
 
                o_stream_flush(client->output);
392
 
                o_stream_uncork(client->output);
393
 
                o_stream_cork(client->output);
394
 
        }
395
 
 
396
603
        ctx->count++;
 
604
 
 
605
        if (cat_list == NULL) {
 
606
                /* normal APPEND */
 
607
                return 1;
 
608
        } else if (cat_list->type == IMAP_ARG_EOL) {
 
609
                /* zero parts */
 
610
                if (!ctx->failed)
 
611
                        client_send_command_error(cmd, "Empty CATENATE list.");
 
612
                client->input_skip_line = TRUE;
 
613
                return -1;
 
614
        } else if ((ret = cmd_append_catenate(cmd, cat_list, nonsync_r)) < 0) {
 
615
                /* invalid parameters, abort immediately */
 
616
                return -1;
 
617
        } else if (ret == 0) {
 
618
                /* CATENATE consisted only of URLs */
 
619
                return 0;
 
620
        } else {
 
621
                /* TEXT part found from CATENATE */
 
622
                return 1;
 
623
        }
 
624
}
 
625
 
 
626
static bool cmd_append_finish_parsing(struct client_command_context *cmd)
 
627
{
 
628
        struct cmd_append_context *ctx = cmd->context;
 
629
        enum mailbox_sync_flags sync_flags;
 
630
        enum imap_sync_flags imap_flags;
 
631
        struct mail_transaction_commit_changes changes;
 
632
        unsigned int save_count;
 
633
        string_t *msg;
 
634
        int ret;
 
635
 
 
636
        /* eat away the trailing CRLF */
 
637
        cmd->client->input_skip_line = TRUE;
 
638
 
 
639
        if (ctx->failed) {
 
640
                /* we failed earlier, error message is sent */
 
641
                cmd_append_finish(ctx);
 
642
                return TRUE;
 
643
        }
 
644
        if (ctx->count == 0) {
 
645
                client_send_command_error(cmd, "Missing message size.");
 
646
                cmd_append_finish(ctx);
 
647
                return TRUE;
 
648
        }
 
649
 
 
650
        ret = mailbox_transaction_commit_get_changes(&ctx->t, &changes);
 
651
        if (ret < 0) {
 
652
                client_send_storage_error(cmd, ctx->storage);
 
653
                cmd_append_finish(ctx);
 
654
                return TRUE;
 
655
        }
 
656
 
 
657
        msg = t_str_new(256);
 
658
        save_count = seq_range_count(&changes.saved_uids);
 
659
        if (save_count == 0 || changes.no_read_perm) {
 
660
                /* not supported by backend (virtual) */
 
661
                str_append(msg, "OK Append completed.");
 
662
        } else {
 
663
                i_assert(ctx->count == save_count);
 
664
                str_printfa(msg, "OK [APPENDUID %u ",
 
665
                            changes.uid_validity);
 
666
                imap_write_seq_range(msg, &changes.saved_uids);
 
667
                str_append(msg, "] Append completed.");
 
668
        }
 
669
        pool_unref(&changes.pool);
 
670
 
 
671
        if (ctx->box == cmd->client->mailbox) {
 
672
                sync_flags = 0;
 
673
                imap_flags = IMAP_SYNC_FLAG_SAFE;
 
674
        } else {
 
675
                sync_flags = MAILBOX_SYNC_FLAG_FAST;
 
676
                imap_flags = 0;
 
677
        }
 
678
 
 
679
        cmd_append_finish(ctx);
 
680
        return cmd_sync(cmd, sync_flags, imap_flags, str_c(msg));
 
681
}
 
682
 
 
683
static bool cmd_append_args_can_stop(struct cmd_append_context *ctx,
 
684
                                     const struct imap_arg *args,
 
685
                                     bool *last_literal_r)
 
686
{
 
687
        const struct imap_arg *cat_list;
 
688
 
 
689
        *last_literal_r = FALSE;
 
690
        if (args->type == IMAP_ARG_EOL)
 
691
                return TRUE;
 
692
 
 
693
        /* [(flags)] ["internal date"] <message literal> | CATENATE (..) */
 
694
        if (args->type == IMAP_ARG_LIST)
 
695
                args++;
 
696
        if (args->type == IMAP_ARG_STRING)
 
697
                args++;
 
698
 
 
699
        if (args->type == IMAP_ARG_LITERAL_SIZE ||
 
700
            args->type == IMAP_ARG_LITERAL_SIZE_NONSYNC)
 
701
                return TRUE;
 
702
        if (imap_arg_atom_equals(args, "CATENATE") &&
 
703
            imap_arg_get_list(&args[1], &cat_list)) {
 
704
                if (catenate_args_can_stop(ctx, cat_list))
 
705
                        return TRUE;
 
706
                *last_literal_r = TRUE;
 
707
        }
 
708
        return FALSE;
 
709
}
 
710
 
 
711
static bool cmd_append_parse_new_msg(struct client_command_context *cmd)
 
712
{
 
713
        struct client *client = cmd->client;
 
714
        struct cmd_append_context *ctx = cmd->context;
 
715
        const struct imap_arg *args;
 
716
        const char *msg;
 
717
        unsigned int arg_min_count;
 
718
        bool fatal, nonsync, last_literal;
 
719
        int ret;
 
720
 
 
721
        /* this function gets called 1) after parsing APPEND <mailbox> and
 
722
           2) with MULTIAPPEND extension after already saving one or more
 
723
           mails. */
 
724
        if (cmd->cancel) {
 
725
                /* cancel the command immediately (disconnection) */
 
726
                cmd_append_finish(ctx);
 
727
                return TRUE;
 
728
        }
 
729
 
 
730
        /* if error occurs, the CRLF is already read. */
 
731
        client->input_skip_line = FALSE;
 
732
 
 
733
        /* parse the entire line up to the first message literal, or in case
 
734
           the input buffer is full of MULTIAPPEND CATENATE URLs, parse at
 
735
           least until the beginning of the next message */
 
736
        arg_min_count = 0; last_literal = FALSE;
 
737
        do {
 
738
                if (!last_literal)
 
739
                        arg_min_count++;
 
740
                else {
 
741
                        /* we only read the literal size. now we read the
 
742
                           literal itself. */
 
743
                }
 
744
                ret = imap_parser_read_args(ctx->save_parser, arg_min_count,
 
745
                                            IMAP_PARSE_FLAG_LITERAL_SIZE |
 
746
                                            IMAP_PARSE_FLAG_LITERAL8, &args);
 
747
        } while (ret >= (int)arg_min_count &&
 
748
                 !cmd_append_args_can_stop(ctx, args, &last_literal));
 
749
        if (ret == -1) {
 
750
                if (!ctx->failed) {
 
751
                        msg = imap_parser_get_error(ctx->save_parser, &fatal);
 
752
                        if (fatal)
 
753
                                client_disconnect_with_error(client, msg);
 
754
                        else
 
755
                                client_send_command_error(cmd, msg);
 
756
                }
 
757
                cmd_append_finish(ctx);
 
758
                return TRUE;
 
759
        }
 
760
        if (ret < 0) {
 
761
                /* need more data */
 
762
                return FALSE;
 
763
        }
 
764
 
 
765
        if (IMAP_ARG_IS_EOL(args)) {
 
766
                /* last message */
 
767
                return cmd_append_finish_parsing(cmd);
 
768
        }
 
769
 
 
770
        ret = cmd_append_handle_args(cmd, args, &nonsync);
 
771
        if (ret < 0) {
 
772
                /* invalid parameters, abort immediately */
 
773
                cmd_append_finish(ctx);
 
774
                return TRUE;
 
775
        }
 
776
        if (ret == 0) {
 
777
                /* CATENATE contained only URLs. Finish it and see if there
 
778
                   are more messsages. */
 
779
                cmd_append_finish_catenate(cmd);
 
780
                imap_parser_reset(ctx->save_parser);
 
781
                return cmd_append_parse_new_msg(cmd);
 
782
        }
 
783
 
 
784
        if (!nonsync) {
 
785
                if (!cmd_append_send_literal_continue(ctx)) {
 
786
                        cmd_append_finish(ctx);
 
787
                        return TRUE;
 
788
                }
 
789
        }
 
790
 
 
791
        i_assert(ctx->litinput != NULL);
397
792
        ctx->message_input = TRUE;
398
793
        cmd->func = cmd_append_continue_message;
399
794
        return cmd_append_continue_message(cmd);
403
798
{
404
799
        struct client *client = cmd->client;
405
800
        struct cmd_append_context *ctx = cmd->context;
406
 
        size_t size;
407
 
        int ret;
 
801
        int ret = 0;
408
802
 
409
803
        if (cmd->cancel) {
 
804
                /* cancel the command immediately (disconnection) */
410
805
                cmd_append_finish(ctx);
411
806
                return TRUE;
412
807
        }
413
808
 
414
809
        if (ctx->save_ctx != NULL) {
415
 
                while (ctx->input->v_offset != ctx->msg_size) {
416
 
                        ret = i_stream_read(ctx->input);
 
810
                while (ctx->litinput->v_offset != ctx->literal_size) {
 
811
                        ret = i_stream_read(ctx->litinput);
417
812
                        if (mailbox_save_continue(ctx->save_ctx) < 0) {
418
813
                                /* we still have to finish reading the message
419
814
                                   from client */
426
821
        }
427
822
 
428
823
        if (ctx->save_ctx == NULL) {
 
824
                /* saving has already failed, we're just eating away the
 
825
                   literal */
 
826
                (void)i_stream_read(ctx->litinput);
 
827
                i_stream_skip(ctx->litinput,
 
828
                              i_stream_get_data_size(ctx->litinput));
 
829
        }
 
830
 
 
831
        if (ctx->litinput->eof || client->input->closed) {
 
832
                uoff_t lit_offset = ctx->litinput->v_offset;
 
833
 
 
834
                /* finished - do one more read, to make sure istream-chain
 
835
                   unreferences its stream, which is needed for litinput's
 
836
                   unreferencing to seek the client->input to correct
 
837
                   position. the seek is needed to avoid trying to seek
 
838
                   backwards in the ctx->input's parent stream. */
 
839
                i_stream_seek(ctx->input, ctx->input->v_offset);
429
840
                (void)i_stream_read(ctx->input);
430
 
                (void)i_stream_get_data(ctx->input, &size);
431
 
                i_stream_skip(ctx->input, size);
432
 
        }
433
 
 
434
 
        if (ctx->input->eof || client->input->closed) {
435
 
                bool all_written = ctx->input->v_offset == ctx->msg_size;
436
 
 
437
 
                /* finished */
438
 
                i_stream_unref(&ctx->input);
439
 
                ctx->input = NULL;
440
 
 
441
 
                if (ctx->save_ctx == NULL) {
 
841
                i_stream_unref(&ctx->litinput);
 
842
 
 
843
                if (ctx->failed) {
 
844
                        if (ctx->save_ctx != NULL)
 
845
                                mailbox_save_cancel(&ctx->save_ctx);
 
846
                } else if (ctx->save_ctx == NULL) {
442
847
                        /* failed above */
443
848
                        client_send_storage_error(cmd, ctx->storage);
444
849
                        ctx->failed = TRUE;
445
 
                } else if (!all_written) {
 
850
                } else if (lit_offset != ctx->literal_size) {
446
851
                        /* client disconnected before it finished sending the
447
852
                           whole message. */
448
853
                        ctx->failed = TRUE;
449
854
                        mailbox_save_cancel(&ctx->save_ctx);
450
 
                        client_disconnect(client, "EOF while appending");
 
855
                        client_disconnect(client,
 
856
                                get_disconnect_reason(ctx, lit_offset));
 
857
                } else if (ctx->catenate) {
 
858
                        /* CATENATE isn't finished yet */
451
859
                } else if (mailbox_save_finish(&ctx->save_ctx) < 0) {
 
860
                        client_send_storage_error(cmd, ctx->storage);
452
861
                        ctx->failed = TRUE;
453
 
                        client_send_storage_error(cmd, ctx->storage);
454
862
                }
455
 
                ctx->save_ctx = NULL;
456
863
 
457
864
                if (client->input->closed) {
458
865
                        cmd_append_finish(ctx);
459
866
                        return TRUE;
460
867
                }
461
868
 
462
 
                /* prepare for next message */
 
869
                /* prepare for the next message (or its part with catenate) */
463
870
                ctx->message_input = FALSE;
464
871
                imap_parser_reset(ctx->save_parser);
465
 
                cmd->func = cmd_append_continue_parsing;
466
 
                return cmd_append_continue_parsing(cmd);
 
872
 
 
873
                if (ctx->catenate) {
 
874
                        cmd->func = cmd_append_continue_catenate;
 
875
                        return cmd_append_continue_catenate(cmd);
 
876
                }
 
877
 
 
878
                i_stream_unref(&ctx->input);
 
879
                cmd->func = cmd_append_parse_new_msg;
 
880
                return cmd_append_parse_new_msg(cmd);
467
881
        }
468
 
 
469
882
        return FALSE;
470
883
}
471
884
 
514
927
        ctx->save_parser = imap_parser_create(client->input, client->output,
515
928
                                              client->set->imap_max_line_length);
516
929
 
517
 
        cmd->func = cmd_append_continue_parsing;
 
930
        cmd->func = cmd_append_parse_new_msg;
518
931
        cmd->context = ctx;
519
 
        return cmd_append_continue_parsing(cmd);
 
932
        return cmd_append_parse_new_msg(cmd);
520
933
}