1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "mail-storage.h"
5 #include "mail-namespace.h"
6 #include "doveadm-print.h"
7 #include "doveadm-mailbox-list-iter.h"
8 #include "doveadm-mail-iter.h"
9 #include "doveadm-mail.h"
10
11 #include <stdio.h>
12
13 struct copy_cmd_context {
14 struct doveadm_mail_cmd_context ctx;
15
16 const char *source_username;
17 struct mail_storage_service_user *source_service_user;
18 struct mail_user *source_user;
19
20 const char *destname;
21 bool move;
22 };
23
24 static int
cmd_copy_box(struct copy_cmd_context * ctx,struct mailbox * destbox,const struct mailbox_info * info)25 cmd_copy_box(struct copy_cmd_context *ctx, struct mailbox *destbox,
26 const struct mailbox_info *info)
27 {
28 struct doveadm_mail_iter *iter;
29 struct mailbox_transaction_context *desttrans;
30 struct mail_save_context *save_ctx;
31 struct mail *mail;
32 int ret = 0, ret2;
33
34 if (doveadm_mail_iter_init(&ctx->ctx, info, ctx->ctx.search_args, 0,
35 NULL, FALSE, &iter) < 0)
36 return -1;
37
38 /* use a separately committed transaction for each mailbox.
39 this guarantees that mails aren't expunged without actually having
40 been copied. */
41 desttrans = mailbox_transaction_begin(destbox,
42 MAILBOX_TRANSACTION_FLAG_EXTERNAL |
43 ctx->ctx.transaction_flags, __func__);
44
45 while (doveadm_mail_iter_next(iter, &mail)) {
46 save_ctx = mailbox_save_alloc(desttrans);
47 mailbox_save_copy_flags(save_ctx, mail);
48 if (ctx->move)
49 ret2 = mailbox_move(&save_ctx, mail);
50 else
51 ret2 = mailbox_copy(&save_ctx, mail);
52 if (ret2 < 0) {
53 i_error("%s message UID %u from '%s' failed: %s",
54 ctx->move ? "Moving" : "Copying",
55 mail->uid, info->vname,
56 mailbox_get_last_internal_error(destbox, NULL));
57 doveadm_mail_failed_mailbox(&ctx->ctx, destbox);
58 ret = -1;
59 }
60 }
61
62 if (mailbox_transaction_commit(&desttrans) < 0) {
63 i_error("Committing %s mails failed: %s",
64 ctx->move ? "moved" : "copied",
65 mailbox_get_last_internal_error(destbox, NULL));
66 doveadm_mail_failed_mailbox(&ctx->ctx, destbox);
67 /* rollback expunges */
68 doveadm_mail_iter_deinit_rollback(&iter);
69 ret = -1;
70 } else {
71 if (doveadm_mail_iter_deinit_sync(&iter) < 0)
72 ret = -1;
73 }
74 return ret;
75 }
76
77 static void
cmd_copy_alloc_source_user(struct copy_cmd_context * ctx)78 cmd_copy_alloc_source_user(struct copy_cmd_context *ctx)
79 {
80 struct mail_storage_service_input input;
81 const char *error;
82
83 input = ctx->ctx.storage_service_input;
84 input.username = ctx->source_username;
85
86 if (mail_storage_service_lookup_next(ctx->ctx.storage_service, &input,
87 &ctx->source_service_user,
88 &ctx->source_user,
89 &error) < 0)
90 i_fatal("Couldn't lookup user %s: %s", input.username, error);
91 }
92
93 static int
cmd_copy_run(struct doveadm_mail_cmd_context * _ctx,struct mail_user * user)94 cmd_copy_run(struct doveadm_mail_cmd_context *_ctx, struct mail_user *user)
95 {
96 struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx;
97 const enum mailbox_list_iter_flags iter_flags =
98 MAILBOX_LIST_ITER_NO_AUTO_BOXES |
99 MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
100 struct doveadm_mailbox_list_iter *iter;
101 struct mail_user *src_user;
102 struct mail_namespace *ns;
103 struct mailbox *destbox;
104 const struct mailbox_info *info;
105 int ret = 0;
106
107 if (ctx->source_username != NULL && ctx->source_user == NULL)
108 cmd_copy_alloc_source_user(ctx);
109
110 ns = mail_namespace_find(user->namespaces, ctx->destname);
111 destbox = mailbox_alloc(ns->list, ctx->destname, MAILBOX_FLAG_SAVEONLY);
112 mailbox_set_reason(destbox, _ctx->cmd->name);
113 if (mailbox_open(destbox) < 0) {
114 i_error("Can't open mailbox '%s': %s", ctx->destname,
115 mailbox_get_last_internal_error(destbox, NULL));
116 doveadm_mail_failed_mailbox(&ctx->ctx, destbox);
117 mailbox_free(&destbox);
118 return -1;
119 }
120
121 src_user = ctx->source_user != NULL ? ctx->source_user : user;
122 iter = doveadm_mailbox_list_iter_init(_ctx, src_user, _ctx->search_args,
123 iter_flags);
124 while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
125 if (cmd_copy_box(ctx, destbox, info) < 0)
126 ret = -1;
127 } T_END;
128 if (doveadm_mailbox_list_iter_deinit(&iter) < 0)
129 ret = -1;
130
131 if (mailbox_sync(destbox, 0) < 0) {
132 i_error("Syncing mailbox '%s' failed: %s", ctx->destname,
133 mailbox_get_last_internal_error(destbox, NULL));
134 doveadm_mail_failed_mailbox(&ctx->ctx, destbox);
135 ret = -1;
136 }
137 mailbox_free(&destbox);
138 return ret;
139 }
140
cmd_copy_init(struct doveadm_mail_cmd_context * _ctx,const char * const args[])141 static void cmd_copy_init(struct doveadm_mail_cmd_context *_ctx,
142 const char *const args[])
143 {
144 struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx;
145 const char *destname = args[0], *cmdname = ctx->move ? "move" : "copy";
146
147 if (destname == NULL || args[1] == NULL)
148 doveadm_mail_help_name(cmdname);
149 args++;
150
151 if (args[0] != NULL && args[1] != NULL &&
152 strcasecmp(args[0], "user") == 0) {
153 if ((_ctx->service_flags &
154 MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0)
155 i_fatal("Use -u parameter to specify destination user");
156
157 ctx->source_username = p_strdup(_ctx->pool, args[1]);
158 args += 2;
159 }
160
161 ctx->destname = p_strdup(ctx->ctx.pool, destname);
162 _ctx->search_args = doveadm_mail_build_search_args(args);
163 if (ctx->move)
164 expunge_search_args_check(ctx->ctx.search_args, cmdname);
165 }
166
cmd_copy_deinit(struct doveadm_mail_cmd_context * _ctx)167 static void cmd_copy_deinit(struct doveadm_mail_cmd_context *_ctx)
168 {
169 struct copy_cmd_context *ctx = (struct copy_cmd_context *)_ctx;
170
171 if (ctx->source_user != NULL) {
172 mail_storage_service_user_unref(&ctx->source_service_user);
173 mail_user_deinit(&ctx->source_user);
174 }
175 }
176
cmd_copy_alloc(void)177 static struct doveadm_mail_cmd_context *cmd_copy_alloc(void)
178 {
179 struct copy_cmd_context *ctx;
180
181 ctx = doveadm_mail_cmd_alloc(struct copy_cmd_context);
182 ctx->ctx.v.init = cmd_copy_init;
183 ctx->ctx.v.deinit = cmd_copy_deinit;
184 ctx->ctx.v.run = cmd_copy_run;
185 doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW);
186 return &ctx->ctx;
187 }
188
cmd_move_alloc(void)189 static struct doveadm_mail_cmd_context *cmd_move_alloc(void)
190 {
191 struct copy_cmd_context *ctx;
192
193 ctx = (struct copy_cmd_context *)cmd_copy_alloc();
194 ctx->move = TRUE;
195 return &ctx->ctx;
196 }
197
198 struct doveadm_cmd_ver2 doveadm_cmd_copy_ver2 = {
199 .name = "copy",
200 .mail_cmd = cmd_copy_alloc,
201 .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<destination> [user <source user>] <search query>",
202 DOVEADM_CMD_PARAMS_START
203 DOVEADM_CMD_MAIL_COMMON
204 DOVEADM_CMD_PARAM('\0', "destination-mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
205 DOVEADM_CMD_PARAM('\0', "source-type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
206 DOVEADM_CMD_PARAM('\0', "source-user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
207 DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
208 DOVEADM_CMD_PARAMS_END
209 };
210
211 struct doveadm_cmd_ver2 doveadm_cmd_move_ver2 = {
212 .name = "move",
213 .mail_cmd = cmd_move_alloc,
214 .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX "<destination> [user <source user>] <search query>",
215 DOVEADM_CMD_PARAMS_START
216 DOVEADM_CMD_MAIL_COMMON
217 DOVEADM_CMD_PARAM('\0', "destination-mailbox", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
218 DOVEADM_CMD_PARAM('\0', "source-type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
219 DOVEADM_CMD_PARAM('\0', "source-user", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
220 DOVEADM_CMD_PARAM('\0', "query", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
221 DOVEADM_CMD_PARAMS_END
222 };
223