~ubuntu-branches/ubuntu/trusty/dovecot/trusty-updates

« back to all changes in this revision

Viewing changes to src/lib-mail/istream-attachment-extractor.c

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-01-08 09:35:49 UTC
  • mfrom: (1.15.3) (96.1.1 trusty-proposed)
  • Revision ID: package-import@ubuntu.com-20140108093549-814nkqdcxfbvgktg
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) 2013 Dovecot authors, see the included COPYING file */
 
2
 
 
3
#include "lib.h"
 
4
#include "istream-private.h"
 
5
#include "ostream.h"
 
6
#include "base64.h"
 
7
#include "buffer.h"
 
8
#include "str.h"
 
9
#include "hash-format.h"
 
10
#include "rfc822-parser.h"
 
11
#include "message-parser.h"
 
12
#include "istream-attachment-extractor.h"
 
13
 
 
14
#define BASE64_ATTACHMENT_MAX_EXTRA_BYTES 1024
 
15
 
 
16
enum mail_attachment_state {
 
17
        MAIL_ATTACHMENT_STATE_NO,
 
18
        MAIL_ATTACHMENT_STATE_MAYBE,
 
19
        MAIL_ATTACHMENT_STATE_YES
 
20
};
 
21
 
 
22
enum base64_state {
 
23
        BASE64_STATE_0 = 0,
 
24
        BASE64_STATE_1,
 
25
        BASE64_STATE_2,
 
26
        BASE64_STATE_3,
 
27
        BASE64_STATE_CR,
 
28
        BASE64_STATE_EOB,
 
29
        BASE64_STATE_EOM
 
30
};
 
31
 
 
32
struct attachment_istream_part {
 
33
        char *content_type, *content_disposition;
 
34
        enum mail_attachment_state state;
 
35
        /* start offset of the message part in the original input stream */
 
36
        uoff_t start_offset;
 
37
 
 
38
        /* for saving attachments base64-decoded: */
 
39
        enum base64_state base64_state;
 
40
        unsigned int base64_line_blocks, cur_base64_blocks;
 
41
        uoff_t base64_bytes;
 
42
        bool base64_have_crlf; /* CRLF linefeeds */
 
43
        bool base64_failed;
 
44
 
 
45
        int temp_fd;
 
46
        struct ostream *temp_output;
 
47
        buffer_t *part_buf;
 
48
};
 
49
 
 
50
struct attachment_istream {
 
51
        struct istream_private istream;
 
52
        pool_t pool;
 
53
 
 
54
        struct istream_attachment_settings set;
 
55
        void *context;
 
56
 
 
57
        struct message_parser_ctx *parser;
 
58
        struct message_part *cur_part;
 
59
        struct attachment_istream_part part;
 
60
 
 
61
        bool retry_read;
 
62
        bool failed;
 
63
};
 
64
 
 
65
static void stream_add_data(struct attachment_istream *astream,
 
66
                            const void *data, size_t size)
 
67
{
 
68
        if (size > 0) {
 
69
                memcpy(i_stream_alloc(&astream->istream, size), data, size);
 
70
                astream->istream.pos += size;
 
71
        }
 
72
}
 
73
 
 
74
static void parse_content_type(struct attachment_istream *astream,
 
75
                               const struct message_header_line *hdr)
 
76
{
 
77
        struct rfc822_parser_context parser;
 
78
        string_t *content_type;
 
79
 
 
80
        rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
 
81
        rfc822_skip_lwsp(&parser);
 
82
 
 
83
        T_BEGIN {
 
84
                content_type = t_str_new(64);
 
85
                if (rfc822_parse_content_type(&parser, content_type) >= 0) {
 
86
                        i_free(astream->part.content_type);
 
87
                        astream->part.content_type =
 
88
                                i_strdup(str_c(content_type));
 
89
                }
 
90
        } T_END;
 
91
}
 
92
 
 
93
static void
 
94
parse_content_disposition(struct attachment_istream *astream,
 
95
                          const struct message_header_line *hdr)
 
96
{
 
97
        /* just pass it without parsing to is_attachment() callback */
 
98
        i_free(astream->part.content_disposition);
 
99
        astream->part.content_disposition =
 
100
                i_strndup(hdr->full_value, hdr->full_value_len);
 
101
}
 
