index-attachment.c revision 4307c886579381dbb1897ea1388ae6978c96f560
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2010-2012 Dovecot authors, see the included COPYING file */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* start offset of the message part in the original input stream */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* for saving attachments base64-decoded: */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen unsigned int base64_line_blocks, cur_base64_blocks;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* per-MIME part data */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic const char *index_attachment_dir_get(struct mail_storage *storage)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenvoid index_attachment_save_begin(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (*storage->set->mail_attachment_dir == '\0')
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen pool = pool_alloconly_create("save attachment", 1024*4);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen ctx->attach = p_new(pool, struct mail_save_attachment, 1);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen message_parser_init(ctx->attach->pool, input, 0, 0);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen p_array_init(&ctx->attach->extrefs, ctx->attach->pool, 8);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic void parse_content_type(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen rfc822_parser_init(&parser, hdr->full_value, hdr->full_value_len, NULL);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (rfc822_parse_content_type(&parser, content_type) >= 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenparse_content_disposition(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* just pass it as-is to backend. */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen i_free(ctx->attach->part.content_disposition);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen i_strndup(hdr->full_value, hdr->full_value_len);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic void index_attachment_save_mail_header(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (strcasecmp(hdr->name, "Content-Type") == 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen else if (strcasecmp(hdr->name, "Content-Disposition") == 0)
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, hdr->name, hdr->name_len);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, hdr->middle, hdr->middle_len);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, hdr->full_value, hdr->full_value_len);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic bool save_is_attachment(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* multiparts may contain attachments as children,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen but they're never themselves */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen apart.content_type = ctx->attach->part.content_type;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen apart.content_disposition = ctx->attach->part.content_disposition;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic int index_attachment_save_temp_open_fd(struct mail_storage *storage)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen mail_user_set_get_temp_prefix(temp_path, storage->user->set);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen fd = safe_mkstemp_hostpid(temp_path, 0600, (uid_t)-1, (gid_t)-1);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "safe_mkstemp(%s) failed: %m", str_c(temp_path));
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic struct hash_format *
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_hash_format_init(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (hash_format_init(storage->set->mail_attachment_hash,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* we already checked this when verifying settings */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen i_panic("mail_attachment_hash=%s unexpectedly failed: %s",
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic int index_attachment_save_temp_open(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen fd = index_attachment_save_temp_open_fd(ctx->transaction->box->storage);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen ctx->attach->part.output = o_stream_create_fd(fd, 0, FALSE);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen ctx->attach->part.part_hash = index_attachment_hash_format_init(ctx);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic int save_check_write_error(struct mail_storage *storage,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (!mail_storage_set_error_from_errno(storage)) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen mail_storage_set_critical(storage, "write(%s) failed: %m",
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic int index_attachment_base64_decode(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_save_attachment_part *part = &ctx->attach->part;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen const unsigned char *data;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (part->base64_bytes < storage->set->mail_attachment_min_size ||
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* only a small part of the MIME part is base64-encoded. */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* only one line of base64 */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen part->base64_line_blocks = part->cur_base64_blocks;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* decode base64 data and write it to another temp file */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen outfd = index_attachment_save_temp_open_fd(storage);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen hash = index_attachment_hash_format_init(ctx);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen buf = buffer_create_dynamic(default_pool, 1024);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen base64_input = i_stream_create_limit(input, part->base64_bytes);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen output = o_stream_create_fd_file(outfd, 0, FALSE);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen while ((ret = i_stream_read(base64_input)) > 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen data = i_stream_get_data(base64_input, &size);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (base64_decode(data, size, &size, buf) < 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "Attachment base64 data unexpectedly broke");
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "read(attachment-temp) failed: %m");
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (save_check_write_error(storage, output) < 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (input->v_offset != part->output->offset && !failed) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* write the rest of the data to the message stream */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen extra_buf = buffer_create_dynamic(default_pool, 1024);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen while ((ret = i_stream_read_data(input, &data, &size, 0)) > 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "read(attachment-temp) failed: %m");
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "close(attachment-temp) failed: %m");
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* successfully wrote it. switch to using it. */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "close(attachment-decoded-temp) failed: %m");
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, extra_buf->data, extra_buf->used);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic int index_attachment_save_finish_part(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_save_attachment_part *part = &ctx->attach->part;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen const unsigned char *data;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen enum fs_open_flags flags = FS_OPEN_FLAG_MKDIR;
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen if (save_check_write_error(storage, part->output) < 0)
928529ce8d2e60f1af53fae095e82e7d5281a7daTimo Sirainen /* there is no trailing LF or '=' characters,
928529ce8d2e60f1af53fae095e82e7d5281a7daTimo Sirainen but it's not completely empty */
928529ce8d2e60f1af53fae095e82e7d5281a7daTimo Sirainen /* base64 data looks ok. */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* open the attachment destination file */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen hash_format_deinit(&part->part_hash, digest_str);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* make sure we can access first 4 bytes without accessing
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen out of bounds memory */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen digest = t_strconcat(digest, "\0\0\0\0", NULL);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen attachment_dir = index_attachment_dir_get(storage);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen path = t_strdup_printf("%s/%c%c/%c%c/%s-%s", attachment_dir,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* copy data to it from temp file */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen input = i_stream_create_fd(part->temp_fd, IO_BLOCK_SIZE, FALSE);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen while (i_stream_read_data(input, &data, &size, 0) > 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "read(%s) failed: %m", i_stream_get_name(input));
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen else if (fs_write_stream_finish(file, &output) < 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen extref = array_append_space(&ctx->attach->extrefs);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen part->base64_failed ? 0 : part->base64_line_blocks;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen extref->base64_have_crlf = part->base64_have_crlf;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_try_base64_decode_char(struct mail_save_attachment_part *part,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* last line */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* first line */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* line is ok */
928529ce8d2e60f1af53fae095e82e7d5281a7daTimo Sirainen part->base64_bytes = part->output->offset + pos + 1;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen part->base64_bytes = part->output->offset + pos + 1;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_try_base64_decode(struct mail_save_attachment_part *part,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (part->base64_failed || part->base64_state == BASE64_STATE_EOM)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen for (i = 0; i < size; i++) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen ret = index_attachment_try_base64_decode_char(part, i,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic void index_attachment_save_body(struct mail_save_context *ctx,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_save_attachment_part *part = &ctx->attach->part;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, block->data, block->size);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (new_size < storage->set->mail_attachment_min_size) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen buffer_append(part_buf, block->data, block->size);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* attachment is large enough. we'll first write it to
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen temp file. */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (index_attachment_save_temp_open(ctx) < 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* failed, fallback to just saving it inline */
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, block->data, block->size);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen index_attachment_try_base64_decode(part, part_buf->data,
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(part->output, part_buf->data, part_buf->used);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* fall through */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen index_attachment_try_base64_decode(part, block->data,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen hash_format_loop(part->part_hash, block->data, block->size);
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(part->output, block->data, block->size);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenstatic void index_attachment_save_close(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_save_attachment_part *part = &ctx->attach->part;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen "close(attachment-temp) failed: %m");
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_save_body_part_changed(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_save_attachment_part *part = &ctx->attach->part;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* body part changed. we're now parsing the end of a
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen boundary, possibly followed by message epilogue */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* body part wasn't large enough. write to main file. */
e2a88d59c0d47d63ce1ad5b1fd95e487124a3fd4Timo Sirainen o_stream_nsend(ctx->output, part->part_buf->data,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (index_attachment_save_finish_part(ctx) < 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenint index_attachment_save_continue(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct mail_storage *storage = ctx->transaction->box->storage;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen struct message_parser_ctx *parser = ctx->attach->parser;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen while ((ret = message_parser_parse_next_block(parser, &block)) > 0) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (index_attachment_save_body_part_changed(ctx) < 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen index_attachment_save_mail_header(ctx, block.hdr);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* end of headers */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen index_mail_cache_parse_continue(ctx->dest_mail);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen mail_storage_set_critical(storage, "read(%s) failed: %m",
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (save_check_write_error(storage, ctx->output) < 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenint index_attachment_save_finish(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (index_attachment_save_body_part_changed(ctx) < 0)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen ret2 = message_parser_deinit(&ctx->attach->parser, &parts);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenvoid index_attachment_save_free(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_save_get_extrefs(struct mail_save_context *ctx)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenindex_attachment_delete_real(struct mail_storage *storage,
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen path = t_strdup_printf("%s/%s", index_attachment_dir_get(storage), name);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen mail_storage_set_critical(storage, "%s", fs_last_error(fs));
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* if the directory is now empty, rmdir it and its parents
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen until it fails */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen attachment_dir = index_attachment_dir_get(storage);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* success, continue to parent */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen } else if (errno == ENOTEMPTY || errno == EEXIST) {
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen /* there are other entries in this directory */
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainenint index_attachment_delete(struct mail_storage *storage,