bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* start offset of the message part in the original input stream */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* for saving attachments base64-decoded: */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen unsigned int base64_line_blocks, cur_base64_blocks;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic void stream_add_data(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen memcpy(i_stream_alloc(&astream->istream, size), data, size);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic void parse_content_type(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
13961419ca9710eb80c254e00510c58c466f3c08Timo Sirainen (void)rfc822_parse_content_type(&parser, content_type);
13961419ca9710eb80c254e00510c58c466f3c08Timo Sirainen astream->part.content_type = i_strdup(str_c(content_type));
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenparse_content_disposition(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* just pass it without parsing to is_attachment() callback */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen i_strndup(hdr->full_value, hdr->full_value_len);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic void astream_parse_header(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, hdr->name, hdr->name_len);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, hdr->middle, hdr->middle_len);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, hdr->value, hdr->value_len);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (strcasecmp(hdr->name, "Content-Type") == 0)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic bool astream_want_attachment(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* multiparts may contain attachments as children,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen but they're never themselves */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen ahdr.content_type = astream->part.content_type;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen ahdr.content_disposition = astream->part.content_disposition;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen return astream->set.want_attachment(&ahdr, astream->context);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic int astream_base64_decode_lf(struct attachment_istream_part *part)
ba8c3c767dad12e9b998613a311c3502f9aaa565Timo Sirainen if (part->base64_have_crlf && part->base64_state != BASE64_STATE_CR) {
ba8c3c767dad12e9b998613a311c3502f9aaa565Timo Sirainen /* mixed LF vs CRLFs */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (part->cur_base64_blocks < part->base64_line_blocks) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* last line */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* first line */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen part->base64_line_blocks = part->cur_base64_blocks;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen } else if (part->cur_base64_blocks == part->base64_line_blocks) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* line is ok */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenastream_try_base64_decode_char(struct attachment_istream_part *part,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen part->base64_bytes = part->temp_output->offset + pos + 1;
dce10c2f67cd90f6d5fef9691d2493fdea5b42a7Timo Sirainen if (part->cur_base64_blocks > part->base64_line_blocks &&
dce10c2f67cd90f6d5fef9691d2493fdea5b42a7Timo Sirainen /* too many blocks */
ba8c3c767dad12e9b998613a311c3502f9aaa565Timo Sirainen /* mixed LF vs CRLFs */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen part->base64_bytes = part->temp_output->offset + pos + 1;
dce10c2f67cd90f6d5fef9691d2493fdea5b42a7Timo Sirainen if (part->cur_base64_blocks > part->base64_line_blocks &&
dce10c2f67cd90f6d5fef9691d2493fdea5b42a7Timo Sirainen /* too many blocks */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenastream_try_base64_decode(struct attachment_istream_part *part,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (part->base64_failed || part->base64_state == BASE64_STATE_EOM)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen for (i = 0; i < size; i++) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen ret = astream_try_base64_decode_char(part, i, (char)data[i]);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic int astream_open_output(struct attachment_istream *astream)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen fd = astream->set.open_temp_fd(astream->context);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi astream->part.temp_output = o_stream_create_fd(fd, 0);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic void astream_add_body(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct attachment_istream_part *part = &astream->part;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, block->data, block->size);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* we'll write data to in-memory buffer until we reach
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen attachment min_size */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen buffer_append(part_buf, block->data, block->size);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* attachment is large enough. we'll first copy the buffered
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen data from memory to temp file */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* failed, fallback to just saving it inline */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, part_buf->data, part_buf->used);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, block->data, block->size);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream_try_base64_decode(part, part_buf->data, part_buf->used);
f784d5bb8edbec88829524135cfa100129f5384dTimo Sirainen /* fall through - write the new data to temp file */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream_try_base64_decode(part, block->data, block->size);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen o_stream_nsend(part->temp_output, block->data, block->size);
f5d8e05492a3d2552b54129344e37ca01d241832Timo Sirainenstatic int astream_decode_base64(struct attachment_istream *astream,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct attachment_istream_part *part = &astream->part;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (part->base64_bytes < astream->set.min_size ||
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen part->temp_output->offset > part->base64_bytes +
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* only a small part of the MIME part is base64-encoded. */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* only one line of base64 */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen part->base64_line_blocks = part->cur_base64_blocks;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* decode base64 data and write it to another temp file */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen outfd = astream->set.open_temp_fd(astream->context);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen buf = buffer_create_dynamic(default_pool, 1024);
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen base64_input = i_stream_create_limit(input, part->base64_bytes);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen output = o_stream_create_fd_file(outfd, 0, FALSE);
9b5fa7fdd9b9f1f61eaddda48036df200fc5e56eTimo Sirainen while ((ret = i_stream_read_bytes(base64_input, &data, &size,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (base64_decode(data, size, &size, buf) < 0) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen "Attachment base64 data unexpectedly broke");
9b5fa7fdd9b9f1f61eaddda48036df200fc5e56eTimo Sirainen bytes_needed = i_stream_get_data_size(base64_input) + 1;
9bc1a7f4aa77af2f78e71be129cefd1406fb5e9dTimo Sirainen i_error("istream-attachment: read(%s) failed: %s",
9bc1a7f4aa77af2f78e71be129cefd1406fb5e9dTimo Sirainen i_error("istream-attachment: write(%s) failed: %s",
9bc1a7f4aa77af2f78e71be129cefd1406fb5e9dTimo Sirainen o_stream_get_name(output), o_stream_get_error(output));
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (input->v_offset != part->temp_output->offset && !failed) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* write the rest of the data to the message stream */
f5d8e05492a3d2552b54129344e37ca01d241832Timo Sirainen *extra_buf_r = buffer_create_dynamic(default_pool, 1024);
3858a7a5da361c35f1e6e50c8e3214dc0cf379d6Phil Carmody while ((ret = i_stream_read_more(input, &data, &size)) > 0) {
9bc1a7f4aa77af2f78e71be129cefd1406fb5e9dTimo Sirainen i_error("istream-attachment: read(%s) failed: %s",
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* successfully wrote it. switch to using it. */
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainenastream_part_finish(struct attachment_istream *astream, const char **error_r)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct attachment_istream_part *part = &astream->part;
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen *error_r = t_strdup_printf("write(%s) failed: %s",
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen info.start_offset = astream->part.start_offset;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* base64_bytes contains how many valid base64 bytes there are so far.
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if the base64 ends properly, it'll specify how much of the MIME part
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen is saved as an attachment. the rest of the data (typically
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen linefeeds) is added back to main stream */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* get the hash before base64-decoder resets it */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen hash_format_write(astream->set.hash_format, digest_str);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* if it looks like we can decode base64 without any data loss,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen do it and write the decoded data to another temp file. */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* there is no trailing LF or '=' characters,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen but it's not completely empty */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* base64 data looks ok. */
f5d8e05492a3d2552b54129344e37ca01d241832Timo Sirainen if (astream_decode_base64(astream, &extra_buf) < 0)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* open attachment output file */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen info.base64_blocks_per_line = part->base64_line_blocks;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen info.base64_have_crlf = part->base64_have_crlf;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* base64-decoder updated the hash, use it */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen hash_format_write(astream->set.hash_format, digest_str);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* couldn't decode base64, so write the entire MIME part
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen as attachment */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen info.encoded_size = part->temp_output->offset;
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen if (astream->set.open_attachment_ostream(&info, &output, error_r,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* copy data to attachment from temp file */
e93184a9055c2530366dfe617e07199603c399ddMartti Rannanjärvi input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE);
3858a7a5da361c35f1e6e50c8e3214dc0cf379d6Phil Carmody while (i_stream_read_more(input, &data, &size) > 0) {
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen *error_r = t_strdup_printf("read(%s) failed: %s",
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen i_stream_get_name(input), i_stream_get_error(input));
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen if (astream->set.close_attachment_ostream(output, ret == 0, error_r,
f5d8e05492a3d2552b54129344e37ca01d241832Timo Sirainen stream_add_data(astream, extra_buf->data, extra_buf->used);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenstatic void astream_part_reset(struct attachment_istream *astream)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct attachment_istream_part *part = &astream->part;
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainenastream_end_of_part(struct attachment_istream *astream, const char **error_r)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct attachment_istream_part *part = &astream->part;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* MIME part changed. we're now parsing the end of a boundary,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen possibly followed by message epilogue */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* MIME part wasn't large enough to be an attachment */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream_add_data(astream, part->part_buf->data,
a3736354b7eb292620cff0fbd8dc8e3ebaf31f4eTimo Sirainen old_size = astream->istream.pos - astream->istream.skip;
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen if (astream_part_finish(astream, error_r) < 0)
a3736354b7eb292620cff0fbd8dc8e3ebaf31f4eTimo Sirainen /* finished base64 may have added a few more trailing
a3736354b7eb292620cff0fbd8dc8e3ebaf31f4eTimo Sirainen bytes to the stream */
edb39c4e2bec31d329816abfd3e2772dd515da07Timo Sirainenstatic int astream_read_next(struct attachment_istream *astream, bool *retry_r)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen struct istream_private *stream = &astream->istream;
0928812e725cd3a4debab2a93d0c9b0436a4de9fTimo Sirainen if (stream->pos - stream->skip >= i_stream_get_max_buffer_size(&stream->istream))
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen switch (message_parser_parse_next_block(astream->parser, &block)) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* done / error */
95ba0bc53d54a31dd74998eb9043799413c71057Timo Sirainen /* final data */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen stream->istream.stream_errno = stream->parent->stream_errno;
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen io_stream_set_error(&stream->iostream, "%s", error);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* need more data */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (block.part != astream->cur_part && astream->cur_part != NULL) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* end of a MIME part */
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen if (astream_end_of_part(astream, &error) < 0) {
0482d891a6669537e10a1abc9866b45f2fc55fccTimo Sirainen io_stream_set_error(&stream->iostream, "%s", error);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* parsing a header */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* end of headers */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen if (astream_want_attachment(astream, block.part)) {
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream->part.state = MAIL_ATTACHMENT_STATE_MAYBE;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream->part.start_offset = stream->parent->v_offset;
edb39c4e2bec31d329816abfd3e2772dd515da07Timo Siraineni_stream_attachment_extractor_read(struct istream_private *stream)
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen } while (retry && astream->set.drain_parent_input);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainenstatic void i_stream_attachment_extractor_close(struct iostream_private *stream,
12e5ac049bd74f8b98d9dc62adcb0bf3217beef6Martti Rannanjärvi message_parser_deinit(&astream->parser, &parts);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen hash_format_deinit_free(&astream->set.hash_format);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Siraineni_stream_create_attachment_extractor(struct istream *input,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen i_assert(set->open_attachment_ostream != NULL);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen i_assert(set->close_attachment_ostream != NULL);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream = i_new(struct attachment_istream, 1);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen /* make sure the caller doesn't try to double-free this */
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream->istream.max_buffer_size = input->real_stream->max_buffer_size;
edb39c4e2bec31d329816abfd3e2772dd515da07Timo Sirainen astream->istream.read = i_stream_attachment_extractor_read;
edb39c4e2bec31d329816abfd3e2772dd515da07Timo Sirainen astream->istream.iostream.close = i_stream_attachment_extractor_close;
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream->istream.istream.blocking = input->blocking;
c6f644533ef64e758fcb654641c668e73e654da7Timo Sirainen astream->pool = pool_alloconly_create("istream attachment", 1024);
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen astream->parser = message_parser_init(astream->pool, input, 0,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS |
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainen return i_stream_create(&astream->istream, input,
8aeae03f9f447c8a792b215c9fb954468053c556Timo Sirainenbool i_stream_attachment_extractor_can_retry(struct istream *input)