102
 
 
103
static void astream_parse_header(struct attachment_istream *astream,
 
104
                                 struct message_header_line *hdr)
 
105
{
 
106
        if (!hdr->continued) {
 
107
                stream_add_data(astream, hdr->name, hdr->name_len);
 
108
                stream_add_data(astream, hdr->middle, hdr->middle_len);
 
109
        }
 
110
        stream_add_data(astream, hdr->value, hdr->value_len);
 
111
        if (!hdr->no_newline) {
 
112
                if (hdr->crlf_newline)
 
113
                        stream_add_data(astream, "\r\n", 2);
 
114
                else
 
115
                        stream_add_data(astream, "\n", 1);
 
116
        }
 
117
 
 
118
        if (hdr->continues) {
 
119
                hdr->use_full_value = TRUE;
 
120
                return;
 
121
        }
 
122
 
 
123
        if (strcasecmp(hdr->name, "Content-Type") == 0)
 
124
                parse_content_type(astream, hdr);
 
125
        else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
 
126
                parse_content_disposition(astream, hdr);
 
127
}
 
128
 
 
129
static bool astream_want_attachment(struct attachment_istream *astream,
 
130
                                    struct message_part *part)
 
131
{
 
132
        struct istream_attachment_header ahdr;
 
133
 
 
134
        if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
 
135
                /* multiparts may contain attachments as children,
 
136
                   but they're never themselves */
 
137
                return FALSE;
 
138
        }
 
139
        if (astream->set.want_attachment == NULL)
 
140
                return TRUE;
 
141
 
 
142
        memset(&ahdr, 0, sizeof(ahdr));
 
143
        ahdr.part = part;
 
144
        ahdr.content_type = astream->part.content_type;
 
145
        ahdr.content_disposition = astream->part.content_disposition;
 
146
        return astream->set.want_attachment(&ahdr, astream->context);
 
147
}
 
148
 
 
149
static int astream_base64_decode_lf(struct attachment_istream_part *part)
 
150
{
 
151
        part->base64_state = BASE64_STATE_0;
 
152
        if (part->cur_base64_blocks < part->base64_line_blocks) {
 
153
                /* last line */
 
154
                part->base64_state = BASE64_STATE_EOM;
 
155
                return 0;
 
156
        } else if (part->base64_line_blocks == 0) {
 
157
                /* first line */
 
158
                if (part->cur_base64_blocks == 0)
 
159
                        return -1;
 
160
                part->base64_line_blocks = part->cur_base64_blocks;
 
161
        } else if (part->cur_base64_blocks == part->base64_line_blocks) {
 
162
                /* line is ok */
 
163
        } else {
 
164
                return -1;
 
165
        }
 
166
        part->cur_base64_blocks = 0;
 
167
        return 1;
 
168
}
 
169
 
 
170
static int
 
171
astream_try_base64_decode_char(struct attachment_istream_part *part,
 
172
                               size_t pos, char chr)
 
173
{
 
174
        switch (part->base64_state) {
 
175
        case BASE64_STATE_0:
 
176
                if (base64_is_valid_char(chr))
 
177
                        part->base64_state++;
 
178
                else if (chr == '\r')
 
179
                        part->base64_state = BASE64_STATE_CR;
 
180
                else if (chr == '\n') {
 
181
                        return astream_base64_decode_lf(part);
 
182
                } else {
 
183
                        return -1;
 
184
                }
 
185
                break;
 
186
        case BASE64_STATE_1:
 
187
                if (!base64_is_valid_char(chr))
 
188
                        return -1;
 
189
                part->base64_state++;
 
190
                break;
 
191
        case BASE64_STATE_2:
 
192
                if (base64_is_valid_char(chr))
 
193
                        part->base64_state++;
 
194
                else if (chr == '=')
 
195
                        part->base64_state = BASE64_STATE_EOB;
 
196
                else
 
197
                        return -1;
 
198
                break;
 
199
        case BASE64_STATE_3:
 
200
                part->base64_bytes = part->temp_output->offset + pos + 1;
 
201
                if (base64_is_valid_char(chr)) {
 
202
                        part->base64_state = BASE64_STATE_0;
 
203
                        part->cur_base64_blocks++;
 
204
                } else if (chr == '=') {
 
205
                        part->base64_state = BASE64_STATE_EOM;
 
206
                        part->cur_base64_blocks++;
 
207
                        return 0;
 
208
                } else {
 
209
                        return -1;
 
210
                }
 
211
                break;
 
212
        case BASE64_STATE_CR:
 
213
                if (chr != '\n')
 
214
                        return -1;
 
215
                part->base64_have_crlf = TRUE;
 
216
                return astream_base64_decode_lf(part);
 
217
        case BASE64_STATE_EOB:
 
218
                if (chr != '=')
 
219
                        return -1;
 
220
 
 
221
                part->base64_bytes = part->temp_output->offset + pos + 1;
 
222
                part->base64_state = BASE64_STATE_EOM;
 
223
                part->cur_base64_blocks++;
 
224
                return 0;
 
225
        case BASE64_STATE_EOM:
 
226
                i_unreached();
 
227
        }
 
228
        return 1;
 
229
}
 
