/src/dovecot/src/lib-storage/index/index-attachment.c
Line | Count | Source |
1 | | /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "safe-mkstemp.h" |
5 | | #include "istream.h" |
6 | | #include "ostream.h" |
7 | | #include "base64.h" |
8 | | #include "hash-format.h" |
9 | | #include "str.h" |
10 | | #include "message-parser.h" |
11 | | #include "rfc822-parser.h" |
12 | | #include "fs-api.h" |
13 | | #include "istream-fs-file.h" |
14 | | #include "istream-attachment-connector.h" |
15 | | #include "istream-attachment-extractor.h" |
16 | | #include "mail-user.h" |
17 | | #include "index-mail.h" |
18 | | #include "index-attachment.h" |
19 | | |
20 | | enum mail_attachment_decode_option { |
21 | | MAIL_ATTACHMENT_DECODE_OPTION_NONE = '-', |
22 | | MAIL_ATTACHMENT_DECODE_OPTION_BASE64 = 'B', |
23 | | MAIL_ATTACHMENT_DECODE_OPTION_CRLF = 'C' |
24 | | }; |
25 | | |
26 | | struct mail_save_attachment { |
27 | | pool_t pool; |
28 | | struct fs *fs; |
29 | | struct istream *input; |
30 | | |
31 | | struct fs_file *cur_file; |
32 | | ARRAY_TYPE(mail_attachment_extref) extrefs; |
33 | | }; |
34 | | |
35 | | static bool index_attachment_want(const struct istream_attachment_header *hdr, |
36 | | void *context) |
37 | 0 | { |
38 | 0 | struct mail_save_context *ctx = context; |
39 | 0 | struct mail_attachment_part apart; |
40 | |
|
41 | 0 | i_zero(&apart); |
42 | 0 | apart.part = hdr->part; |
43 | 0 | apart.content_type = hdr->content_type; |
44 | 0 | apart.content_disposition = hdr->content_disposition; |
45 | |
|
46 | 0 | if (ctx->part_is_attachment != NULL) |
47 | 0 | return ctx->part_is_attachment(ctx, &apart); |
48 | | |
49 | | /* don't treat text/ parts as attachments */ |
50 | 0 | return hdr->content_type != NULL && |
51 | 0 | !str_begins_icase_with(hdr->content_type, "text/"); |
52 | 0 | } |
53 | | |
54 | | static int index_attachment_open_temp_fd(void *context) |
55 | 0 | { |
56 | 0 | struct mail_save_context *ctx = context; |
57 | 0 | struct mail_storage *storage = ctx->transaction->box->storage; |
58 | 0 | string_t *temp_path; |
59 | 0 | int fd; |
60 | |
|
61 | 0 | temp_path = t_str_new(256); |
62 | 0 | mail_user_set_get_temp_prefix(temp_path, storage->user->set); |
63 | 0 | fd = safe_mkstemp_hostpid(temp_path, 0600, (uid_t)-1, (gid_t)-1); |
64 | 0 | if (fd == -1) { |
65 | 0 | mailbox_set_critical(ctx->transaction->box, |
66 | 0 | "safe_mkstemp(%s) failed: %m", str_c(temp_path)); |
67 | 0 | return -1; |
68 | 0 | } |
69 | 0 | if (unlink(str_c(temp_path)) < 0) { |
70 | 0 | mailbox_set_critical(ctx->transaction->box, |
71 | 0 | "unlink(%s) failed: %m", str_c(temp_path)); |
72 | 0 | i_close_fd(&fd); |
73 | 0 | return -1; |
74 | 0 | } |
75 | 0 | return fd; |
76 | 0 | } |
77 | | |
78 | | static int |
79 | | index_attachment_open_ostream(struct istream_attachment_info *info, |
80 | | struct ostream **output_r, |
81 | | const char **error_r ATTR_UNUSED, void *context) |
82 | 0 | { |
83 | 0 | struct mail_save_context *ctx = context; |
84 | 0 | struct mail_save_attachment *attach = ctx->data.attach; |
85 | 0 | struct mail_storage *storage = ctx->transaction->box->storage; |
86 | 0 | struct mail_attachment_extref *extref; |
87 | 0 | enum fs_open_flags flags = 0; |
88 | 0 | const char *path, *digest = info->hash; |
89 | 0 | guid_128_t guid_128; |
90 | |
|
91 | 0 | i_assert(attach->cur_file == NULL); |
92 | | |
93 | 0 | if (storage->set->parsed_fsync_mode != FSYNC_MODE_NEVER) |
94 | 0 | flags |= FS_OPEN_FLAG_FSYNC; |
95 | |
|
96 | 0 | if (strlen(digest) < 4) { |
97 | | /* make sure we can access first 4 bytes without accessing |
98 | | out of bounds memory */ |
99 | 0 | digest = t_strconcat(digest, "\0\0\0\0", NULL); |
100 | 0 | } |
101 | |
|
102 | 0 | guid_128_generate(guid_128); |
103 | 0 | path = t_strdup_printf("%s/%c%c/%c%c/%s-%s", |
104 | 0 | storage->set->mail_ext_attachment_path, |
105 | 0 | digest[0], digest[1], |
106 | 0 | digest[2], digest[3], digest, |
107 | 0 | guid_128_to_string(guid_128)); |
108 | 0 | attach->cur_file = fs_file_init(attach->fs, path, |
109 | 0 | FS_OPEN_MODE_REPLACE | flags); |
110 | |
|
111 | 0 | extref = array_append_space(&attach->extrefs); |
112 | 0 | extref->start_offset = info->start_offset; |
113 | 0 | extref->size = info->encoded_size; |
114 | 0 | extref->path = p_strdup(attach->pool, path + |
115 | 0 | strlen(storage->set->mail_ext_attachment_path) + 1); |
116 | 0 | extref->base64_blocks_per_line = info->base64_blocks_per_line; |
117 | 0 | extref->base64_have_crlf = info->base64_have_crlf; |
118 | |
|
119 | 0 | *output_r = fs_write_stream(attach->cur_file); |
120 | 0 | return 0; |
121 | 0 | } |
122 | | |
123 | | static int |
124 | | index_attachment_close_ostream(struct ostream *output, bool success, |
125 | | const char **error, void *context) |
126 | 0 | { |
127 | 0 | struct mail_save_context *ctx = context; |
128 | 0 | struct mail_save_attachment *attach = ctx->data.attach; |
129 | 0 | int ret = success ? 0 : -1; |
130 | |
|
131 | 0 | i_assert(attach->cur_file != NULL); |
132 | | |
133 | 0 | if (ret < 0) |
134 | 0 | fs_write_stream_abort_error(attach->cur_file, &output, "%s", *error); |
135 | 0 | else if (fs_write_stream_finish(attach->cur_file, &output) < 0) { |
136 | 0 | *error = t_strdup_printf("Couldn't create attachment %s: %s", |
137 | 0 | fs_file_path(attach->cur_file), |
138 | 0 | fs_file_last_error(attach->cur_file)); |
139 | 0 | ret = -1; |
140 | 0 | } |
141 | 0 | fs_file_deinit(&attach->cur_file); |
142 | |
|
143 | 0 | if (ret < 0) { |
144 | 0 | array_pop_back(&attach->extrefs); |
145 | 0 | } |
146 | 0 | return ret; |
147 | 0 | } |
148 | | |
149 | | void index_attachment_save_begin(struct mail_save_context *ctx, |
150 | | struct fs *fs, struct istream *input) |
151 | 0 | { |
152 | 0 | struct mail_storage *storage = ctx->transaction->box->storage; |
153 | 0 | struct mail_save_attachment *attach; |
154 | 0 | struct istream_attachment_settings set; |
155 | 0 | const char *error; |
156 | 0 | pool_t pool; |
157 | |
|
158 | 0 | i_assert(ctx->data.attach == NULL); |
159 | | |
160 | 0 | if (*storage->set->mail_ext_attachment_path == '\0') |
161 | 0 | return; |
162 | | |
163 | 0 | i_zero(&set); |
164 | 0 | set.min_size = storage->set->mail_ext_attachment_min_size; |
165 | 0 | if (hash_format_init(storage->set->mail_ext_attachment_hash, |
166 | 0 | &set.hash_format, &error) < 0) { |
167 | | /* we already checked this when verifying settings */ |
168 | 0 | i_panic("mail_attachment_hash=%s unexpectedly failed: %s", |
169 | 0 | storage->set->mail_ext_attachment_hash, error); |
170 | 0 | } |
171 | 0 | set.want_attachment = index_attachment_want; |
172 | 0 | set.open_temp_fd = index_attachment_open_temp_fd; |
173 | 0 | set.open_attachment_ostream = index_attachment_open_ostream; |
174 | 0 | set.close_attachment_ostream = index_attachment_close_ostream; |
175 | |
|
176 | 0 | pool = pool_alloconly_create("save attachment", 1024); |
177 | 0 | attach = p_new(pool, struct mail_save_attachment, 1); |
178 | 0 | attach->pool = pool; |
179 | 0 | attach->fs = fs; |
180 | 0 | attach->input = i_stream_create_attachment_extractor(input, &set, ctx); |
181 | 0 | p_array_init(&attach->extrefs, attach->pool, 8); |
182 | 0 | ctx->data.attach = attach; |
183 | 0 | } |
184 | | |
185 | | static int save_check_write_error(struct mail_save_context *ctx, |
186 | | struct ostream *output) |
187 | 0 | { |
188 | 0 | struct mail_storage *storage = ctx->transaction->box->storage; |
189 | |
|
190 | 0 | if (output->stream_errno == 0) |
191 | 0 | return 0; |
192 | | |
193 | 0 | if (!mail_storage_set_error_from_errno(storage)) { |
194 | 0 | mail_set_critical(ctx->dest_mail, "write(%s) failed: %s", |
195 | 0 | o_stream_get_name(output), o_stream_get_error(output)); |
196 | 0 | } |
197 | 0 | return -1; |
198 | 0 | } |
199 | | |
200 | | int index_attachment_save_continue(struct mail_save_context *ctx) |
201 | 0 | { |
202 | 0 | struct mail_save_attachment *attach = ctx->data.attach; |
203 | 0 | const unsigned char *data; |
204 | 0 | size_t size; |
205 | 0 | ssize_t ret; |
206 | |
|
207 | 0 | if (attach->input->stream_errno != 0) |
208 | 0 | return -1; |
209 | | |
210 | 0 | do { |
211 | 0 | ret = i_stream_read(attach->input); |
212 | 0 | if (ret > 0 || ret == -2) { |
213 | 0 | data = i_stream_get_data(attach->input, &size); |
214 | 0 | o_stream_nsend(ctx->data.output, data, size); |
215 | 0 | i_stream_skip(attach->input, size); |
216 | 0 | } |
217 | 0 | index_mail_cache_parse_continue(ctx->dest_mail); |
218 | 0 | if (ret == 0 && !i_stream_attachment_extractor_can_retry(attach->input)) { |
219 | | /* need more input */ |
220 | 0 | return 0; |
221 | 0 | } |
222 | 0 | } while (ret != -1); |
223 | | |
224 | 0 | if (attach->input->stream_errno != 0) { |
225 | 0 | mail_set_critical(ctx->dest_mail, "read(%s) failed: %s", |
226 | 0 | i_stream_get_name(attach->input), |
227 | 0 | i_stream_get_error(attach->input)); |
228 | 0 | return -1; |
229 | 0 | } |
230 | 0 | if (ctx->data.output != NULL) { |
231 | 0 | if (save_check_write_error(ctx, ctx->data.output) < 0) |
232 | 0 | return -1; |
233 | 0 | } |
234 | 0 | return 0; |
235 | 0 | } |
236 | | |
237 | | int index_attachment_save_finish(struct mail_save_context *ctx) |
238 | 0 | { |
239 | 0 | struct mail_save_attachment *attach = ctx->data.attach; |
240 | |
|
241 | 0 | (void)i_stream_read(attach->input); |
242 | 0 | i_assert(attach->input->eof); |
243 | 0 | return attach->input->stream_errno == 0 ? 0 : -1; |
244 | 0 | } |
245 | | |
246 | | void index_attachment_save_free(struct mail_save_context *ctx) |
247 | 0 | { |
248 | 0 | struct mail_save_attachment *attach = ctx->data.attach; |
249 | |
|
250 | 0 | if (attach != NULL) { |
251 | 0 | i_stream_unref(&attach->input); |
252 | 0 | pool_unref(&attach->pool); |
253 | 0 | ctx->data.attach = NULL; |
254 | 0 | } |
255 | 0 | } |
256 | | |
257 | | const ARRAY_TYPE(mail_attachment_extref) * |
258 | | index_attachment_save_get_extrefs(struct mail_save_context *ctx) |
259 | 0 | { |
260 | 0 | return ctx->data.attach == NULL ? NULL : |
261 | 0 | &ctx->data.attach->extrefs; |
262 | 0 | } |
263 | | |
264 | | static int |
265 | | index_attachment_delete_real(struct mail_storage *storage, |
266 | | struct fs *fs, const char *name) |
267 | 0 | { |
268 | 0 | struct fs_file *file; |
269 | 0 | const char *path; |
270 | 0 | int ret; |
271 | |
|
272 | 0 | path = t_strdup_printf("%s/%s", storage->set->mail_ext_attachment_path, name); |
273 | 0 | file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY); |
274 | 0 | if ((ret = fs_delete(file)) < 0) |
275 | 0 | mail_storage_set_critical(storage, "%s", fs_file_last_error(file)); |
276 | 0 | fs_file_deinit(&file); |
277 | 0 | return ret; |
278 | 0 | } |
279 | | |
280 | | int index_attachment_delete(struct mail_storage *storage, |
281 | | struct fs *fs, const char *name) |
282 | 0 | { |
283 | 0 | int ret; |
284 | |
|
285 | 0 | T_BEGIN { |
286 | 0 | ret = index_attachment_delete_real(storage, fs, name); |
287 | 0 | } T_END; |
288 | 0 | return ret; |
289 | 0 | } |
290 | | |
291 | | void index_attachment_append_extrefs(string_t *str, |
292 | | const ARRAY_TYPE(mail_attachment_extref) *extrefs) |
293 | 0 | { |
294 | 0 | const struct mail_attachment_extref *extref; |
295 | 0 | bool add_space = FALSE; |
296 | 0 | unsigned int startpos; |
297 | |
|
298 | 0 | array_foreach(extrefs, extref) { |
299 | 0 | if (!add_space) |
300 | 0 | add_space = TRUE; |
301 | 0 | else |
302 | 0 | str_append_c(str, ' '); |
303 | 0 | str_printfa(str, "%"PRIuUOFF_T" %"PRIuUOFF_T" ", |
304 | 0 | extref->start_offset, extref->size); |
305 | |
|
306 | 0 | startpos = str_len(str); |
307 | 0 | if (extref->base64_have_crlf) |
308 | 0 | str_append_c(str, MAIL_ATTACHMENT_DECODE_OPTION_CRLF); |
309 | 0 | if (extref->base64_blocks_per_line > 0) { |
310 | 0 | str_printfa(str, "%c%u", |
311 | 0 | MAIL_ATTACHMENT_DECODE_OPTION_BASE64, |
312 | 0 | extref->base64_blocks_per_line * 4); |
313 | 0 | } |
314 | 0 | if (startpos == str_len(str)) { |
315 | | /* make it clear there are no options */ |
316 | 0 | str_append_c(str, MAIL_ATTACHMENT_DECODE_OPTION_NONE); |
317 | 0 | } |
318 | 0 | str_append_c(str, ' '); |
319 | 0 | str_append(str, extref->path); |
320 | 0 | } |
321 | 0 | } |
322 | | |
323 | | static bool |
324 | | parse_extref_decode_options(const char *str, |
325 | | struct mail_attachment_extref *extref) |
326 | 0 | { |
327 | 0 | unsigned int num; |
328 | |
|
329 | 0 | if (*str == MAIL_ATTACHMENT_DECODE_OPTION_NONE) |
330 | 0 | return str[1] == '\0'; |
331 | | |
332 | 0 | while (*str != '\0') { |
333 | 0 | switch (*str) { |
334 | 0 | case MAIL_ATTACHMENT_DECODE_OPTION_BASE64: |
335 | 0 | str++; num = 0; |
336 | 0 | while (*str >= '0' && *str <= '9') { |
337 | 0 | num = num*10 + (*str-'0'); |
338 | 0 | str++; |
339 | 0 | } |
340 | 0 | if (num == 0 || num % 4 != 0) |
341 | 0 | return FALSE; |
342 | | |
343 | 0 | extref->base64_blocks_per_line = num/4; |
344 | 0 | break; |
345 | 0 | case MAIL_ATTACHMENT_DECODE_OPTION_CRLF: |
346 | 0 | extref->base64_have_crlf = TRUE; |
347 | 0 | str++; |
348 | 0 | break; |
349 | 0 | default: |
350 | 0 | return FALSE; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | return TRUE; |
354 | 0 | } |
355 | | |
356 | | bool index_attachment_parse_extrefs(const char *line, pool_t pool, |
357 | | ARRAY_TYPE(mail_attachment_extref) *extrefs) |
358 | 0 | { |
359 | 0 | struct mail_attachment_extref extref; |
360 | 0 | const char *const *args; |
361 | 0 | unsigned int i, len; |
362 | 0 | uoff_t last_voffset; |
363 | |
|
364 | 0 | args = t_strsplit(line, " "); |
365 | 0 | len = str_array_length(args); |
366 | 0 | if ((len % 4) != 0) |
367 | 0 | return FALSE; |
368 | | |
369 | 0 | last_voffset = 0; |
370 | 0 | for (i = 0; args[i] != NULL; i += 4) { |
371 | 0 | const char *start_offset_str = args[i+0]; |
372 | 0 | const char *size_str = args[i+1]; |
373 | 0 | const char *decode_options = args[i+2]; |
374 | 0 | const char *path = args[i+3]; |
375 | |
|
376 | 0 | i_zero(&extref); |
377 | 0 | if (str_to_uoff(start_offset_str, &extref.start_offset) < 0 || |
378 | 0 | str_to_uoff(size_str, &extref.size) < 0 || |
379 | 0 | extref.start_offset < last_voffset || |
380 | 0 | !parse_extref_decode_options(decode_options, &extref)) |
381 | 0 | return FALSE; |
382 | | |
383 | 0 | last_voffset += extref.size + |
384 | 0 | (extref.start_offset - last_voffset); |
385 | |
|
386 | 0 | extref.path = p_strdup(pool, path); |
387 | 0 | array_push_back(extrefs, &extref); |
388 | 0 | } |
389 | 0 | return TRUE; |
390 | 0 | } |
391 | | |
392 | | uoff_t index_attachment_base64_decoded_size(const struct mail_attachment_extref *extref) |
393 | 0 | { |
394 | 0 | uoff_t encoded_size = extref->size; |
395 | 0 | unsigned int nl_size = extref->base64_have_crlf ? 2 : 1; |
396 | 0 | unsigned int line_size = extref->base64_blocks_per_line * 4 + nl_size; |
397 | 0 | uoff_t nl_count = (encoded_size - 1) / line_size; |
398 | 0 | uoff_t nl_bytes = nl_size * nl_count; |
399 | |
|
400 | 0 | i_assert(extref->base64_blocks_per_line > 0); |
401 | 0 | return MAX_BASE64_DECODED_SIZE(encoded_size - nl_bytes); |
402 | 0 | } |
403 | | |
404 | | int index_attachment_stream_get(struct fs *fs, const char *attachment_dir, |
405 | | const char *path_suffix, |
406 | | struct istream **stream, uoff_t full_size, |
407 | | const char *ext_refs, const char **error_r) |
408 | 0 | { |
409 | 0 | ARRAY_TYPE(mail_attachment_extref) extrefs_arr; |
410 | 0 | const struct mail_attachment_extref *extref; |
411 | 0 | struct istream_attachment_connector *conn; |
412 | 0 | struct istream *input; |
413 | 0 | struct fs_file *file; |
414 | 0 | const char *path; |
415 | 0 | int ret; |
416 | |
|
417 | 0 | *error_r = NULL; |
418 | |
|
419 | 0 | t_array_init(&extrefs_arr, 16); |
420 | 0 | if (!index_attachment_parse_extrefs(ext_refs, pool_datastack_create(), |
421 | 0 | &extrefs_arr)) { |
422 | 0 | *error_r = "Broken ext-refs string"; |
423 | 0 | return -1; |
424 | 0 | } |
425 | 0 | conn = istream_attachment_connector_begin(*stream, full_size); |
426 | |
|
427 | 0 | array_foreach(&extrefs_arr, extref) { |
428 | 0 | path = t_strdup_printf("%s/%s%s", attachment_dir, |
429 | 0 | extref->path, path_suffix); |
430 | 0 | file = fs_file_init(fs, path, FS_OPEN_MODE_READONLY | |
431 | 0 | FS_OPEN_FLAG_SEEKABLE); |
432 | 0 | uoff_t raw_size; |
433 | 0 | if (extref->base64_blocks_per_line > 0) |
434 | 0 | raw_size = index_attachment_base64_decoded_size(extref); |
435 | 0 | else |
436 | 0 | raw_size = extref->size; |
437 | 0 | fs_set_metadata(file, FS_METADATA_FILE_SIZE, t_strdup_printf("%"PRIuUOFF_T, raw_size)); |
438 | 0 | input = i_stream_create_fs_file(&file, IO_BLOCK_SIZE); |
439 | |
|
440 | 0 | ret = istream_attachment_connector_add(conn, input, |
441 | 0 | extref->start_offset, extref->size, |
442 | 0 | extref->base64_blocks_per_line, |
443 | 0 | extref->base64_have_crlf, error_r); |
444 | 0 | i_stream_unref(&input); |
445 | 0 | if (ret < 0) { |
446 | 0 | istream_attachment_connector_abort(&conn); |
447 | 0 | return -1; |
448 | 0 | } |
449 | 0 | } |
450 | | |
451 | 0 | input = istream_attachment_connector_finish(&conn); |
452 | 0 | i_stream_set_name(input, t_strdup_printf( |
453 | 0 | "attachments-connector(%s)", i_stream_get_name(*stream))); |
454 | 0 | i_stream_unref(stream); |
455 | 0 | *stream = input; |
456 | 0 | return 0; |
457 | 0 | } |