/src/dovecot/src/lib-storage/index/imapc/imapc-save.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "str.h" |
5 | | #include "istream.h" |
6 | | #include "istream-crlf.h" |
7 | | #include "ostream.h" |
8 | | #include "imap-date.h" |
9 | | #include "imap-util.h" |
10 | | #include "imap-seqset.h" |
11 | | #include "imap-quote.h" |
12 | | #include "index-mail.h" |
13 | | #include "mail-copy.h" |
14 | | #include "mailbox-list-private.h" |
15 | | #include "imapc-msgmap.h" |
16 | | #include "imapc-storage.h" |
17 | | #include "imapc-sync.h" |
18 | | #include "imapc-mail.h" |
19 | | #include "seq-set-builder.h" |
20 | | |
21 | | struct imapc_save_context { |
22 | | struct mail_save_context ctx; |
23 | | |
24 | | struct imapc_mailbox *mbox; |
25 | | struct imapc_mailbox *src_mbox; |
26 | | struct mail_index_transaction *trans; |
27 | | |
28 | | int fd; |
29 | | char *temp_path; |
30 | | struct istream *input; |
31 | | |
32 | | uint32_t dest_uid_validity; |
33 | | ARRAY_TYPE(seq_range) dest_saved_uids; |
34 | | unsigned int save_count; |
35 | | |
36 | | bool failed:1; |
37 | | bool finished:1; |
38 | | }; |
39 | | |
40 | | struct imapc_save_cmd_context { |
41 | | struct imapc_save_context *ctx; |
42 | | int ret; |
43 | | }; |
44 | | |
45 | 0 | #define IMAPC_SAVECTX(s) container_of(s, struct imapc_save_context, ctx) |
46 | 0 | #define IMAPC_SERVER_CMDLINE_MAX_LEN 8000 |
47 | | |
48 | | void imapc_transaction_save_rollback(struct mail_save_context *_ctx); |
49 | | static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox); |
50 | | |
51 | | struct mail_save_context * |
52 | | imapc_save_alloc(struct mailbox_transaction_context *t) |
53 | 0 | { |
54 | 0 | struct imapc_mailbox *mbox = IMAPC_MAILBOX(t->box); |
55 | 0 | struct imapc_save_context *ctx; |
56 | |
|
57 | 0 | i_assert((t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); |
58 | | |
59 | 0 | if (t->save_ctx == NULL) { |
60 | 0 | ctx = i_new(struct imapc_save_context, 1); |
61 | 0 | ctx->ctx.transaction = t; |
62 | 0 | ctx->mbox = mbox; |
63 | 0 | ctx->src_mbox = NULL; |
64 | 0 | ctx->trans = t->itrans; |
65 | 0 | ctx->fd = -1; |
66 | 0 | t->save_ctx = &ctx->ctx; |
67 | 0 | } |
68 | 0 | return t->save_ctx; |
69 | 0 | } |
70 | | |
71 | | int imapc_save_begin(struct mail_save_context *_ctx, struct istream *input) |
72 | 0 | { |
73 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
74 | 0 | const char *path; |
75 | |
|
76 | 0 | i_assert(ctx->fd == -1); |
77 | | |
78 | 0 | if (imapc_storage_client_handle_auth_failure(ctx->mbox->storage->client)) |
79 | 0 | return -1; |
80 | | |
81 | 0 | ctx->fd = imapc_client_create_temp_fd(ctx->mbox->storage->client->client, |
82 | 0 | &path); |
83 | 0 | if (ctx->fd == -1) { |
84 | 0 | mail_set_critical(_ctx->dest_mail, |
85 | 0 | "Couldn't create temp file %s", path); |
86 | 0 | ctx->failed = TRUE; |
87 | 0 | return -1; |
88 | 0 | } |
89 | | /* we may not know the size of the input, or be sure that it contains |
90 | | only CRLFs. so we'll always first write the mail to a temp file and |
91 | | upload it from there to remote server. */ |
92 | 0 | ctx->finished = FALSE; |
93 | 0 | ctx->temp_path = i_strdup(path); |
94 | 0 | ctx->input = i_stream_create_crlf(input); |
95 | 0 | _ctx->data.output = o_stream_create_fd_file(ctx->fd, 0, FALSE); |
96 | 0 | o_stream_cork(_ctx->data.output); |
97 | 0 | return 0; |
98 | 0 | } |
99 | | |
100 | | int imapc_save_continue(struct mail_save_context *_ctx) |
101 | 0 | { |
102 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
103 | |
|
104 | 0 | if (ctx->failed) |
105 | 0 | return -1; |
106 | | |
107 | 0 | if (index_storage_save_continue(_ctx, ctx->input, NULL) < 0) { |
108 | 0 | ctx->failed = TRUE; |
109 | 0 | return -1; |
110 | 0 | } |
111 | 0 | return 0; |
112 | 0 | } |
113 | | |
114 | | static void imapc_save_appenduid(struct imapc_save_context *ctx, |
115 | | const struct imapc_command_reply *reply, |
116 | | uint32_t *uid_r) |
117 | 0 | { |
118 | 0 | const char *const *args; |
119 | 0 | uint32_t uid_validity, dest_uid; |
120 | |
|
121 | 0 | *uid_r = 0; |
122 | | |
123 | | /* <uidvalidity> <dest uid-set> */ |
124 | 0 | args = t_strsplit(reply->resp_text_value, " "); |
125 | 0 | if (str_array_length(args) != 2) |
126 | 0 | return; |
127 | | |
128 | 0 | if (str_to_uint32(args[0], &uid_validity) < 0) |
129 | 0 | return; |
130 | 0 | if (ctx->dest_uid_validity == 0) |
131 | 0 | ctx->dest_uid_validity = uid_validity; |
132 | 0 | else if (ctx->dest_uid_validity != uid_validity) |
133 | 0 | return; |
134 | | |
135 | 0 | if (str_to_uint32(args[1], &dest_uid) == 0) { |
136 | 0 | seq_range_array_add_with_init(&ctx->dest_saved_uids, |
137 | 0 | 32, dest_uid); |
138 | 0 | *uid_r = dest_uid; |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | | static void |
143 | | imapc_save_add_to_index(struct imapc_save_context *ctx, uint32_t uid) |
144 | 0 | { |
145 | 0 | struct mail *_mail = ctx->ctx.dest_mail; |
146 | 0 | struct index_mail *imail = INDEX_MAIL(_mail); |
147 | 0 | uint32_t seq; |
148 | | |
149 | | /* we'll temporarily append messages and at commit time expunge |
150 | | them all, since we can't guarantee that no one else has saved |
151 | | messages to remote server during our transaction */ |
152 | 0 | mail_index_append(ctx->trans, uid, &seq); |
153 | 0 | mail_set_seq_saving(_mail, seq); |
154 | 0 | imail->data.no_caching = TRUE; |
155 | 0 | imail->data.forced_no_caching = TRUE; |
156 | |
|
157 | 0 | if (ctx->fd != -1) { |
158 | 0 | struct imapc_mail *imapc_mail = IMAPC_MAIL(_mail); |
159 | 0 | imail->data.stream = i_stream_create_fd_autoclose(&ctx->fd, 0); |
160 | 0 | imapc_mail->header_fetched = TRUE; |
161 | 0 | imapc_mail->body_fetched = TRUE; |
162 | | /* The saved stream wasn't actually read, but it needs to be |
163 | | set accessed to avoid assert-crash. */ |
164 | 0 | _mail->mail_stream_accessed = TRUE; |
165 | 0 | imapc_mail_init_stream(imapc_mail); |
166 | 0 | } |
167 | |
|
168 | 0 | ctx->save_count++; |
169 | 0 | } |
170 | | |
171 | | static void imapc_save_callback(const struct imapc_command_reply *reply, |
172 | | void *context) |
173 | 0 | { |
174 | 0 | struct imapc_save_cmd_context *ctx = context; |
175 | 0 | uint32_t uid = 0; |
176 | |
|
177 | 0 | if (reply->state == IMAPC_COMMAND_STATE_OK) { |
178 | 0 | if (reply->resp_text_key != NULL && |
179 | 0 | strcasecmp(reply->resp_text_key, "APPENDUID") == 0) |
180 | 0 | imapc_save_appenduid(ctx->ctx, reply, &uid); |
181 | 0 | imapc_save_add_to_index(ctx->ctx, uid); |
182 | 0 | ctx->ret = 0; |
183 | 0 | } else if (imapc_storage_client_handle_auth_failure(ctx->ctx->mbox->storage->client)) { |
184 | 0 | ctx->ret = -1; |
185 | 0 | } else if (reply->state == IMAPC_COMMAND_STATE_NO) { |
186 | 0 | imapc_copy_error_from_reply(ctx->ctx->mbox->storage, |
187 | 0 | MAIL_ERROR_PARAMS, reply); |
188 | 0 | ctx->ret = -1; |
189 | 0 | } else { |
190 | 0 | mailbox_set_critical(&ctx->ctx->mbox->box, |
191 | 0 | "imapc: APPEND failed: %s", reply->text_full); |
192 | 0 | ctx->ret = -1; |
193 | 0 | } |
194 | 0 | imapc_client_stop(ctx->ctx->mbox->storage->client->client); |
195 | 0 | } |
196 | | |
197 | | static void |
198 | | imapc_save_noop_callback(const struct imapc_command_reply *reply ATTR_UNUSED, |
199 | | void *context) |
200 | 0 | { |
201 | 0 | struct imapc_save_cmd_context *ctx = context; |
202 | | |
203 | | /* we don't really care about the reply */ |
204 | 0 | ctx->ret = 0; |
205 | 0 | imapc_client_stop(ctx->ctx->mbox->storage->client->client); |
206 | 0 | } |
207 | | |
208 | | static void |
209 | | imapc_copy_rollback_store_callback(const struct imapc_command_reply *reply ATTR_UNUSED, |
210 | | void *context) |
211 | 0 | { |
212 | 0 | struct imapc_save_context *ctx = context; |
213 | | /* Can't do much about a non successful STORE here */ |
214 | 0 | if (reply->state != IMAPC_COMMAND_STATE_OK) { |
215 | 0 | e_error(ctx->src_mbox->box.event, |
216 | 0 | "imapc: Failed to set \\Deleted flag for rolling back " |
217 | 0 | "failed copy: %s", reply->text_full); |
218 | 0 | ctx->src_mbox->rollback_pending = FALSE; |
219 | 0 | ctx->finished = TRUE; |
220 | 0 | ctx->failed = TRUE; |
221 | 0 | } else { |
222 | 0 | i_assert(ctx->src_mbox->rollback_pending); |
223 | 0 | } |
224 | | /* No need stop the imapc client here there is always an additional |
225 | | expunge callback after this. */ |
226 | 0 | } |
227 | | |
228 | | static void |
229 | | imapc_copy_rollback_expunge_callback(const struct imapc_command_reply *reply ATTR_UNUSED, |
230 | | void *context) |
231 | 0 | { |
232 | 0 | struct imapc_save_context *ctx = context; |
233 | | |
234 | | /* Can't do much about a non successful EXPUNGE here */ |
235 | 0 | if (reply->state != IMAPC_COMMAND_STATE_OK) { |
236 | 0 | e_error(ctx->src_mbox->box.event, |
237 | 0 | "imapc: Failed to expunge messages for rolling back " |
238 | 0 | "failed copy: %s", reply->text_full); |
239 | 0 | ctx->src_mbox->rollback_pending = FALSE; |
240 | 0 | ctx->finished = TRUE; |
241 | 0 | ctx->failed = TRUE; |
242 | 0 | } else { |
243 | 0 | ctx->finished = TRUE; |
244 | 0 | ctx->src_mbox->rollback_pending = FALSE; |
245 | 0 | } |
246 | 0 | imapc_client_stop(ctx->src_mbox->storage->client->client); |
247 | 0 | } |
248 | | |
249 | | static void |
250 | | imapc_append_keywords(string_t *str, struct mail_keywords *kw) |
251 | 0 | { |
252 | 0 | const ARRAY_TYPE(keywords) *kw_arr; |
253 | 0 | const char *kw_str; |
254 | 0 | unsigned int i; |
255 | |
|
256 | 0 | kw_arr = mail_index_get_keywords(kw->index); |
257 | 0 | for (i = 0; i < kw->count; i++) { |
258 | 0 | kw_str = array_idx_elem(kw_arr, kw->idx[i]); |
259 | 0 | if (str_len(str) > 1) |
260 | 0 | str_append_c(str, ' '); |
261 | 0 | str_append(str, kw_str); |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | static int imapc_save_append(struct imapc_save_context *ctx) |
266 | 0 | { |
267 | 0 | struct mail_save_context *_ctx = &ctx->ctx; |
268 | 0 | struct mail_save_data *mdata = &_ctx->data; |
269 | 0 | struct imapc_command *cmd; |
270 | 0 | struct imapc_save_cmd_context sctx; |
271 | 0 | struct istream *input; |
272 | 0 | const char *flags = "", *internaldate = ""; |
273 | |
|
274 | 0 | if (mdata->flags != 0 || mdata->keywords != NULL) { |
275 | 0 | string_t *str = t_str_new(64); |
276 | |
|
277 | 0 | str_append(str, " ("); |
278 | 0 | imap_write_flags(str, mdata->flags & ENUM_NEGATE(MAIL_RECENT), |
279 | 0 | NULL); |
280 | 0 | if (mdata->keywords != NULL) |
281 | 0 | imapc_append_keywords(str, mdata->keywords); |
282 | 0 | str_append_c(str, ')'); |
283 | 0 | flags = str_c(str); |
284 | 0 | } |
285 | 0 | if (mdata->received_date != (time_t)-1) { |
286 | 0 | internaldate = t_strdup_printf(" \"%s\"", |
287 | 0 | imap_to_datetime(mdata->received_date)); |
288 | 0 | } |
289 | |
|
290 | 0 | ctx->mbox->exists_received = FALSE; |
291 | |
|
292 | 0 | input = i_stream_create_fd(ctx->fd, IO_BLOCK_SIZE); |
293 | 0 | sctx.ctx = ctx; |
294 | 0 | sctx.ret = -2; |
295 | 0 | cmd = imapc_client_cmd(ctx->mbox->storage->client->client, |
296 | 0 | imapc_save_callback, &sctx); |
297 | 0 | imapc_command_sendf(cmd, "APPEND %s%1s%1s %p", |
298 | 0 | imapc_mailbox_get_remote_name(ctx->mbox), |
299 | 0 | flags, internaldate, input); |
300 | 0 | i_stream_unref(&input); |
301 | 0 | while (sctx.ret == -2) |
302 | 0 | imapc_mailbox_run(ctx->mbox); |
303 | |
|
304 | 0 | if (sctx.ret == 0 && ctx->mbox->selected && |
305 | 0 | !ctx->mbox->exists_received) { |
306 | | /* e.g. Courier doesn't send EXISTS reply before the tagged |
307 | | APPEND reply. That isn't exactly required by the IMAP RFC, |
308 | | but it makes the behavior better. See if NOOP finds |
309 | | the mail. */ |
310 | 0 | sctx.ret = -2; |
311 | 0 | cmd = imapc_client_cmd(ctx->mbox->storage->client->client, |
312 | 0 | imapc_save_noop_callback, &sctx); |
313 | 0 | imapc_command_set_flags(cmd, IMAPC_COMMAND_FLAG_RETRIABLE); |
314 | 0 | imapc_command_send(cmd, "NOOP"); |
315 | 0 | while (sctx.ret == -2) |
316 | 0 | imapc_mailbox_run(ctx->mbox); |
317 | 0 | } |
318 | 0 | return sctx.ret; |
319 | 0 | } |
320 | | |
321 | | int imapc_save_finish(struct mail_save_context *_ctx) |
322 | 0 | { |
323 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
324 | 0 | struct mail_storage *storage = _ctx->transaction->box->storage; |
325 | |
|
326 | 0 | ctx->finished = TRUE; |
327 | |
|
328 | 0 | if (!ctx->failed) { |
329 | 0 | if (o_stream_finish(_ctx->data.output) < 0) { |
330 | 0 | if (!mail_storage_set_error_from_errno(storage)) { |
331 | 0 | mail_set_critical(_ctx->dest_mail, |
332 | 0 | "write(%s) failed: %s", ctx->temp_path, |
333 | 0 | o_stream_get_error(_ctx->data.output)); |
334 | 0 | } |
335 | 0 | ctx->failed = TRUE; |
336 | 0 | } |
337 | 0 | } |
338 | |
|
339 | 0 | if (!ctx->failed) { |
340 | 0 | if (imapc_save_append(ctx) < 0) |
341 | 0 | ctx->failed = TRUE; |
342 | 0 | } |
343 | |
|
344 | 0 | o_stream_unref(&_ctx->data.output); |
345 | 0 | i_stream_unref(&ctx->input); |
346 | 0 | i_close_fd_path(&ctx->fd, ctx->temp_path); |
347 | 0 | i_free(ctx->temp_path); |
348 | 0 | index_save_context_free(_ctx); |
349 | 0 | return ctx->failed ? -1 : 0; |
350 | 0 | } |
351 | | |
352 | | void imapc_save_cancel(struct mail_save_context *_ctx) |
353 | 0 | { |
354 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
355 | |
|
356 | 0 | ctx->failed = TRUE; |
357 | 0 | (void)imapc_transaction_save_commit_pre(_ctx); |
358 | 0 | (void)imapc_save_finish(_ctx); |
359 | 0 | } |
360 | | |
361 | | static void imapc_copy_bulk_finish(struct imapc_save_context *ctx) |
362 | 0 | { |
363 | 0 | while (ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) |
364 | 0 | imapc_mailbox_run_nofetch(ctx->src_mbox); |
365 | 0 | } |
366 | | |
367 | | int imapc_transaction_save_commit_pre(struct mail_save_context *_ctx) |
368 | 0 | { |
369 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
370 | 0 | struct mail_transaction_commit_changes *changes = |
371 | 0 | _ctx->transaction->changes; |
372 | 0 | uint32_t i, last_seq; |
373 | |
|
374 | 0 | i_assert(ctx->finished || ctx->failed); |
375 | | |
376 | | /* expunge all added messages from index before commit */ |
377 | 0 | last_seq = mail_index_view_get_messages_count(_ctx->transaction->view); |
378 | 0 | if (last_seq == 0) |
379 | 0 | return -1; |
380 | 0 | for (i = 0; i < ctx->save_count; i++) |
381 | 0 | mail_index_expunge(ctx->trans, last_seq - i); |
382 | |
|
383 | 0 | if (!ctx->failed && array_is_created(&ctx->dest_saved_uids)) { |
384 | 0 | changes->uid_validity = ctx->dest_uid_validity; |
385 | 0 | array_append_array(&changes->saved_uids, &ctx->dest_saved_uids); |
386 | 0 | } |
387 | 0 | return 0; |
388 | 0 | } |
389 | | |
390 | | int imapc_transaction_save_commit(struct mailbox_transaction_context *t) |
391 | 0 | { |
392 | 0 | struct imapc_save_context *ctx = NULL; |
393 | 0 | struct imapc_mailbox *src_mbox = NULL; |
394 | |
|
395 | 0 | if (t->save_ctx != NULL) { |
396 | 0 | ctx = IMAPC_SAVECTX(t->save_ctx); |
397 | 0 | src_mbox = ctx->src_mbox; |
398 | 0 | } |
399 | |
|
400 | 0 | if (src_mbox != NULL && src_mbox->pending_copy_request != NULL) { |
401 | | /* If there is still a copy command to send flush it now */ |
402 | 0 | imapc_mail_copy_bulk_flush(src_mbox); |
403 | 0 | imapc_copy_bulk_finish(ctx); |
404 | 0 | } |
405 | |
|
406 | 0 | if (ctx != NULL) |
407 | 0 | return ctx->failed ? -1 : 0; |
408 | 0 | return 0; |
409 | 0 | } |
410 | | |
411 | | void imapc_transaction_save_commit_post(struct mail_save_context *_ctx, |
412 | | struct mail_index_transaction_commit_result *result ATTR_UNUSED) |
413 | 0 | { |
414 | 0 | imapc_transaction_save_rollback(_ctx); |
415 | 0 | } |
416 | | |
417 | | static void |
418 | | imapc_expunge_construct_cmd_str(string_t *store_cmd, |
419 | | string_t *expunge_cmd, |
420 | | string_t *uids) |
421 | 0 | { |
422 | 0 | str_append(store_cmd, "UID STORE "); |
423 | 0 | str_append_str(store_cmd, uids); |
424 | 0 | str_append(store_cmd, " +FLAGS (\\Deleted)"); |
425 | 0 | str_append(expunge_cmd, "UID EXPUNGE "); |
426 | 0 | str_append_str(expunge_cmd, uids); |
427 | | /* Clear already appended uids */ |
428 | 0 | str_truncate(uids, 0); |
429 | 0 | } |
430 | | |
431 | | static void |
432 | | imapc_expunge_send_cmd_str(struct imapc_save_context *ctx, |
433 | | string_t *uids) |
434 | 0 | { |
435 | 0 | struct imapc_command *store_cmd, *expunge_cmd; |
436 | |
|
437 | 0 | string_t *store_cmd_str, *expunge_cmd_str; |
438 | 0 | store_cmd_str = t_str_new(128); |
439 | 0 | expunge_cmd_str = t_str_new(128); |
440 | |
|
441 | 0 | imapc_expunge_construct_cmd_str(store_cmd_str, expunge_cmd_str, uids); |
442 | | /* Make sure line length is less than 8k */ |
443 | 0 | i_assert(str_len(store_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN); |
444 | 0 | i_assert(str_len(expunge_cmd_str) < IMAPC_SERVER_CMDLINE_MAX_LEN); |
445 | | |
446 | 0 | store_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, |
447 | 0 | imapc_copy_rollback_store_callback, |
448 | 0 | ctx); |
449 | 0 | expunge_cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, |
450 | 0 | imapc_copy_rollback_expunge_callback, |
451 | 0 | ctx); |
452 | 0 | ctx->src_mbox->rollback_pending = TRUE; |
453 | 0 | imapc_command_send(store_cmd, str_c(store_cmd_str)); |
454 | 0 | imapc_command_send(expunge_cmd, str_c(expunge_cmd_str)); |
455 | 0 | } |
456 | | |
457 | | static void |
458 | | imapc_rollback_send_expunge(struct imapc_save_context *ctx) |
459 | 0 | { |
460 | 0 | string_t *uids_str; |
461 | 0 | struct seqset_builder *seqset_builder; |
462 | 0 | struct seq_range_iter iter; |
463 | 0 | unsigned int i = 0; |
464 | 0 | uint32_t uid; |
465 | |
|
466 | 0 | if (!array_not_empty(&ctx->src_mbox->copy_rollback_expunge_uids)) |
467 | 0 | return; |
468 | | |
469 | 0 | uids_str = t_str_new(128); |
470 | 0 | seqset_builder = seqset_builder_init(uids_str); |
471 | 0 | seq_range_array_iter_init(&iter, &ctx->src_mbox->copy_rollback_expunge_uids); |
472 | | |
473 | | /* Iterate over all uids that must be rolled back */ |
474 | 0 | while (seq_range_array_iter_nth(&iter, i++, &uid)) { |
475 | | /* Try to add the to the seqset builder while respecting |
476 | | the maximum length of IMAPC_SERVER_CMDLINE_MAX_LEN. */ |
477 | 0 | if (!seqset_builder_try_add(seqset_builder, |
478 | 0 | IMAPC_SERVER_CMDLINE_MAX_LEN - |
479 | 0 | strlen("UID STORE +FLAGS (\\Deleted)"), |
480 | 0 | uid)) { |
481 | | /* Maximum length is reached send the rollback |
482 | | and wait for it to be finished. */ |
483 | 0 | seqset_builder_deinit(&seqset_builder); |
484 | 0 | imapc_expunge_send_cmd_str(ctx, uids_str); |
485 | 0 | while (ctx->src_mbox->rollback_pending) |
486 | 0 | imapc_mailbox_run_nofetch(ctx->src_mbox); |
487 | | |
488 | | /* Truncate the uids_str and create a new |
489 | | seqset_builder for the next command */ |
490 | 0 | str_truncate(uids_str, 0); |
491 | 0 | seqset_builder = seqset_builder_init(uids_str); |
492 | | /* Make sure the current uid which is part of |
493 | | the next uid_str */ |
494 | 0 | seqset_builder_add(seqset_builder, uid); |
495 | 0 | } |
496 | 0 | } |
497 | 0 | seqset_builder_deinit(&seqset_builder); |
498 | 0 | if (str_len(uids_str) > 0) |
499 | 0 | imapc_expunge_send_cmd_str(ctx, uids_str); |
500 | 0 | while (ctx->src_mbox->rollback_pending) |
501 | 0 | imapc_mailbox_run_nofetch(ctx->src_mbox); |
502 | 0 | } |
503 | | |
504 | | static void imapc_copy_bulk_ctx_deinit(struct imapc_save_context *ctx) |
505 | 0 | { |
506 | | /* Clean up the pending copy and the context attached to it */ |
507 | 0 | str_truncate(ctx->src_mbox->pending_copy_cmd, 0); |
508 | 0 | i_free(ctx->src_mbox->copy_dest_box); |
509 | 0 | } |
510 | | |
511 | | void imapc_transaction_save_rollback(struct mail_save_context *_ctx) |
512 | 0 | { |
513 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
514 | |
|
515 | 0 | if ((ctx->src_mbox != NULL && ctx->src_mbox->pending_copy_request != NULL) || |
516 | 0 | !ctx->finished) { |
517 | | /* There is still a pending copy which should not be send |
518 | | as rollback() is called or the transaction has not yet |
519 | | finished and rollback is called */ |
520 | 0 | ctx->failed = TRUE; |
521 | 0 | (void)imapc_transaction_save_commit_pre(_ctx); |
522 | |
|
523 | 0 | i_assert(ctx->finished || ctx->src_mbox != NULL); |
524 | | /* Clean up the pending copy and the context attached to it */ |
525 | 0 | if (ctx->src_mbox != NULL) { |
526 | 0 | if (ctx->src_mbox->pending_copy_request != NULL) { |
527 | 0 | seqset_builder_deinit(&ctx->src_mbox->pending_copy_request->uidset_builder); |
528 | 0 | i_free(ctx->src_mbox->pending_copy_request); |
529 | 0 | } |
530 | 0 | imapc_copy_bulk_ctx_deinit(ctx); |
531 | 0 | imapc_client_stop(ctx->src_mbox->storage->client->client); |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | | /* Expunge all added messages from index */ |
536 | 0 | if (ctx->failed && array_is_created(&ctx->dest_saved_uids)) { |
537 | 0 | i_assert(ctx->src_mbox != NULL); |
538 | 0 | seq_range_array_merge(&ctx->src_mbox->copy_rollback_expunge_uids, &ctx->dest_saved_uids); |
539 | | /* Make sure context is not finished already */ |
540 | 0 | ctx->finished = FALSE; |
541 | 0 | imapc_rollback_send_expunge(ctx); |
542 | 0 | array_free(&ctx->dest_saved_uids); |
543 | 0 | } |
544 | | |
545 | 0 | if (ctx->finished || ctx->failed) { |
546 | 0 | array_free(&ctx->dest_saved_uids); |
547 | 0 | i_free(ctx); |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | | static bool imapc_save_copyuid(struct imapc_save_context *ctx, |
552 | | const struct imapc_command_reply *reply, |
553 | | uint32_t *uid_r) |
554 | 0 | { |
555 | 0 | ARRAY_TYPE(seq_range) dest_uidset, source_uidset; |
556 | 0 | struct seq_range_iter iter; |
557 | 0 | const char *const *args; |
558 | 0 | uint32_t uid_validity; |
559 | |
|
560 | 0 | *uid_r = 0; |
561 | | |
562 | | /* <uidvalidity> <source uid-set> <dest uid-set> */ |
563 | 0 | args = t_strsplit(reply->resp_text_value, " "); |
564 | 0 | if (str_array_length(args) != 3) |
565 | 0 | return FALSE; |
566 | | |
567 | 0 | if (str_to_uint32(args[0], &uid_validity) < 0) |
568 | 0 | return FALSE; |
569 | 0 | if (ctx->dest_uid_validity == 0) |
570 | 0 | ctx->dest_uid_validity = uid_validity; |
571 | 0 | else if (ctx->dest_uid_validity != uid_validity) |
572 | 0 | return FALSE; |
573 | | |
574 | 0 | t_array_init(&source_uidset, 8); |
575 | 0 | t_array_init(&dest_uidset, 8); |
576 | |
|
577 | 0 | if (imap_seq_set_nostar_parse(args[1], &source_uidset) < 0) |
578 | 0 | return FALSE; |
579 | 0 | if (imap_seq_set_nostar_parse(args[2], &dest_uidset) < 0) |
580 | 0 | return FALSE; |
581 | | |
582 | 0 | if (!array_is_created(&ctx->dest_saved_uids)) |
583 | 0 | i_array_init(&ctx->dest_saved_uids, 8); |
584 | |
|
585 | 0 | seq_range_array_merge(&ctx->dest_saved_uids, &dest_uidset); |
586 | |
|
587 | 0 | seq_range_array_iter_init(&iter, &dest_uidset); |
588 | 0 | (void)seq_range_array_iter_nth(&iter, 0, uid_r); |
589 | 0 | return TRUE; |
590 | 0 | } |
591 | | |
592 | | static void imapc_copy_set_error(struct imapc_save_context *sctx, |
593 | | const struct imapc_command_reply *reply) |
594 | 0 | { |
595 | 0 | sctx->failed = TRUE; |
596 | |
|
597 | 0 | if (reply->state != IMAPC_COMMAND_STATE_BAD) |
598 | 0 | imapc_copy_error_from_reply(sctx->mbox->storage, |
599 | 0 | MAIL_ERROR_PARAMS, reply); |
600 | 0 | else |
601 | 0 | mailbox_set_critical(&sctx->mbox->box, |
602 | 0 | "imapc: COPY failed: %s", |
603 | 0 | reply->text_full); |
604 | 0 | } |
605 | | |
606 | | static void |
607 | | imapc_copy_simple_callback(const struct imapc_command_reply *reply, |
608 | | void *context) |
609 | 0 | { |
610 | 0 | struct imapc_save_cmd_context *ctx = context; |
611 | 0 | uint32_t uid = 0; |
612 | |
|
613 | 0 | if (reply->state == IMAPC_COMMAND_STATE_OK) { |
614 | 0 | if (reply->resp_text_key != NULL && |
615 | 0 | strcasecmp(reply->resp_text_key, "COPYUID") == 0) |
616 | 0 | imapc_save_copyuid(ctx->ctx, reply, &uid); |
617 | 0 | imapc_save_add_to_index(ctx->ctx, uid); |
618 | 0 | ctx->ret = 0; |
619 | 0 | } else if (reply->state == IMAPC_COMMAND_STATE_NO) { |
620 | 0 | imapc_copy_error_from_reply(ctx->ctx->mbox->storage, |
621 | 0 | MAIL_ERROR_PARAMS, reply); |
622 | 0 | ctx->ret = -1; |
623 | 0 | } else { |
624 | 0 | mailbox_set_critical(&ctx->ctx->mbox->box, |
625 | 0 | "imapc: COPY failed: %s", reply->text_full); |
626 | 0 | ctx->ret = -1; |
627 | 0 | } |
628 | 0 | imapc_client_stop(ctx->ctx->mbox->storage->client->client); |
629 | 0 | } |
630 | | |
631 | | static int |
632 | | imapc_copy_simple(struct mail_save_context *_ctx, struct mail *mail) |
633 | 0 | { |
634 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
635 | 0 | struct mailbox_transaction_context *_t = _ctx->transaction; |
636 | 0 | struct imapc_save_cmd_context sctx; |
637 | 0 | struct imapc_command *cmd; |
638 | |
|
639 | 0 | sctx.ret = -2; |
640 | 0 | sctx.ctx = ctx; |
641 | 0 | cmd = imapc_client_mailbox_cmd(ctx->src_mbox->client_box, |
642 | 0 | imapc_copy_simple_callback, |
643 | 0 | &sctx); |
644 | 0 | imapc_command_sendf(cmd, "UID COPY %u %s", mail->uid, _t->box->name); |
645 | 0 | while (sctx.ret == -2) |
646 | 0 | imapc_mailbox_run(ctx->src_mbox); |
647 | 0 | ctx->finished = TRUE; |
648 | 0 | return sctx.ret; |
649 | 0 | } |
650 | | |
651 | | static void imapc_copy_bulk_callback(const struct imapc_command_reply *reply, |
652 | | void *context) |
653 | 0 | { |
654 | 0 | struct imapc_copy_request *request = context; |
655 | 0 | struct imapc_save_context *ctx = request->sctx; |
656 | 0 | struct imapc_mailbox *mbox = ctx->src_mbox; |
657 | 0 | unsigned int uid; |
658 | |
|
659 | 0 | i_assert(mbox != NULL); |
660 | 0 | i_assert(request == mbox->pending_copy_request); |
661 | | |
662 | | /* Check the reply state and add uid's to index and |
663 | | dest_saved_uids. */ |
664 | 0 | if (ctx->failed) { |
665 | | /* If the saving already failed try to find UIDs already |
666 | | copied from the reply so that rollback can expunge |
667 | | them */ |
668 | 0 | if (null_strcasecmp(reply->resp_text_key, "COPYUID") == 0) { |
669 | 0 | (void)imapc_save_copyuid(ctx, reply, &uid); |
670 | 0 | imapc_transaction_save_rollback(&ctx->ctx); |
671 | 0 | } |
672 | 0 | } else if (reply->state == IMAPC_COMMAND_STATE_OK) { |
673 | 0 | if (reply->resp_text_key != NULL && |
674 | 0 | strcasecmp(reply->resp_text_key, "COPYUID") == 0 && |
675 | 0 | imapc_save_copyuid(ctx, reply, &uid)) { |
676 | 0 | ctx->finished = TRUE; |
677 | 0 | } |
678 | 0 | } else { |
679 | 0 | imapc_copy_set_error(ctx, reply); |
680 | 0 | } |
681 | |
|
682 | 0 | ctx->src_mbox->pending_copy_request = NULL; |
683 | 0 | i_free(request); |
684 | 0 | imapc_client_stop(mbox->storage->client->client); |
685 | 0 | } |
686 | | |
687 | | static void imapc_mail_copy_bulk_flush(struct imapc_mailbox *mbox) |
688 | 0 | { |
689 | 0 | struct imapc_command *cmd; |
690 | |
|
691 | 0 | i_assert(mbox != NULL); |
692 | 0 | i_assert(mbox->pending_copy_request != NULL); |
693 | 0 | i_assert(mbox->client_box != NULL); |
694 | | |
695 | 0 | cmd = imapc_client_mailbox_cmd(mbox->client_box, |
696 | 0 | imapc_copy_bulk_callback, |
697 | 0 | mbox->pending_copy_request); |
698 | |
|
699 | 0 | seqset_builder_deinit(&mbox->pending_copy_request->uidset_builder); |
700 | |
|
701 | 0 | str_append(mbox->pending_copy_cmd, " "); |
702 | 0 | imap_append_astring(mbox->pending_copy_cmd, mbox->copy_dest_box, 0); |
703 | |
|
704 | 0 | imapc_command_send(cmd, str_c(mbox->pending_copy_cmd)); |
705 | |
|
706 | 0 | imapc_copy_bulk_ctx_deinit(mbox->pending_copy_request->sctx); |
707 | 0 | } |
708 | | |
709 | | static bool |
710 | | imapc_mail_copy_bulk_try_merge(struct imapc_mailbox *mbox, uint32_t uid, |
711 | | const char *box) |
712 | 0 | { |
713 | 0 | i_assert(str_begins_with(str_c(mbox->pending_copy_cmd), "UID COPY ")); |
714 | | |
715 | 0 | if (strcmp(box, mbox->copy_dest_box) != 0) { |
716 | | /* Not the same mailbox merging not possible */ |
717 | 0 | return FALSE; |
718 | 0 | } |
719 | 0 | return seqset_builder_try_add(mbox->pending_copy_request->uidset_builder, |
720 | 0 | IMAPC_SERVER_CMDLINE_MAX_LEN, uid); |
721 | 0 | } |
722 | | |
723 | | static void |
724 | | imapc_mail_copy_bulk_delayed_send_or_merge(struct imapc_save_context *ctx, |
725 | | uint32_t uid, |
726 | | const char *box) |
727 | 0 | { |
728 | 0 | struct imapc_mailbox *mbox = ctx->src_mbox; |
729 | |
|
730 | 0 | if (mbox->pending_copy_request != NULL && |
731 | 0 | !imapc_mail_copy_bulk_try_merge(mbox, uid, box)) { |
732 | | /* send the previous COPY and create new one after |
733 | | waiting for this one to be finished. */ |
734 | 0 | imapc_mail_copy_bulk_flush(mbox); |
735 | 0 | imapc_copy_bulk_finish(mbox->pending_copy_request->sctx); |
736 | 0 | } |
737 | 0 | if (mbox->pending_copy_request == NULL) { |
738 | 0 | mbox->pending_copy_request = |
739 | 0 | i_new(struct imapc_copy_request, 1); |
740 | 0 | str_printfa(mbox->pending_copy_cmd, "UID COPY "); |
741 | 0 | mbox->pending_copy_request->uidset_builder = |
742 | 0 | seqset_builder_init(mbox->pending_copy_cmd); |
743 | 0 | seqset_builder_add(mbox->pending_copy_request->uidset_builder, |
744 | 0 | uid); |
745 | 0 | mbox->copy_dest_box = i_strdup(box); |
746 | 0 | } else { |
747 | 0 | i_assert(mbox->pending_copy_request->sctx == ctx); |
748 | 0 | } |
749 | 0 | mbox->pending_copy_request->sctx = ctx; |
750 | 0 | } |
751 | | |
752 | | static int |
753 | | imapc_copy_bulk(struct imapc_save_context *ctx, struct mail *mail) |
754 | 0 | { |
755 | 0 | struct imapc_mailbox *mbox = IMAPC_MAILBOX(ctx->ctx.transaction->box); |
756 | |
|
757 | 0 | imapc_mail_copy_bulk_delayed_send_or_merge(ctx, mail->uid, |
758 | 0 | imapc_mailbox_get_remote_name(mbox)); |
759 | 0 | imapc_save_add_to_index(ctx, 0); |
760 | |
|
761 | 0 | return ctx->failed ? -1 : 0; |
762 | 0 | } |
763 | | |
764 | | static bool imapc_is_mail_expunged(struct imapc_mailbox *mbox, uint32_t uid) |
765 | 0 | { |
766 | 0 | if (array_is_created(&mbox->delayed_expunged_uids) && |
767 | 0 | seq_range_exists(&mbox->delayed_expunged_uids, uid)) |
768 | 0 | return TRUE; |
769 | 0 | if (mbox->delayed_sync_trans == NULL) |
770 | 0 | return FALSE; |
771 | | |
772 | 0 | struct mail_index_view *view = |
773 | 0 | mail_index_transaction_get_view(mbox->delayed_sync_trans); |
774 | 0 | uint32_t seq; |
775 | 0 | return mail_index_lookup_seq(view, uid, &seq) && |
776 | 0 | mail_index_transaction_is_expunged(mbox->delayed_sync_trans, seq); |
777 | 0 | } |
778 | | |
779 | | int imapc_copy(struct mail_save_context *_ctx, struct mail *mail) |
780 | 0 | { |
781 | 0 | struct imapc_save_context *ctx = IMAPC_SAVECTX(_ctx); |
782 | 0 | struct mailbox_transaction_context *_t = _ctx->transaction; |
783 | 0 | struct imapc_msgmap *src_msgmap; |
784 | 0 | uint32_t rseq; |
785 | 0 | int ret; |
786 | |
|
787 | 0 | i_assert((_t->flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); |
788 | | |
789 | 0 | if (_t->box->storage == mail->box->storage) { |
790 | | /* Currently we don't support copying mails from multiple |
791 | | different source mailboxes within the same transaction. */ |
792 | 0 | i_assert(ctx->src_mbox == NULL || &ctx->src_mbox->box == mail->box); |
793 | 0 | ctx->src_mbox = IMAPC_MAILBOX(mail->box); |
794 | 0 | if (!mail->expunged && |
795 | 0 | imapc_is_mail_expunged(ctx->src_mbox, mail->uid)) |
796 | 0 | mail_set_expunged(mail); |
797 | | /* same server, we can use COPY for the mail */ |
798 | 0 | src_msgmap = |
799 | 0 | imapc_client_mailbox_get_msgmap(ctx->src_mbox->client_box); |
800 | 0 | if (mail->expunged || |
801 | 0 | !imapc_msgmap_uid_to_rseq(src_msgmap, mail->uid, &rseq)) { |
802 | 0 | mail_storage_set_error(mail->box->storage, |
803 | 0 | MAIL_ERROR_EXPUNGED, |
804 | 0 | "Some of the requested messages no longer exist."); |
805 | 0 | ctx->finished = TRUE; |
806 | 0 | index_save_context_free(_ctx); |
807 | 0 | return -1; |
808 | 0 | } |
809 | | /* Mail has not been expunged and can be copied. */ |
810 | 0 | if (ctx->mbox->capabilities == 0) { |
811 | | /* The destination mailbox has not yet been selected |
812 | | so the capabilities are unknown */ |
813 | 0 | if (imapc_client_get_capabilities(ctx->mbox->storage->client->client, |
814 | 0 | &ctx->mbox->capabilities) < 0) { |
815 | 0 | mail_storage_set_error(mail->box->storage, |
816 | 0 | MAIL_ERROR_UNAVAILABLE, |
817 | 0 | "Failed to determine capabilities for mailbox."); |
818 | 0 | ctx->finished = TRUE; |
819 | 0 | index_save_context_free(_ctx); |
820 | 0 | return -1; |
821 | 0 | } |
822 | 0 | } |
823 | 0 | if ((ctx->mbox->capabilities & IMAPC_CAPABILITY_UIDPLUS) != 0) |
824 | 0 | ret = imapc_copy_bulk(ctx, mail); |
825 | 0 | else |
826 | 0 | ret = imapc_copy_simple(_ctx, mail); |
827 | 0 | index_save_context_free(_ctx); |
828 | 0 | return ret; |
829 | 0 | } |
830 | 0 | return mail_storage_copy(_ctx, mail); |
831 | 0 | } |