230
 
 
231
static void
 
232
astream_try_base64_decode(struct attachment_istream_part *part,
 
233
                          const unsigned char *data, size_t size)
 
234
{
 
235
        size_t i;
 
236
        int ret;
 
237
 
 
238
        if (part->base64_failed || part->base64_state == BASE64_STATE_EOM)
 
239
                return;
 
240
 
 
241
        for (i = 0; i < size; i++) {
 
242
                ret = astream_try_base64_decode_char(part, i, (char)data[i]);
 
243
                if (ret <= 0) {
 
244
                        if (ret < 0)
 
245
                                part->base64_failed = TRUE;
 
246
                        break;
 
247
                }
 
248
        }
 
249
}
 
250
 
 
251
static int astream_open_output(struct attachment_istream *astream)
 
252
{
 
253
        int fd;
 
254
 
 
255
        i_assert(astream->part.temp_fd == -1);
 
256
 
 
257
        fd = astream->set.open_temp_fd(astream->context);
 
258
        if (fd == -1)
 
259
                return -1;
 
260
 
 
261
        astream->part.temp_fd = fd;
 
262
        astream->part.temp_output = o_stream_create_fd(fd, 0, FALSE);
 
263
        o_stream_cork(astream->part.temp_output);
 
264
        return 0;
 
265
}
 
266
 
 
267
static void astream_add_body(struct attachment_istream *astream,
 
268
                             const struct message_block *block)
 
269
{
 
270
        struct attachment_istream_part *part = &astream->part;
 
271
        buffer_t *part_buf;
 
272
        size_t new_size;
 
273
 
 
274
        switch (part->state) {
 
275
        case MAIL_ATTACHMENT_STATE_NO:
 
276
                stream_add_data(astream, block->data, block->size);
 
277
                break;
 
278
        case MAIL_ATTACHMENT_STATE_MAYBE:
 
279
                /* we'll write data to in-memory buffer until we reach
 
280
                   attachment min_size */
 
281
                if (part->part_buf == NULL) {
 
282
                        part->part_buf =
 
283
                                buffer_create_dynamic(default_pool,
 
284
                                                      astream->set.min_size);
 
285
                }
 
286
                part_buf = part->part_buf;
 
287
                new_size = part_buf->used + block->size;
 
288
                if (new_size < astream->set.min_size) {
 
289
                        buffer_append(part_buf, block->data, block->size);
 
290
                        break;
 
291
                }
 
292
                /* attachment is large enough. we'll first copy the buffered
 
293
                   data from memory to temp file */
 
294
                if (astream_open_output(astream) < 0) {
 
295
                        /* failed, fallback to just saving it inline */
 
296
                        part->state = MAIL_ATTACHMENT_STATE_NO;
 
297
                        stream_add_data(astream, part_buf->data, part_buf->used);
 
298
                        stream_add_data(astream, block->data, block->size);
 
299
                        break;
 
300
                }
 
301
                part->state = MAIL_ATTACHMENT_STATE_YES;
 
302
                astream_try_base64_decode(part, part_buf->data, part_buf->used);
 
303
                hash_format_loop(astream->set.hash_format,
 
304
                                 part_buf->data, part_buf->used);
 
305
                o_stream_nsend(part->temp_output,
 
306
                               part_buf->data, part_buf->used);
 
307
                buffer_set_used_size(part_buf, 0);
 
308
                /* fall through to write the new data to temp file */
 
309
        case MAIL_ATTACHMENT_STATE_YES:
 
310
                astream_try_base64_decode(part, block->data, block->size);
 
311
                hash_format_loop(astream->set.hash_format,
 
312
                                 block->data, block->size);
 
313
                o_stream_nsend(part->temp_output, block->data, block->size);
 
314
                break;
 
315
        }
 
316
}
 
