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