summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/index-attachment.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/index-attachment.c')
-rw-r--r--src/lib-storage/index/index-attachment.c446
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;
+}