317
 
 
318
static int astream_decode_base64(struct attachment_istream *astream)
 
319
{
 
320
        struct attachment_istream_part *part = &astream->part;
 
321
        buffer_t *extra_buf = NULL;
 
322
        struct istream *input, *base64_input;
 
323
        struct ostream *output;
 
324
        const unsigned char *data;
 
325
        size_t size;
 
326
        ssize_t ret;
 
327
        buffer_t *buf;
 
328
        int outfd;
 
329
        bool failed = FALSE;
 
330
 
 
331
        if (part->base64_bytes < astream->set.min_size ||
 
332
            part->temp_output->offset > part->base64_bytes +
 
333
                                        BASE64_ATTACHMENT_MAX_EXTRA_BYTES) {
 
334
                /* only a small part of the MIME part is base64-encoded. */
 
335
                return -1;
 
336
        }
 
337
 
 
338
        if (part->base64_line_blocks == 0) {
 
339
                /* only one line of base64 */
 
340
                part->base64_line_blocks = part->cur_base64_blocks;
 
341
                i_assert(part->base64_line_blocks > 0);
 
342
        }
 
343
 
 
344
        /* decode base64 data and write it to another temp file */
 
345
        outfd = astream->set.open_temp_fd(astream->context);
 
346
        if (outfd == -1)
 
347
                return -1;
 
348
 
 
349
        buf = buffer_create_dynamic(default_pool, 1024);
 
350
        input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
 
351
        base64_input = i_stream_create_limit(input, part->base64_bytes);
 
352
        output = o_stream_create_fd_file(outfd, 0, FALSE);
 
353
        o_stream_cork(output);
 
354
 
 
355
        hash_format_reset(astream->set.hash_format);
 
356
        while ((ret = i_stream_read(base64_input)) > 0) {
 
357
                data = i_stream_get_data(base64_input, &size);
 
358
                buffer_set_used_size(buf, 0);
 
359
                if (base64_decode(data, size, &size, buf) < 0) {
 
360
                        i_error("istream-attachment: BUG: "
 
361
                                "Attachment base64 data unexpectedly broke");
 
362
                        failed = TRUE;
 
363
                        break;
 
364
                }
 
365
                i_stream_skip(base64_input, size);
 
366
                o_stream_nsend(output, buf->data, buf->used);
 
367
                hash_format_loop(astream->set.hash_format,
 
368
                                 buf->data, buf->used);
 
369
        }
 
370
        if (ret != -1) {
 
371
                i_assert(failed);
 
372
        } else if (base64_input->stream_errno != 0) {
 
373
                i_error("istream-attachment: read(%s) failed: %m",
 
374
                        i_stream_get_name(base64_input));
 
375
                failed = TRUE;
 
376
        }
 
377
        if (o_stream_nfinish(output) < 0) {
 
378
                i_error("istream-attachment: write(%s) failed: %m",
 
379
                        o_stream_get_name(output));
 
380
                failed = TRUE;
 
381
        }
 
382
 
 
383
        buffer_free(&buf);
 
384
        i_stream_unref(&base64_input);
 
385
        o_stream_unref(&output);
 
386
 
 
387
        if (input->v_offset != part->temp_output->offset && !failed) {
 
388
                /* write the rest of the data to the message stream */
 
389
                extra_buf = buffer_create_dynamic(default_pool, 1024);
 
390
                while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) {
 
391
                        buffer_append(extra_buf, data, size);
 
392
                        i_stream_skip(input, size);
 
393
                }
 
394
                i_assert(ret == -1);
 
395
                if (input->stream_errno != 0) {
 
396
                        i_error("istream-attachment: read(%s) failed: %m",
 
397
                                i_stream_get_name(base64_input));
 
398
                        failed = TRUE;
 
399
                }
 
400
        }
 
401
        i_stream_unref(&input);
 
402
 
 
403
        if (failed) {
 
404
                i_close_fd(&outfd);
 
405
                return -1;
 
406
        }
 
407
 
 
408
        /* successfully wrote it. switch to using it. */
 
409
        o_stream_destroy(&part->temp_output);
 
