1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "net.h"
5 #include "ostream.h"
6 #include "str.h"
7 #include "strescape.h"
8 #include "wildcard-match.h"
9 #include "mail-stats.h"
10 #include "mail-command.h"
11 #include "mail-session.h"
12 #include "mail-user.h"
13 #include "mail-domain.h"
14 #include "mail-ip.h"
15 #include "client.h"
16 #include "client-export.h"
17 
18 enum mail_export_level {
19 	MAIL_EXPORT_LEVEL_COMMAND,
20 	MAIL_EXPORT_LEVEL_SESSION,
21 	MAIL_EXPORT_LEVEL_USER,
22 	MAIL_EXPORT_LEVEL_DOMAIN,
23 	MAIL_EXPORT_LEVEL_IP,
24 	MAIL_EXPORT_LEVEL_GLOBAL
25 };
26 static const char *mail_export_level_names[] = {
27 	"command", "session", "user", "domain", "ip", "global"
28 };
29 
30 struct mail_export_filter {
31 	const char *user, *domain, *session;
32 	struct ip_addr ip;
33 	unsigned int ip_bits;
34 	time_t since;
35 	bool connected;
36 };
37 
38 struct client_export_cmd {
39 	enum mail_export_level level;
40 	struct mail_export_filter filter;
41 	string_t *str;
42 	int (*export_iter)(struct client *client);
43 	bool header_sent;
44 };
45 
46 static int
mail_export_level_parse(const char * str,enum mail_export_level * level_r)47 mail_export_level_parse(const char *str, enum mail_export_level *level_r)
48 {
49 	unsigned int i;
50 
51 	for (i = 0; i < N_ELEMENTS(mail_export_level_names); i++) {
52 		if (strcmp(mail_export_level_names[i], str) == 0) {
53 			*level_r = (enum mail_export_level)i;
54 			return 0;
55 		}
56 	}
57 	return -1;
58 }
59 
60 static int
mail_export_parse_filter(const char * const * args,pool_t pool,struct mail_export_filter * filter_r,const char ** error_r)61 mail_export_parse_filter(const char *const *args, pool_t pool,
62 			 struct mail_export_filter *filter_r,
63 			 const char **error_r)
64 {
65 	unsigned long l;
66 
67 	/* filters:
68 	   user=<wildcard> | domain=<wildcard> | session=<str>
69 	   ip=<ip>[/<mask>]
70 	   since=<timestamp>
71 	   connected
72 	*/
73 	i_zero(filter_r);
74 	for (; *args != NULL; args++) {
75 		if (str_begins(*args, "user="))
76 			filter_r->user = p_strdup(pool, *args + 5);
77 		else if (str_begins(*args, "domain="))
78 			filter_r->domain = p_strdup(pool, *args + 7);
79 		else if (str_begins(*args, "session="))
80 			filter_r->session = p_strdup(pool, *args + 8);
81 		else if (str_begins(*args, "ip=")) {
82 			if (net_parse_range(*args + 3, &filter_r->ip,
83 					    &filter_r->ip_bits) < 0) {
84 				*error_r = "Invalid ip filter";
85 				return -1;
86 			}
87 		} else if (str_begins(*args, "since=")) {
88 			if (str_to_ulong(*args + 6, &l) < 0) {
89 				*error_r = "Invalid since filter";
90 				return -1;
91 			}
92 			filter_r->since = (time_t)l;
93 		} else if (strcmp(*args, "connected") == 0) {
94 			filter_r->connected = TRUE;
95 		}
96 	}
97 	return 0;
98 }
99 
100 static void
client_export_stats_headers(struct client * client)101 client_export_stats_headers(struct client *client)
102 {
103 	unsigned int i, count = stats_field_count();
104 	string_t *str = t_str_new(128);
105 
106 	i_assert(count > 0);
107 
108 	str_append(str, stats_field_name(0));
109 	for (i = 1; i < count; i++) {
110 		str_append_c(str, '\t');
111 		str_append(str, stats_field_name(i));
112 	}
113 	str_append_c(str, '\n');
114 	o_stream_nsend(client->output, str_data(str), str_len(str));
115 }
116 
117 static void
client_export_stats(string_t * str,const struct stats * stats)118 client_export_stats(string_t *str, const struct stats *stats)
119 {
120 	unsigned int i, count = stats_field_count();
121 
122 	i_assert(count > 0);
123 
124 	stats_field_value(str, stats, 0);
125 	for (i = 1; i < count; i++) {
126 		str_append_c(str, '\t');
127 		stats_field_value(str, stats, i);
128 	}
129 }
130 
131 static bool
mail_export_filter_match_session(const struct mail_export_filter * filter,const struct mail_session * session)132 mail_export_filter_match_session(const struct mail_export_filter *filter,
133 				 const struct mail_session *session)
134 {
135 	if (filter->connected && session->disconnected)
136 		return FALSE;
137 	if (filter->since > session->last_update.tv_sec)
138 		return FALSE;
139 	if (filter->session != NULL &&
140 	    strcmp(session->id, filter->session) != 0)
141 		return FALSE;
142 	if (filter->user != NULL &&
143 	    !wildcard_match(session->user->name, filter->user))
144 		return FALSE;
145 	if (filter->domain != NULL &&
146 	    !wildcard_match(session->user->domain->name, filter->domain))
147 		return FALSE;
148 	if (filter->ip_bits > 0 &&
149 	    !net_is_in_network(&session->ip->ip, &filter->ip, filter->ip_bits))
150 		return FALSE;
151 	return TRUE;
152 }
153 
154 static bool
mail_export_filter_match_user_common(const struct mail_export_filter * filter,const struct mail_user * user)155 mail_export_filter_match_user_common(const struct mail_export_filter *filter,
156 				     const struct mail_user *user)
157 {
158 	struct mail_session *s;
159 	bool connected = FALSE, ip_ok = FALSE;
160 
161 	if (filter->user != NULL &&
162 	    !wildcard_match(user->name, filter->user))
163 		return FALSE;
164 
165 	if (filter->connected || filter->ip_bits > 0) {
166 		for (s = user->sessions; s != NULL; s = s->user_next) {
167 			if (!s->disconnected)
168 				connected = TRUE;
169 			if (filter->ip_bits > 0 &&
170 			    net_is_in_network(&s->ip->ip, &filter->ip,
171 					      filter->ip_bits))
172 				ip_ok = TRUE;
173 
174 		}
175 		if (filter->connected && !connected)
176 			return FALSE;
177 		if (filter->ip_bits > 0 && !ip_ok)
178 			return FALSE;
179 	}
180 	return TRUE;
181 }
182 
183 static bool
mail_export_filter_match_user(const struct mail_export_filter * filter,const struct mail_user * user)184 mail_export_filter_match_user(const struct mail_export_filter *filter,
185 			      const struct mail_user *user)
186 {
187 	if (filter->since > user->last_update.tv_sec)
188 		return FALSE;
189 	if (filter->domain != NULL &&
190 	    !wildcard_match(user->domain->name, filter->domain))
191 		return FALSE;
192 	return mail_export_filter_match_user_common(filter, user);
193 }
194 
195 static bool
mail_export_filter_match_domain(const struct mail_export_filter * filter,const struct mail_domain * domain)196 mail_export_filter_match_domain(const struct mail_export_filter *filter,
197 				const struct mail_domain *domain)
198 {
199 	struct mail_user *user;
200 
201 	if (filter->since > domain->last_update.tv_sec)
202 		return FALSE;
203 	if (filter->domain != NULL &&
204 	    !wildcard_match(domain->name, filter->domain))
205 		return FALSE;
206 
207 	if (filter->user != NULL || filter->connected || filter->ip_bits > 0) {
208 		for (user = domain->users; user != NULL; user = user->domain_next) {
209 			if (mail_export_filter_match_user_common(filter, user))
210 				break;
211 		}
212 		if (user == NULL)
213 			return FALSE;
214 	}
215 	return TRUE;
216 }
217 
218 static bool
mail_export_filter_match_ip(const struct mail_export_filter * filter,const struct mail_ip * ip)219 mail_export_filter_match_ip(const struct mail_export_filter *filter,
220 			    const struct mail_ip *ip)
221 {
222 	struct mail_session *s;
223 	bool connected = FALSE, user_ok = FALSE, domain_ok = FALSE;
224 
225 	if (filter->connected || filter->ip_bits > 0) {
226 		for (s = ip->sessions; s != NULL; s = s->ip_next) {
227 			if (!s->disconnected)
228 				connected = TRUE;
229 			if (filter->user != NULL &&
230 			    wildcard_match(s->user->name, filter->user))
231 				user_ok = TRUE;
232 			if (filter->domain != NULL &&
233 			    wildcard_match(s->user->domain->name, filter->domain))
234 				domain_ok = TRUE;
235 		}
236 		if (filter->connected && !connected)
237 			return FALSE;
238 		if (filter->user != NULL && !user_ok)
239 			return FALSE;
240 		if (filter->domain != NULL && !domain_ok)
241 			return FALSE;
242 	}
243 	if (filter->since > ip->last_update.tv_sec)
244 		return FALSE;
245 	if (filter->ip_bits > 0 &&
246 	    !net_is_in_network(&ip->ip, &filter->ip, filter->ip_bits))
247 		return FALSE;
248 	return TRUE;
249 }
250 
client_export_timeval(string_t * str,const struct timeval * tv)251 static void client_export_timeval(string_t *str, const struct timeval *tv)
252 {
253 	str_printfa(str, "\t%ld.%06u", (long)tv->tv_sec,
254 		    (unsigned int)tv->tv_usec);
255 }
256 
client_export_iter_command(struct client * client)257 static int client_export_iter_command(struct client *client)
258 {
259 	struct client_export_cmd *cmd = client->cmd_export;
260 	struct mail_command *command = client->mail_cmd_iter;
261 
262 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_COMMAND);
263 	mail_command_unref(&client->mail_cmd_iter);
264 
265 	if (!cmd->header_sent) {
266 		o_stream_nsend_str(client->output,
267 			"cmd\targs\tsession\tuser\tlast_update\t");
268 		client_export_stats_headers(client);
269 		cmd->header_sent = TRUE;
270 	}
271 
272 	for (; command != NULL; command = command->stable_next) {
273 		if (client_is_busy(client))
274 			break;
275 		if (!mail_export_filter_match_session(&cmd->filter,
276 						      command->session))
277 			continue;
278 
279 		str_truncate(cmd->str, 0);
280 		str_append_tabescaped(cmd->str, command->name);
281 		str_append_c(cmd->str, '\t');
282 		str_append_tabescaped(cmd->str, command->args);
283 		str_append_c(cmd->str, '\t');
284 		str_append(cmd->str, command->session->id);
285 		str_append_c(cmd->str, '\t');
286 		str_append_tabescaped(cmd->str,
287 				      command->session->user->name);
288 		client_export_timeval(cmd->str, &command->last_update);
289 		str_append_c(cmd->str, '\t');
290 		client_export_stats(cmd->str, command->stats);
291 		str_append_c(cmd->str, '\n');
292 		o_stream_nsend(client->output, str_data(cmd->str),
293 			       str_len(cmd->str));
294 	}
295 
296 	if (command != NULL) {
297 		client->mail_cmd_iter = command;
298 		mail_command_ref(command);
299 		return 0;
300 	}
301 	return 1;
302 }
303 
client_export_iter_session(struct client * client)304 static int client_export_iter_session(struct client *client)
305 {
306 	struct client_export_cmd *cmd = client->cmd_export;
307 	struct mail_session *session = client->mail_session_iter;
308 
309 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_SESSION);
310 	mail_session_unref(&client->mail_session_iter);
311 
312 	if (!cmd->header_sent) {
313 		o_stream_nsend_str(client->output,
314 			"session\tuser\tip\tservice\tpid\tconnected"
315 			"\tlast_update\tnum_cmds\t");
316 		client_export_stats_headers(client);
317 		cmd->header_sent = TRUE;
318 	}
319 
320 	for (; session != NULL; session = session->stable_next) {
321 		if (client_is_busy(client))
322 			break;
323 		if (!mail_export_filter_match_session(&cmd->filter, session))
324 			continue;
325 
326 		str_truncate(cmd->str, 0);
327 		str_append(cmd->str, session->id);
328 		str_append_c(cmd->str, '\t');
329 		str_append_tabescaped(cmd->str, session->user->name);
330 		str_append_c(cmd->str, '\t');
331 		if (session->ip != NULL) T_BEGIN {
332 			str_append(cmd->str, net_ip2addr(&session->ip->ip));
333 		} T_END;
334 		str_append_c(cmd->str, '\t');
335 		str_append_tabescaped(cmd->str, session->service);
336 		str_printfa(cmd->str, "\t%ld", (long)session->pid);
337 		str_printfa(cmd->str, "\t%d", !session->disconnected);
338 		client_export_timeval(cmd->str, &session->last_update);
339 		str_printfa(cmd->str, "\t%u\t", session->num_cmds);
340 		client_export_stats(cmd->str, session->stats);
341 		str_append_c(cmd->str, '\n');
342 		o_stream_nsend(client->output, str_data(cmd->str),
343 			       str_len(cmd->str));
344 	}
345 
346 	if (session != NULL) {
347 		client->mail_session_iter = session;
348 		mail_session_ref(session);
349 		return 0;
350 	}
351 	return 1;
352 }
353 
client_export_iter_user(struct client * client)354 static int client_export_iter_user(struct client *client)
355 {
356 	struct client_export_cmd *cmd = client->cmd_export;
357 	struct mail_user *user = client->mail_user_iter;
358 
359 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_USER);
360 	mail_user_unref(&client->mail_user_iter);
361 
362 	if (!cmd->header_sent) {
363 		o_stream_nsend_str(client->output,
364 			"user\treset_timestamp\tlast_update"
365 			"\tnum_logins\tnum_cmds\t");
366 		client_export_stats_headers(client);
367 		cmd->header_sent = TRUE;
368 	}
369 
370 	for (; user != NULL; user = user->stable_next) {
371 		if (client_is_busy(client))
372 			break;
373 		if (!mail_export_filter_match_user(&cmd->filter, user))
374 			continue;
375 
376 		str_truncate(cmd->str, 0);
377 		str_append_tabescaped(cmd->str, user->name);
378 		str_printfa(cmd->str, "\t%ld", (long)user->reset_timestamp);
379 		client_export_timeval(cmd->str, &user->last_update);
380 		str_printfa(cmd->str, "\t%u\t%u\t",
381 			    user->num_logins, user->num_cmds);
382 		client_export_stats(cmd->str, user->stats);
383 		str_append_c(cmd->str, '\n');
384 		o_stream_nsend(client->output, str_data(cmd->str),
385 			       str_len(cmd->str));
386 	}
387 
388 	if (user != NULL) {
389 		client->mail_user_iter = user;
390 		mail_user_ref(user);
391 		return 0;
392 	}
393 	return 1;
394 }
395 
client_export_iter_domain(struct client * client)396 static int client_export_iter_domain(struct client *client)
397 {
398 	struct client_export_cmd *cmd = client->cmd_export;
399 	struct mail_domain *domain = client->mail_domain_iter;
400 
401 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_DOMAIN);
402 	mail_domain_unref(&client->mail_domain_iter);
403 
404 	if (!cmd->header_sent) {
405 		o_stream_nsend_str(client->output,
406 			"domain\treset_timestamp\tlast_update"
407 			"\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
408 		client_export_stats_headers(client);
409 		cmd->header_sent = TRUE;
410 	}
411 
412 	for (; domain != NULL; domain = domain->stable_next) {
413 		if (client_is_busy(client))
414 			break;
415 		if (!mail_export_filter_match_domain(&cmd->filter, domain))
416 			continue;
417 
418 		str_truncate(cmd->str, 0);
419 		str_append_tabescaped(cmd->str, domain->name);
420 		str_printfa(cmd->str, "\t%ld", (long)domain->reset_timestamp);
421 		client_export_timeval(cmd->str, &domain->last_update);
422 		str_printfa(cmd->str, "\t%u\t%u\t%u\t",
423 			    domain->num_logins, domain->num_cmds,
424 			    domain->num_connected_sessions);
425 		client_export_stats(cmd->str, domain->stats);
426 		str_append_c(cmd->str, '\n');
427 		o_stream_nsend(client->output, str_data(cmd->str),
428 			       str_len(cmd->str));
429 	}
430 
431 	if (domain != NULL) {
432 		client->mail_domain_iter = domain;
433 		mail_domain_ref(domain);
434 		return 0;
435 	}
436 	return 1;
437 }
438 
client_export_iter_ip(struct client * client)439 static int client_export_iter_ip(struct client *client)
440 {
441 	struct client_export_cmd *cmd = client->cmd_export;
442 	struct mail_ip *ip = client->mail_ip_iter;
443 
444 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_IP);
445 	mail_ip_unref(&client->mail_ip_iter);
446 
447 	if (!cmd->header_sent) {
448 		o_stream_nsend_str(client->output,
449 			"ip\treset_timestamp\tlast_update"
450 			"\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
451 		client_export_stats_headers(client);
452 		cmd->header_sent = TRUE;
453 	}
454 
455 	for (; ip != NULL; ip = ip->stable_next) {
456 		if (client_is_busy(client))
457 			break;
458 		if (!mail_export_filter_match_ip(&cmd->filter, ip))
459 			continue;
460 
461 		str_truncate(cmd->str, 0);
462 		T_BEGIN {
463 			str_append(cmd->str, net_ip2addr(&ip->ip));
464 		} T_END;
465 		str_printfa(cmd->str, "\t%ld", (long)ip->reset_timestamp);
466 		client_export_timeval(cmd->str, &ip->last_update);
467 		str_printfa(cmd->str, "\t%u\t%u\t%u\t",
468 			    ip->num_logins, ip->num_cmds, ip->num_connected_sessions);
469 		client_export_stats(cmd->str, ip->stats);
470 		str_append_c(cmd->str, '\n');
471 		o_stream_nsend(client->output, str_data(cmd->str),
472 			       str_len(cmd->str));
473 	}
474 
475 	if (ip != NULL) {
476 		client->mail_ip_iter = ip;
477 		mail_ip_ref(ip);
478 		return 0;
479 	}
480 	return 1;
481 }
482 
client_export_iter_global(struct client * client)483 static int client_export_iter_global(struct client *client)
484 {
485 	struct client_export_cmd *cmd = client->cmd_export;
486 	struct mail_global *g = &mail_global_stats;
487 
488 	i_assert(cmd->level == MAIL_EXPORT_LEVEL_GLOBAL);
489 
490 	if (!cmd->header_sent) {
491 		o_stream_nsend_str(client->output,
492 			"reset_timestamp\tlast_update"
493 			"\tnum_logins\tnum_cmds\tnum_connected_sessions\t");
494 		client_export_stats_headers(client);
495 		cmd->header_sent = TRUE;
496 	}
497 
498 	str_truncate(cmd->str, 0);
499 	str_printfa(cmd->str, "%ld", (long)g->reset_timestamp);
500 	client_export_timeval(cmd->str, &g->last_update);
501 	str_printfa(cmd->str, "\t%u\t%u\t%u\t",
502 		    g->num_logins, g->num_cmds, g->num_connected_sessions);
503 	client_export_stats(cmd->str, g->stats);
504 	str_append_c(cmd->str, '\n');
505 	o_stream_nsend(client->output, str_data(cmd->str),
506 		       str_len(cmd->str));
507 	return 1;
508 }
509 
client_export_more(struct client * client)510 static int client_export_more(struct client *client)
511 {
512 	if (client->cmd_export->export_iter(client) == 0)
513 		return 0;
514 	o_stream_nsend_str(client->output, "\n");
515 	return 1;
516 }
517 
client_export_iter_init(struct client * client)518 static bool client_export_iter_init(struct client *client)
519 {
520 	struct client_export_cmd *cmd = client->cmd_export;
521 
522 	if (cmd->filter.user != NULL && strchr(cmd->filter.user, '*') == NULL &&
523 	    (cmd->level == MAIL_EXPORT_LEVEL_USER ||
524 	     cmd->level == MAIL_EXPORT_LEVEL_SESSION)) {
525 		/* exact user */
526 		struct mail_user *user = mail_user_lookup(cmd->filter.user);
527 		if (user == NULL)
528 			return FALSE;
529 		if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) {
530 			client->mail_session_iter = user->sessions;
531 			if (client->mail_session_iter == NULL)
532 				return FALSE;
533 			mail_session_ref(client->mail_session_iter);
534 			cmd->export_iter = client_export_iter_session;
535 		} else {
536 			client->mail_user_iter = user;
537 			mail_user_ref(user);
538 			cmd->export_iter = client_export_iter_user;
539 		}
540 		return TRUE;
541 	}
542 	if (cmd->filter.ip_bits == IPADDR_BITS(&cmd->filter.ip) &&
543 	    (cmd->level == MAIL_EXPORT_LEVEL_IP ||
544 	     cmd->level == MAIL_EXPORT_LEVEL_SESSION)) {
545 		/* exact IP address */
546 		struct mail_ip *ip = mail_ip_lookup(&cmd->filter.ip);
547 		if (ip == NULL)
548 			return FALSE;
549 		if (cmd->level == MAIL_EXPORT_LEVEL_SESSION) {
550 			client->mail_session_iter = ip->sessions;
551 			if (client->mail_session_iter == NULL)
552 				return FALSE;
553 			mail_session_ref(client->mail_session_iter);
554 			cmd->export_iter = client_export_iter_session;
555 		} else {
556 			client->mail_ip_iter = ip;
557 			mail_ip_ref(ip);
558 			cmd->export_iter = client_export_iter_ip;
559 		}
560 		return TRUE;
561 	}
562 	if (cmd->filter.domain != NULL &&
563 	    strchr(cmd->filter.domain, '*') == NULL &&
564 	    (cmd->level == MAIL_EXPORT_LEVEL_DOMAIN ||
565 	     cmd->level == MAIL_EXPORT_LEVEL_USER)) {
566 		/* exact domain */
567 		struct mail_domain *domain =
568 			mail_domain_lookup(cmd->filter.domain);
569 		if (domain == NULL)
570 			return FALSE;
571 		if (cmd->level == MAIL_EXPORT_LEVEL_USER) {
572 			client->mail_user_iter = domain->users;
573 			mail_user_ref(client->mail_user_iter);
574 			cmd->export_iter = client_export_iter_user;
575 		} else {
576 			client->mail_domain_iter = domain;
577 			mail_domain_ref(domain);
578 			cmd->export_iter = client_export_iter_domain;
579 		}
580 		return TRUE;
581 	}
582 
583 	switch (cmd->level) {
584 	case MAIL_EXPORT_LEVEL_COMMAND:
585 		client->mail_cmd_iter = stable_mail_commands_head;
586 		if (client->mail_cmd_iter == NULL)
587 			return FALSE;
588 		mail_command_ref(client->mail_cmd_iter);
589 		cmd->export_iter = client_export_iter_command;
590 		break;
591 	case MAIL_EXPORT_LEVEL_SESSION:
592 		client->mail_session_iter = stable_mail_sessions;
593 		if (client->mail_session_iter == NULL)
594 			return FALSE;
595 		mail_session_ref(client->mail_session_iter);
596 		cmd->export_iter = client_export_iter_session;
597 		break;
598 	case MAIL_EXPORT_LEVEL_USER:
599 		client->mail_user_iter = stable_mail_users;
600 		if (client->mail_user_iter == NULL)
601 			return FALSE;
602 		mail_user_ref(client->mail_user_iter);
603 		cmd->export_iter = client_export_iter_user;
604 		break;
605 	case MAIL_EXPORT_LEVEL_DOMAIN:
606 		client->mail_domain_iter = stable_mail_domains;
607 		if (client->mail_domain_iter == NULL)
608 			return FALSE;
609 		mail_domain_ref(client->mail_domain_iter);
610 		cmd->export_iter = client_export_iter_domain;
611 		break;
612 	case MAIL_EXPORT_LEVEL_IP:
613 		client->mail_ip_iter = stable_mail_ips;
614 		if (client->mail_ip_iter == NULL)
615 			return FALSE;
616 		mail_ip_ref(client->mail_ip_iter);
617 		cmd->export_iter = client_export_iter_ip;
618 		break;
619 	case MAIL_EXPORT_LEVEL_GLOBAL:
620 		cmd->export_iter = client_export_iter_global;
621 		break;
622 	}
623 	i_assert(cmd->export_iter != NULL);
624 	return TRUE;
625 }
626 
client_export(struct client * client,const char * const * args,const char ** error_r)627 int client_export(struct client *client, const char *const *args,
628 		  const char **error_r)
629 {
630 	const char *level_str = args[0];
631 	struct client_export_cmd *cmd;
632 
633 	p_clear(client->cmd_pool);
634 	cmd = p_new(client->cmd_pool, struct client_export_cmd, 1);
635 	cmd->str = str_new(client->cmd_pool, 256);
636 
637 	if (level_str == NULL) {
638 		*error_r = "Missing level parameter";
639 		return -1;
640 	}
641 	if (mail_export_level_parse(level_str, &cmd->level) < 0) {
642 		*error_r = "Invalid level";
643 		return -1;
644 	}
645 	if (mail_export_parse_filter(args + 1, client->cmd_pool,
646 				     &cmd->filter, error_r) < 0)
647 		return -1;
648 
649 	client->cmd_export = cmd;
650 	if (!client_export_iter_init(client)) {
651 		/* nothing to export */
652 		o_stream_nsend_str(client->output, "\n");
653 		return 1;
654 	}
655 	client->cmd_more = client_export_more;
656 	return client_export_more(client);
657 }
658