diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-storage/index/index-attachment.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/lib-storage/index/index-attachment.c b/src/lib-storage/index/index-attachment.c new file mode 100644 index 0000000..6e51fab --- /dev/null +++ b/src/lib-storage/index/index-attachment.c @@ -0,0 +1,446 @@ +/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "safe-mkstemp.h" +#include "fs-api.h" +#include "istream.h" +#include "ostream.h" +#include "base64.h" +#include "hash-format.h" +#include "str.h" +#include "message-parser.h" +#include "rfc822-parser.h" +#include "fs-api.h" +#include "istream-fs-file.h" +#include "istream-attachment-connector.h" +#include "istream-attachment-extractor.h" +#include "mail-user.h" +#include "index-mail.h" +#include "index-attachment.h" + +enum mail_attachment_decode_option { + MAIL_ATTACHMENT_DECODE_OPTION_NONE = '-', + MAIL_ATTACHMENT_DECODE_OPTION_BASE64 = 'B', + MAIL_ATTACHMENT_DECODE_OPTION_CRLF = 'C' +}; + +struct mail_save_attachment { + pool_t pool; + struct fs *fs; + struct istream *input; + + struct fs_file *cur_file; + ARRAY_TYPE(mail_attachment_extref) extrefs; +}; + +static const char *index_attachment_dir_get(struct mail_storage *storage) +{ + return mail_user_home_expand(storage->user, + storage->set->mail_attachment_dir); +} + +static bool index_attachment_want(const struct istream_attachment_header *hdr, + void *context) +{ + struct mail_save_context *ctx = context; + struct mail_attachment_part apart; + + i_zero(&apart); + apart.part = hdr->part; + apart.content_type = hdr->content_type; + apart.content_disposition = hdr->content_disposition; + + if (ctx->part_is_attachment != NULL) + return ctx->part_is_attachment(ctx, &apart); + + /* don't treat text/ parts as attachments */ + return hdr->content_type != NULL && + strncasecmp(hdr->content_type, "text/", 5) != 0; +} + +static int index_attachment_open_temp_fd(void *context) +{ + struct mail_save_context *ctx = context; + struct mail_storage *storage = ctx->transaction->box->storage; + string_t *temp_path; + int fd; + + temp_path = t_str_new(256); + mail_user_set_get_temp_prefix(temp_path, storage->user->set); + fd = safe_mkstemp_hostpid(temp_path, 0600, (uid_t)-1, (gid_t)-1); + if (fd == -1) { + mailbox_set_critical(ctx->transaction->box, + "safe_mkstemp(%s) failed: %m", str_c(temp_path)); + return -1; + } + if (unlink(str_c(temp_path)) < 0) { + mailbox_set_critical(ctx->transaction->box, + "unlink(%s) failed: %m", str_c(temp_path)); + i_close_fd(&fd); + return -1; + } + return fd; +} + +static int +index_attachment_open_ostream(struct istream_attachment_info *info, + struct ostream **output_r, + const char **error_r ATTR_UNUSED, void *context) +{ + struct mail_save_context *ctx = context; + struct mail_save_attachment *attach = ctx->data.attach; + struct mail_storage *storage = ctx->transaction->box->storage; + struct mail_attachment_extref *extref; + enum fs_open_flags flags = 0; + const char *attachment_dir, *path, *digest = info->hash; + guid_128_t guid_128; + + i_assert(attach->cur_file == NULL); + + if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER) + flags |= FS_OPEN_FLAG_FSYNC; + + if (strlen(digest) < 4) { + /* make sure we can access first 4 bytes without accessing + out of bounds memory */ + digest = t_strconcat(digest, "\0\0\0\0", NULL); + } + + guid_128_generate(guid_128); + attachment_dir = index_attachment_dir_get(storage); + path = t_strdup_printf("%s/%c%c/%c%c/%s-%s", attachment_dir, + digest[0], digest[1], + digest[2], digest[3], digest, + guid_128_to_string(guid_128)); + attach->cur_file = fs_file_init(attach->fs, path, + FS_OPEN_MODE_REPLACE | flags); + + extref = array_append_space(&attach->extrefs); + extref->start_offset = info->start_offset; + extref->size = info->encoded_size; + extref->path = p_strdup(attach->pool, + path + strlen(attachment_dir) + 1); + extref->base64_blocks_per_line = info->base64_blocks_per_line; + extref->base64_have_crlf = info->base64_have_crlf; + + *output_r = fs_write_stream(attach->cur_file); + return 0; +} + +static int +index_attachment_close_ostream(struct ostream *output, bool success, + const char **error, void *context) +{ + struct mail_save_context *ctx = context; + struct mail_save_attachment *attach = ctx->data.attach; + int ret = success ? 0 : -1; + + i_assert(attach->cur_file != NULL); + + if (ret < 0) + fs_write_stream_abort_error(attach->cur_file, &output, "%s", *error); + else if (fs_write_stream_finish(attach->cur_file, &output) < 0) { + *error = t_strdup_printf("Couldn't create attachment %s: %s", + fs_file_path(attach->cur_file), + fs_file_last_error(attach->cur_file)); + ret = -1; + } + fs_file_deinit(&attach->cur_file); + + if (ret < 0) { + array_pop_back(&attach->extrefs); + } + return ret; +} + +void index_attachment_save_begin(struct mail_save_context *ctx, + struct fs *fs, struct istream *input) +{ + struct mail_storage *storage = ctx->transaction->box->storage; + struct mail_save_attachment *attach; + struct istream_attachment_settings set; + const char *error; + pool_t pool; + + i_assert(ctx->data.attach == NULL); + + if (*storage->set->mail_attachment_dir == '\0') + return; + + i_zero(&set); + set.min_size = storage->set->mail_attachment_min_size; + if (hash_format_init(storage->set->mail_attachment_hash, + &set.hash_format, &error) < 0) { + /* we already checked this when verifying settings */ + i_panic("mail_attachment_hash=%s unexpectedly failed: %s", + storage->set->mail_attachment_hash, error); + } + set.want_attachment = index_attachment_want; + set.open_temp_fd = index_attachment_open_temp_fd; + set.open_attachment_ostream = index_attachment_open_ostream; + set.close_attachment_ostream = index_attachment_close_ostream; + + pool = pool_alloconly_create("save attachment", 1024); + attach = p_new(pool, struct mail_save_attachment, 1); + attach->pool = pool; + attach->fs = fs; + attach->input = i_stream_create_attachment_extractor(input, &set, ctx); + p_array_init(&attach->extrefs, attach->pool, 8); + ctx->data.attach = attach; +} + +static int save_check_write_error(struct mail_save_context *ctx, + struct ostream *output) +{ + struct mail_storage *storage = ctx->transaction->box->storage; + + if (output->stream_errno == 0) + return 0; + + if (!mail_storage_set_error_from_errno(storage)) { + mail_set_critical(ctx->dest_mail, "write(%s) failed: %s", + o_stream_get_name(output), o_stream_get_error(output)); + } + return -1; +} + +int index_attachment_save_continue(struct mail_save_context *ctx) +{ + struct mail_save_attachment *attach = ctx->data.attach; + const unsigned char *data; + size_t size; + ssize_t ret; + + if (attach->input->stream_errno != 0) + return -1; + + do { + ret = i_stream_read(attach->input); + if (ret > 0 || ret == -2) { + data = i_stream_get_data(attach->input, &size); + o_stream_nsend(ctx->data.output, data, size); + i_stream_skip(attach->input, size); + } + index_mail_cache_parse_continue(ctx->dest_mail); + if (ret == 0 && !i_stream_attachment_extractor_can_retry(attach->input)) { + /* need more input */ + return 0; + } + } while (ret != -1); + + if (attach->input->stream_errno != 0) { + mail_set_critical(ctx->dest_mail, "read(%s) failed: %s", + i_stream_get_name(attach->input), + i_stream_get_error(attach->input)); + return -1; + } + if (ctx->data.output != NULL) { + if (save_check_write_error(ctx, ctx->data.output) < 0) + return -1; + } + return 0; +} + +int index_attachment_save_finish(struct mail_save_context *ctx) +{ + struct mail_save_attachment *attach = ctx->data.attach; + + (void)i_stream_read(attach->input); + i_assert(attach->input->eof); + return attach->input->stream_errno == 0 ? 0 : -1; +} + +void index_attachment_save_free(struct mail_save_context *ctx) +{ + struct mail_save_attachment *attach = ctx->data.attach; + + if (attach != NULL) { + i_stream_unref(&attach->input); + pool_unref(&attach->pool); + ctx->data.attach = NULL; + } +} + +const ARRAY_TYPE(mail_attachment_extref) * +index_attachment_save_get_extrefs(struct mail_save_context *ctx) +{ + return ctx->data.attach == NULL ? NULL : + &ctx->data.attach->extrefs; +} + +static int +index_attachment_delete_real(struct mail_storage *storage, + struct fs *fs, const char *name) +{ + struct fs_file *file; + const char *path; + int ret; + + path = t_strdup_printf("%s/%s", index_attachment_dir_get(storage), name); + file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); + if ((ret = fs_delete(file)) < 0) + mail_storage_set_critical(storage, "%s", fs_file_last_error(file)); + fs_file_deinit(&file); + return ret; +} + +int index_attachment_delete(struct mail_storage *storage, + struct fs *fs, const char *name) +{ + int ret; + + T_BEGIN { + ret = index_attachment_delete_real(storage, fs, name); + } T_END; + return ret; +} + +void index_attachment_append_extrefs(string_t *str, + const ARRAY_TYPE(mail_attachment_extref) *extrefs) +{ + const struct mail_attachment_extref *extref; + bool add_space = FALSE; + unsigned int startpos; + + array_foreach(extrefs, extref) { + if (!add_space) + add_space = TRUE; + else + str_append_c(str, ' '); + str_printfa(str, "%"PRIuUOFF_T" %"PRIuUOFF_T" ", + extref->start_offset, extref->size); + + startpos = str_len(str); + if (extref->base64_have_crlf) + str_append_c(str, MAIL_ATTACHMENT_DECODE_OPTION_CRLF); + if (extref->base64_blocks_per_line > 0) { + str_printfa(str, "%c%u", + MAIL_ATTACHMENT_DECODE_OPTION_BASE64, + extref->base64_blocks_per_line * 4); + } + if (startpos == str_len(str)) { + /* make it clear there are no options */ + str_append_c(str, MAIL_ATTACHMENT_DECODE_OPTION_NONE); + } + str_append_c(str, ' '); + str_append(str, extref->path); + } +} + +static bool +parse_extref_decode_options(const char *str, + struct mail_attachment_extref *extref) +{ + unsigned int num; + + if (*str == MAIL_ATTACHMENT_DECODE_OPTION_NONE) + return str[1] == '\0'; + + while (*str != '\0') { + switch (*str) { + case MAIL_ATTACHMENT_DECODE_OPTION_BASE64: + str++; num = 0; + while (*str >= '0' && *str <= '9') { + num = num*10 + (*str-'0'); + str++; + } + if (num == 0 || num % 4 != 0) + return FALSE; + + extref->base64_blocks_per_line = num/4; + break; + case MAIL_ATTACHMENT_DECODE_OPTION_CRLF: + extref->base64_have_crlf = TRUE; + str++; + break; + default: + return FALSE; + } + } + return TRUE; +} + +bool index_attachment_parse_extrefs(const char *line, pool_t pool, + ARRAY_TYPE(mail_attachment_extref) *extrefs) +{ + struct mail_attachment_extref extref; + const char *const *args; + unsigned int i, len; + uoff_t last_voffset; + + args = t_strsplit(line, " "); + len = str_array_length(args); + if ((len % 4) != 0) + return FALSE; + + last_voffset = 0; + for (i = 0; args[i] != NULL; i += 4) { + const char *start_offset_str = args[i+0]; + const char *size_str = args[i+1]; + const char *decode_options = args[i+2]; + const char *path = args[i+3]; + + i_zero(&extref); + if (str_to_uoff(start_offset_str, &extref.start_offset) < 0 || + str_to_uoff(size_str, &extref.size) < 0 || + extref.start_offset < last_voffset || + !parse_extref_decode_options(decode_options, &extref)) + return FALSE; + + last_voffset += extref.size + + (extref.start_offset - last_voffset); + + extref.path = p_strdup(pool, path); + array_push_back(extrefs, &extref); + } + return TRUE; +} + +int index_attachment_stream_get(struct fs *fs, const char *attachment_dir, + const char *path_suffix, + struct istream **stream, uoff_t full_size, + const char *ext_refs, const char **error_r) +{ + ARRAY_TYPE(mail_attachment_extref) extrefs_arr; + const struct mail_attachment_extref *extref; + struct istream_attachment_connector *conn; + struct istream *input; + struct fs_file *file; + const char *path; + int ret; + + *error_r = NULL; + + t_array_init(&extrefs_arr, 16); + if (!index_attachment_parse_extrefs(ext_refs, pool_datastack_create(), + &extrefs_arr)) { + *error_r = "Broken ext-refs string"; + return -1; + } + conn = istream_attachment_connector_begin(*stream, full_size); + + array_foreach(&extrefs_arr, extref) { + path = t_strdup_printf("%s/%s%s", attachment_dir, + extref->path, path_suffix); + file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY | + FS_OPEN_FLAG_SEEKABLE); + input = i_stream_create_fs_file(&file, IO_BLOCK_SIZE); + + ret = istream_attachment_connector_add(conn, input, + extref->start_offset, extref->size, + extref->base64_blocks_per_line, + extref->base64_have_crlf, error_r); + i_stream_unref(&input); + if (ret < 0) { + istream_attachment_connector_abort(&conn); + return -1; + } + } + + input = istream_attachment_connector_finish(&conn); + i_stream_set_name(input, t_strdup_printf( + "attachments-connector(%s)", i_stream_get_name(*stream))); + i_stream_unref(stream); + *stream = input; + return 0; +} |