410
        i_close_fd(&part->temp_fd);
 
411
        part->temp_fd = outfd;
 
412
 
 
413
        if (extra_buf != NULL) {
 
414
                stream_add_data(astream, extra_buf->data, extra_buf->used);
 
415
                buffer_free(&extra_buf);
 
416
        }
 
417
        return 0;
 
418
}
 
419
 
 
420
static int
 
421
astream_part_finish(struct attachment_istream *astream, const char **error_r)
 
422
{
 
423
        struct attachment_istream_part *part = &astream->part;
 
424
        struct istream_attachment_info info;
 
425
        struct istream *input;
 
426
        struct ostream *output;
 
427
        string_t *digest_str;
 
428
        const unsigned char *data;
 
429
        size_t size;
 
430
        int ret = 0;
 
431
 
 
432
        if (o_stream_nfinish(part->temp_output) < 0) {
 
433
                *error_r = t_strdup_printf("write(%s) failed: %s",
 
434
                                           o_stream_get_name(part->temp_output),
 
435
                                           o_stream_get_error(part->temp_output));
 
436
                return -1;
 
437
        }
 
438
 
 
439
        memset(&info, 0, sizeof(info));
 
440
        info.start_offset = astream->part.start_offset;
 
441
        /* base64_bytes contains how many valid base64 bytes there are so far.
 
442
           if the base64 ends properly, it'll specify how much of the MIME part
 
443
           is saved as an attachment. the rest of the data (typically
 
444
           linefeeds) is added back to main stream */
 
445
        info.encoded_size = part->base64_bytes;
 
446
        /* get the hash before base64-decoder resets it */
 
447
        digest_str = t_str_new(128);
 
448
        hash_format_write(astream->set.hash_format, digest_str);
 
449
        info.hash = str_c(digest_str);
 
450
 
 
451
        /* if it looks like we can decode base64 without any data loss,
 
452
           do it and write the decoded data to another temp file. */
 
453
        if (!part->base64_failed) {
 
454
                if (part->base64_state == BASE64_STATE_0 &&
 
455
                    part->base64_bytes > 0) {
 
456
                        /* there is no trailing LF or '=' characters,
 
457
                           but it's not completely empty */
 
458
                        part->base64_state = BASE64_STATE_EOM;
 
459
                }
 
460
                if (part->base64_state == BASE64_STATE_EOM) {
 
461
                        /* base64 data looks ok. */
 
462
                        if (astream_decode_base64(astream) < 0)
 
463
                                part->base64_failed = TRUE;
 
464
                } else {
 
465
                        part->base64_failed = TRUE;
 
466
                }
 
467
        }
 
468
 
 
469
        /* open attachment output file */
 
470
        info.part = astream->cur_part;
 
471
        if (!part->base64_failed) {
 
472
                info.base64_blocks_per_line = part->base64_line_blocks;
 
473
                info.base64_have_crlf = part->base64_have_crlf;
 
474
                /* base64-decoder updated the hash, use it */
 
475
                str_truncate(digest_str, 0);
 
476
                hash_format_write(astream->set.hash_format, digest_str);
 
477
                info.hash = str_c(digest_str);
 
478
        } else {
 
479
                /* couldn't decode base64, so write the entire MIME part
 
480
                   as attachment */
 
481
                info.encoded_size = part->temp_output->offset;
 
482
        }
 
483
        if (astream->set.open_attachment_ostream(&info, &output, error_r,
 
484
                                                 astream->context) < 0)
 
485
                return -1;
 
486
 
 
487
        /* copy data to attachment from temp file */
 
488
        input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
 
489
        while (i_stream_read_data(input, &data, &size, 0) > 0) {
 
490
                o_stream_nsend(output, data, size);
 
491
                i_stream_skip(input, size);
 
492
        }
 
493
 
 
494
        if (input->stream_errno != 0) {
 
495
                *error_r = t_strdup_printf("read(%s) failed: %s",
 
496
                        i_stream_get_name(input), i_stream_get_error(input));
 
497
                ret = -1;
 
498
        }
 
499
        i_stream_destroy(&input);
 
500
 
 
501
        if (astream->set.close_attachment_ostream(output, ret == 0, error_r,
 
502
                                                  astream->context) < 0)
 
503
                ret = -1;
 
504
        return ret;
 
505
}
 
