1 /* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "str.h"
5 #include "mail-namespace.h"
6 #include "mail-storage.h"
7 #include "mail-search.h"
8 #include "doveadm-print.h"
9 #include "doveadm-mail.h"
10 #include "doveadm-mailbox-list-iter.h"
11 
12 #define ALL_STATUS_ITEMS \
13 	(STATUS_MESSAGES | STATUS_RECENT | \
14 	 STATUS_UIDNEXT | STATUS_UIDVALIDITY | \
15 	 STATUS_UNSEEN | STATUS_HIGHESTMODSEQ)
16 #define ALL_METADATA_ITEMS \
17 	(MAILBOX_METADATA_VIRTUAL_SIZE | MAILBOX_METADATA_GUID | \
18 	 MAILBOX_METADATA_FIRST_SAVE_DATE)
19 
20 #define TOTAL_STATUS_ITEMS \
21 	(STATUS_MESSAGES | STATUS_RECENT | STATUS_UNSEEN)
22 #define TOTAL_METADATA_ITEMS \
23 	(MAILBOX_METADATA_VIRTUAL_SIZE)
24 
25 struct status_cmd_context {
26 	struct doveadm_mail_cmd_context ctx;
27 	struct mail_search_args *search_args;
28 
29 	enum mailbox_status_items status_items;
30 	enum mailbox_metadata_items metadata_items;
31 	struct mailbox_status total_status;
32 	struct mailbox_metadata total_metadata;
33 
34 	bool total_sum:1;
35 };
36 
status_parse_fields(struct status_cmd_context * ctx,const char * const * fields)37 static void status_parse_fields(struct status_cmd_context *ctx,
38 				const char *const *fields)
39 {
40 	if (*fields == NULL)
41 		i_fatal_status(EX_USAGE, "No status fields");
42 
43 	for (; *fields != NULL; fields++) {
44 		const char *field = *fields;
45 
46 		if (strcmp(field, "all") == 0) {
47 			if (ctx->total_sum) {
48 				ctx->status_items |= TOTAL_STATUS_ITEMS;
49 				ctx->metadata_items |= TOTAL_METADATA_ITEMS;
50 			} else {
51 				ctx->status_items |= ALL_STATUS_ITEMS;
52 				ctx->metadata_items |= ALL_METADATA_ITEMS;
53 			}
54 		} else if (strcmp(field, "messages") == 0)
55 			ctx->status_items |= STATUS_MESSAGES;
56 		else if (strcmp(field, "recent") == 0)
57 			ctx->status_items |= STATUS_RECENT;
58 		else if (strcmp(field, "uidnext") == 0)
59 			ctx->status_items |= STATUS_UIDNEXT;
60 		else if (strcmp(field, "uidvalidity") == 0)
61 			ctx->status_items |= STATUS_UIDVALIDITY;
62 		else if (strcmp(field, "unseen") == 0)
63 			ctx->status_items |= STATUS_UNSEEN;
64 		else if (strcmp(field, "highestmodseq") == 0)
65 			ctx->status_items |= STATUS_HIGHESTMODSEQ;
66 		else if (strcmp(field, "vsize") == 0)
67 			ctx->metadata_items |= MAILBOX_METADATA_VIRTUAL_SIZE;
68 		else if (strcmp(field, "guid") == 0)
69 			ctx->metadata_items |= MAILBOX_METADATA_GUID;
70 		else if (strcmp(field, "firstsaved") == 0)
71 			ctx->metadata_items |= MAILBOX_METADATA_FIRST_SAVE_DATE;
72 		else {
73 			i_fatal_status(EX_USAGE,
74 				       "Unknown status field: %s", field);
75 		}
76 
77 		if (ctx->total_sum &&
78 		    ((ctx->status_items & ENUM_NEGATE(TOTAL_STATUS_ITEMS)) != 0 ||
79 		     (ctx->metadata_items & ENUM_NEGATE(TOTAL_METADATA_ITEMS)) != 0)) {
80 			i_fatal_status(EX_USAGE,
81 				"Status field %s can't be used with -t", field);
82 		}
83 	}
84 }
85 
86 static void ATTR_NULL(2)
status_output(struct status_cmd_context * ctx,struct mailbox * box,const struct mailbox_status * status,const struct mailbox_metadata * metadata)87 status_output(struct status_cmd_context *ctx, struct mailbox *box,
88 	      const struct mailbox_status *status,
89 	      const struct mailbox_metadata *metadata)
90 {
91 	if (box != NULL)
92 		doveadm_print(mailbox_get_vname(box));
93 
94 	if ((ctx->status_items & STATUS_MESSAGES) != 0)
95 		doveadm_print_num(status->messages);
96 	if ((ctx->status_items & STATUS_RECENT) != 0)
97 		doveadm_print_num(status->recent);
98 	if ((ctx->status_items & STATUS_UIDNEXT) != 0)
99 		doveadm_print_num(status->uidnext);
100 	if ((ctx->status_items & STATUS_UIDVALIDITY) != 0)
101 		doveadm_print_num(status->uidvalidity);
102 	if ((ctx->status_items & STATUS_UNSEEN) != 0)
103 		doveadm_print_num(status->unseen);
104 	if ((ctx->status_items & STATUS_HIGHESTMODSEQ) != 0)
105 		doveadm_print_num(status->highest_modseq);
106 	if ((ctx->metadata_items & MAILBOX_METADATA_VIRTUAL_SIZE) != 0)
107 		doveadm_print_num(metadata->virtual_size);
108 	if ((ctx->metadata_items & MAILBOX_METADATA_GUID) != 0)
109 		doveadm_print(guid_128_to_string(metadata->guid));
110 	if ((ctx->metadata_items & MAILBOX_METADATA_FIRST_SAVE_DATE) > 0) {
111 		if (metadata->first_save_date > -1)
112 			doveadm_print_num(metadata->first_save_date);
113 		else
114 			doveadm_print("never");
115 	}
116 }
117 
118 static void
status_sum(struct status_cmd_context * ctx,const struct mailbox_status * status,const struct mailbox_metadata * metadata)119 status_sum(struct status_cmd_context *ctx,
120 	   const struct mailbox_status *status,
121 	   const struct mailbox_metadata *metadata)
122 {
123 	struct mailbox_status *dest = &ctx->total_status;
124 
125 	dest->messages += status->messages;
126 	dest->recent += status->recent;
127 	dest->unseen += status->unseen;
128 	ctx->total_metadata.virtual_size += metadata->virtual_size;
129 }
130 
131 static int
status_mailbox(struct status_cmd_context * ctx,const struct mailbox_info * info)132 status_mailbox(struct status_cmd_context *ctx, const struct mailbox_info *info)
133 {
134 	struct mailbox *box;
135 	struct mailbox_status status;
136 	struct mailbox_metadata metadata;
137 
138 	box = doveadm_mailbox_find(ctx->ctx.cur_mail_user, info->vname);
139 	mailbox_set_reason(box, ctx->ctx.cmd->name);
140 	if (mailbox_get_status(box, ctx->status_items, &status) < 0 ||
141 	    mailbox_get_metadata(box, ctx->metadata_items, &metadata) < 0) {
142 		i_error("Mailbox %s: Failed to lookup mailbox status: %s",
143 			mailbox_get_vname(box),
144 			mailbox_get_last_internal_error(box, NULL));
145 		doveadm_mail_failed_mailbox(&ctx->ctx, box);
146 		mailbox_free(&box);
147 		return -1;
148 	}
149 	if (!ctx->total_sum)
150 		status_output(ctx, box, &status, &metadata);
151 	else
152 		status_sum(ctx, &status, &metadata);
153 	mailbox_free(&box);
154 	return 0;
155 }
156 
157 static int
cmd_mailbox_status_run(struct doveadm_mail_cmd_context * _ctx,struct mail_user * user)158 cmd_mailbox_status_run(struct doveadm_mail_cmd_context *_ctx,
159 		       struct mail_user *user)
160 {
161 	struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx;
162 	enum mailbox_list_iter_flags iter_flags =
163 		MAILBOX_LIST_ITER_NO_AUTO_BOXES |
164 		MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
165 	struct doveadm_mailbox_list_iter *iter;
166 	const struct mailbox_info *info;
167 	int ret = 0;
168 
169 	i_zero(&ctx->total_status);
170 	i_zero(&ctx->total_metadata);
171 
172 	iter = doveadm_mailbox_list_iter_init(_ctx, user, ctx->search_args,
173 					      iter_flags);
174 	while ((info = doveadm_mailbox_list_iter_next(iter)) != NULL) {
175 		T_BEGIN {
176 			if (status_mailbox(ctx, info) < 0)
177 				ret = -1;
178 		} T_END;
179 	}
180 	if (doveadm_mailbox_list_iter_deinit(&iter) < 0)
181 		ret = -1;
182 
183 	if (ctx->total_sum) {
184 		status_output(ctx, NULL, &ctx->total_status,
185 			      &ctx->total_metadata);
186 	}
187 	return ret;
188 }
189 
cmd_mailbox_status_init(struct doveadm_mail_cmd_context * _ctx,const char * const args[])190 static void cmd_mailbox_status_init(struct doveadm_mail_cmd_context *_ctx,
191 				    const char *const args[])
192 {
193 	struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx;
194 	const char *fields = args[0];
195 
196 	if (fields == NULL || args[1] == NULL)
197 		doveadm_mail_help_name("mailbox status");
198 
199 	status_parse_fields(ctx, t_strsplit_spaces(fields, " "));
200 	ctx->search_args = doveadm_mail_mailbox_search_args_build(args+1);
201 
202 	if (!ctx->total_sum) {
203 		doveadm_print_header("mailbox", "mailbox",
204 				     DOVEADM_PRINT_HEADER_FLAG_HIDE_TITLE);
205 	}
206 	if ((ctx->status_items & STATUS_MESSAGES) != 0)
207 		doveadm_print_header_simple("messages");
208 	if ((ctx->status_items & STATUS_RECENT) != 0)
209 		doveadm_print_header_simple("recent");
210 	if ((ctx->status_items & STATUS_UIDNEXT) != 0)
211 		doveadm_print_header_simple("uidnext");
212 	if ((ctx->status_items & STATUS_UIDVALIDITY) != 0)
213 		doveadm_print_header_simple("uidvalidity");
214 	if ((ctx->status_items & STATUS_UNSEEN) != 0)
215 		doveadm_print_header_simple("unseen");
216 	if ((ctx->status_items & STATUS_HIGHESTMODSEQ) != 0)
217 		doveadm_print_header_simple("highestmodseq");
218 	if ((ctx->metadata_items & MAILBOX_METADATA_VIRTUAL_SIZE) != 0)
219 		doveadm_print_header_simple("vsize");
220 	if ((ctx->metadata_items & MAILBOX_METADATA_GUID) != 0)
221 		doveadm_print_header_simple("guid");
222 	if ((ctx->metadata_items & MAILBOX_METADATA_FIRST_SAVE_DATE) != 0)
223 		doveadm_print_header_simple("firstsaved");
224 }
225 
cmd_mailbox_status_deinit(struct doveadm_mail_cmd_context * _ctx)226 static void cmd_mailbox_status_deinit(struct doveadm_mail_cmd_context *_ctx)
227 {
228 	struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx;
229 
230 	if (ctx->search_args != NULL)
231 		mail_search_args_unref(&ctx->search_args);
232 }
233 
234 static bool
cmd_mailbox_status_parse_arg(struct doveadm_mail_cmd_context * _ctx,int c)235 cmd_mailbox_status_parse_arg(struct doveadm_mail_cmd_context *_ctx, int c)
236 {
237 	struct status_cmd_context *ctx = (struct status_cmd_context *)_ctx;
238 
239 	switch (c) {
240 	case 't':
241 		ctx->total_sum = TRUE;
242 		break;
243 	case 'f':
244 		break;
245 	default:
246 		return FALSE;
247 	}
248 	return TRUE;
249 }
250 
cmd_mailbox_status_alloc(void)251 static struct doveadm_mail_cmd_context *cmd_mailbox_status_alloc(void)
252 {
253 	struct status_cmd_context *ctx;
254 
255 	ctx = doveadm_mail_cmd_alloc(struct status_cmd_context);
256 	ctx->ctx.getopt_args = "t";
257 	ctx->ctx.v.parse_arg = cmd_mailbox_status_parse_arg;
258 	ctx->ctx.v.init = cmd_mailbox_status_init;
259 	ctx->ctx.v.deinit = cmd_mailbox_status_deinit;
260 	ctx->ctx.v.run = cmd_mailbox_status_run;
261 	doveadm_print_init(DOVEADM_PRINT_TYPE_FLOW);
262 	return &ctx->ctx;
263 }
264 
265 struct doveadm_cmd_ver2 doveadm_cmd_mailbox_status_ver2 = {
266         .name = "mailbox status",
267         .mail_cmd = cmd_mailbox_status_alloc,
268         .usage = DOVEADM_CMD_MAIL_USAGE_PREFIX"<fields> <mailbox> [...]",
269 DOVEADM_CMD_PARAMS_START
270 DOVEADM_CMD_MAIL_COMMON
271 DOVEADM_CMD_PARAM('t', "total-sum", CMD_PARAM_BOOL, 0)
272 DOVEADM_CMD_PARAM('f', "field", CMD_PARAM_ARRAY, 0)
273 DOVEADM_CMD_PARAM('\0', "fieldstr", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL | CMD_PARAM_FLAG_DO_NOT_EXPOSE) /* FIXME: horrible hack, remove me when possible */
274 DOVEADM_CMD_PARAM('\0', "mailbox-mask", CMD_PARAM_ARRAY, CMD_PARAM_FLAG_POSITIONAL)
275 DOVEADM_CMD_PARAMS_END
276 };
277