506
 
 
507
static void astream_part_reset(struct attachment_istream *astream)
 
508
{
 
509
        struct attachment_istream_part *part = &astream->part;
 
510
 
 
511
        if (part->temp_output != NULL)
 
512
                o_stream_destroy(&part->temp_output);
 
513
        if (part->temp_fd != -1)
 
514
                i_close_fd(&part->temp_fd);
 
515
 
 
516
        i_free_and_null(part->content_type);
 
517
        i_free_and_null(part->content_disposition);
 
518
        if (part->part_buf != NULL)
 
519
                buffer_free(&part->part_buf);
 
520
 
 
521
        memset(part, 0, sizeof(*part));
 
522
        part->temp_fd = -1;
 
523
        hash_format_reset(astream->set.hash_format);
 
524
}
 
525
 
 
526
static int
 
527
astream_end_of_part(struct attachment_istream *astream, const char **error_r)
 
528
{
 
529
        struct attachment_istream_part *part = &astream->part;
 
530
        size_t old_size;
 
531
        int ret = 0;
 
532
 
 
533
        /* MIME part changed. we're now parsing the end of a boundary,
 
534
           possibly followed by message epilogue */
 
535
        switch (part->state) {
 
536
        case MAIL_ATTACHMENT_STATE_NO:
 
537
                break;
 
538
        case MAIL_ATTACHMENT_STATE_MAYBE:
 
539
                /* MIME part wasn't large enough to be an attachment */
 
540
                if (part->part_buf != NULL) {
 
541
                        stream_add_data(astream, part->part_buf->data,
 
542
                                        part->part_buf->used);
 
543
                        ret = part->part_buf->used > 0 ? 1 : 0;
 
544
                }
 
545
                break;
 
546
        case MAIL_ATTACHMENT_STATE_YES:
 
547
                old_size = astream->istream.pos - astream->istream.skip;
 
548
                if (astream_part_finish(astream, error_r) < 0)
 
549
                        ret = -1;
 
550
                else {
 
551
                        /* finished base64 may have added a few more trailing
 
552
                           bytes to the stream */
 
553
                        ret = astream->istream.pos -
 
554
                                astream->istream.skip - old_size;
 
555
                }
 
556
                break;
 
557
        }
 
558
        part->state = MAIL_ATTACHMENT_STATE_NO;
 
559
        astream_part_reset(astream);
 
560
        return ret;
 
561
}
 
562
 
 
563
static int astream_read_next(struct attachment_istream *astream, bool *retry_r)
 
564
{
 
565
        struct istream_private *stream = &astream->istream;
 
566
        struct message_block block;
 
567
        size_t old_size, new_size;
 
568
        const char *error;
 
569
        int ret;
 
570
 
 
571
        *retry_r = FALSE;
 
572
 
 
573
        if (stream->pos - stream->skip >= stream->max_buffer_size)
 
574
                return -2;
 
575
 
 
576
        if (astream->failed) {
 
577
                stream->istream.stream_errno = EINVAL;
 
578
                return -1;
 
579
        }
 
580
 
 
581
        old_size = stream->pos - stream->skip;
 
582
        switch (message_parser_parse_next_block(astream->parser, &block)) {
 
583
        case -1:
 
584
                /* done / error */
 
585
                ret = astream_end_of_part(astream, &error);
 
586
                if (ret > 0) {
 
587
                        /* final data */
 
588
                        new_size = stream->pos - stream->skip;
 
589
                        return new_size - old_size;
 
590
                }
 
591
                stream->istream.eof = TRUE;
 
592
                stream->istream.stream_errno = stream->parent->stream_errno;
 
593
 
 
594
                if (ret < 0) {
 
595
                        io_stream_set_error(&stream->iostream, "%s", error);
 
596
                        stream->istream.stream_errno = EINVAL;
 
597
                        astream->failed = TRUE;
 
598
                }
 
599
                astream->cur_part = NULL;
 
600
                return -1;
 
601
        case 0:
 
602
                /* need more data */
 
603
                return 0;
 
604
        default:
 
605
                break;
 
606
        }
 
607
 
 
608
        if (block.part != astream->cur_part && astream->cur_part != NULL) {
 
609
                /* end of a MIME part */
 
610
                if (astream_end_of_part(astream, &error) < 0) {
 
611
                        io_stream_set_error(&stream->iostream, "%s", error);
 
612
                        stream->istream.stream_errno = EINVAL;
 
613
                        astream->failed = TRUE;
 
614
                        return -1;
 
615
                }
 
616
        }
 
617
        astream->cur_part = block.part;
 
618
 
 
619
        if (block.hdr != NULL) {
 
620
                /* parsing a header */
 
621
                astream_parse_header(astream, block.hdr);
 
622
        } else if (block.size == 0) {
 
623
                /* end of headers */
 
624
                if (astream_want_attachment(astream, block.part)) {
 
625
                        astream->part.state = MAIL_ATTACHMENT_STATE_MAYBE;
 
626
                        astream->part.start_offset = stream->parent->v_offset;
 
627
                }
 
628
        } else {
 
629
                astream_add_body(astream, &block);
 
630
        }
 
631
        new_size = stream->pos - stream->skip;
 
632
        *retry_r = new_size == old_size;
 
633
        return new_size - old_size;
 
634
}
 
635
 
 
636
static ssize_t
 
637
i_stream_attachment_extractor_read(struct istream_private *stream)
 
638
{
 
639
        struct attachment_istream *astream =
 
640
                (struct attachment_istream *)stream;
 
641
        bool retry;
 
642
        ssize_t ret;
 
643
 
 
644
        do {
 
645
                ret = astream_read_next(astream, &retry);
 
646
        } while (retry && astream->set.drain_parent_input);
 
647
 
 
648
        astream->retry_read = retry;
 
649
        return ret;
 
650
}
 
651
 
 
652
static void i_stream_attachment_extractor_close(struct iostream_private *stream,
 
653
                                                bool close_parent)
 
654
{
 
655
        struct attachment_istream *astream =
 
656
                (struct attachment_istream *)stream;
 
657
        struct message_part *parts;
 
658
        int ret;
 
659
 
 
660
        if (astream->parser != NULL) {
 
661
                ret = message_parser_deinit(&astream->parser, &parts);
 
662
                i_assert(ret == 0); /* we didn't use preparsed message_parts */
 
663
        }
 
664
        hash_format_deinit_free(&astream->set.hash_format);
 
665
        if (astream->pool != NULL)
 
666
                pool_unref(&astream->pool);
 
667
        if (close_parent)
 
668
                i_stream_close(astream->istream.parent);
 
669
}
 
670
 
 
671
struct istream *
 
672
i_stream_create_attachment_extractor(struct istream *input,
 
673
                                     struct istream_attachment_settings *set,
 
674
                                     void *context)
 
675
{
 
676
        struct attachment_istream *astream;
 
677
 
 
678
        i_assert(set->min_size > 0);
 
679
        i_assert(set->hash_format != NULL);
 
680
        i_assert(set->open_attachment_ostream != NULL);
 
681
        i_assert(set->close_attachment_ostream != NULL);
 
682
 
 
683
        astream = i_new(struct attachment_istream, 1);
 
684
        astream->part.temp_fd = -1;
 
685
        astream->set = *set;
 
686
        astream->context = context;
 
687
        astream->retry_read = TRUE;
 
688
 
 
689
        /* make sure the caller doesn't try to double-free this */
 
690
        set->hash_format = NULL;
 
691
 
 
692
        astream->istream.max_buffer_size = input->real_stream->max_buffer_size;
 
693
 
 
694
        astream->istream.read = i_stream_attachment_extractor_read;
 
695
        astream->istream.iostream.close = i_stream_attachment_extractor_close;
 
696
 
 
697
        astream->istream.istream.readable_fd = FALSE;
 
698
        astream->istream.istream.blocking = input->blocking;
 
699
        astream->istream.istream.seekable = FALSE;
 
700
 
 
701
        astream->pool = pool_alloconly_create("istream attachment", 1024);
 
702
        astream->parser = message_parser_init(astream->pool, input, 0,
 
703
                                MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
 
704
                                MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES);
 
705
        return i_stream_create(&astream->istream, input,
 
706
                               i_stream_get_fd(input));
 
707
}
 
708
 
 
709
bool i_stream_attachment_extractor_can_retry(struct istream *input)
 
710
{
 
711
        struct attachment_istream *astream =
 
712
                (struct attachment_istream *)input->real_stream;
 
713
 
 
714
        return astream->retry_read;
 
715
}