1 /*
2  Copyright (C) 1999-2004 IC & S  dbmail@ic-s.nl
3  Copyright (c) 2004-2012 NFG Net Facilities Group BV support@nfg.nl
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later
9  version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 /*
22  *
23  * imapcommands.c
24  *
25  * IMAP server command implementations
26  */
27 
28 #include "dbmail.h"
29 #define THIS_MODULE "imap"
30 
31 #ifndef _GNU_SOURCE
32 #define _GNU_SOURCE
33 #endif
34 
35 extern DBParam_T db_params;
36 #define DBPFX db_params.pfx
37 
38 extern ServerConfig_T *server_conf;
39 extern int selfpipe[2];
40 extern pthread_mutex_t selfpipe_lock;
41 extern GAsyncQueue *queue;
42 extern const char *imap_flag_desc[];
43 extern const char *imap_flag_desc_escaped[];
44 extern const char AcceptedMailboxnameChars[];
45 
46 int imap_before_smtp = 0;
47 
48 struct cmd_t {
49 	gboolean silent;
50 	int action;
51 	int flaglist[IMAP_NFLAGS];
52 	GList *keywords;
53 	uint64_t mailbox_id;
54 	uint64_t seq;
55 	uint64_t unchangedsince;
56 };
57 
58 /*
59  * push a message onto the queue and notify the
60  * event-loop by sending a char into the selfpipe
61  */
62 
63 #define SESSION_GET \
64 	ImapSession *self = D->session
65 
66 #define SESSION_RETURN \
67 	D->session->command_state = TRUE; \
68 	g_async_queue_push(queue, (gpointer)D); \
69 	PLOCK(selfpipe_lock); \
70 	if (selfpipe[1] > -1) { \
71 		if (write(selfpipe[1], "D", 1)) { /* ignore */; } \
72 	} \
73 	PUNLOCK(selfpipe_lock); \
74 	return;
75 
76 /* Macro for OK answers with optional response code */
77 #define SESSION_OK_COMMON(RESP_CODE_FORMAT, RESP_CODE_VALUE) \
78 	if (self->state != CLIENTSTATE_ERROR) \
79 		dbmail_imap_session_buff_printf(self, \
80 				"%s OK " RESP_CODE_FORMAT "%s%s completed\r\n", \
81 				self->tag, RESP_CODE_VALUE, self->use_uid ? "UID " : "", \
82 				self->command)
83 
84 /* The original SESSION_OK macro has been refactored to SESSION_OK_COMMON */
85 #define SESSION_OK \
86 	SESSION_OK_COMMON("%s", "")
87 
88 #define SESSION_OK_WITH_RESP_CODE(VALUE) \
89 	SESSION_OK_COMMON("[%s] ", VALUE)
90 
91 static void _fetch_update(ImapSession *self, MessageInfo *msginfo, gboolean showmodseq, gboolean showflags);
92 
check_state_and_args(ImapSession * self,int minargs,int maxargs,ClientState_T state)93 static int check_state_and_args(ImapSession * self, int minargs, int maxargs, ClientState_T state)
94 {
95 	int i;
96 
97 	if (self->state == CLIENTSTATE_ERROR) return 0;
98 
99 	/* check state */
100 	if (state != CLIENTSTATE_ANY) {
101 		if (self->state != state) {
102 			if (!  (state == CLIENTSTATE_AUTHENTICATED && self->state == CLIENTSTATE_SELECTED)) {
103 				dbmail_imap_session_buff_printf(self, "%s BAD %s command received in invalid state [%d] != [%d]\r\n",
104 					self->tag, self->command, self->state, state);
105 				return 0;
106 			}
107 		}
108 	}
109 
110 	/* check args */
111 	for (i = 0; i < minargs; i++) {
112 		if (!self->args[self->args_idx+i]) {
113 			/* error: need more args */
114 			dbmail_imap_session_buff_printf(self, "%s BAD missing argument%s to %s\r\n", self->tag, (minargs == 1) ? "" : "(s)",
115 					self->command);
116 			return 0;
117 		}
118 	}
119 
120 	for (i = 0; self->args[self->args_idx+i]; i++);
121 
122 	if (maxargs && (i > maxargs)) {
123 		/* error: too many args */
124 		dbmail_imap_session_buff_printf(self, "%s BAD too many arguments to %s\r\n",
125 				self->tag, self->command);
126 		return 0;
127 	}
128 
129 	/* succes */
130 	return 1;
131 }
132 
133 
134 
135 /*
136  * RETURN VALUES _ic_ functions:
137  *
138  * -1 Fatal error, close connection to user
139  *  0 Succes
140  *  1 Non-fatal error, connection stays alive
141  */
142 
143 /*
144  * ANY-STATE COMMANDS: capability, starttls, noop, logout, id
145  */
146 /*
147  * _ic_capability()
148  *
149  * returns a string to the client containing the server capabilities
150  */
151 // a trivial silly thread example
_ic_capability_enter(dm_thread_data * D)152 void _ic_capability_enter(dm_thread_data *D)
153 {
154 	SESSION_GET;
155 	dbmail_imap_session_buff_printf(self, "* %s %s\r\n", self->command, Capa_as_string(self->capa));
156 	SESSION_OK;
157 	SESSION_RETURN;
158 }
159 
_ic_capability(ImapSession * self)160 int _ic_capability(ImapSession *self)
161 {
162 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_ANY)) return 1;
163 	dm_thread_data_push((gpointer)self, _ic_capability_enter, _ic_cb_leave, NULL);
164 	return 0;
165 }
166 
_ic_starttls(ImapSession * self)167 int _ic_starttls(ImapSession *self)
168 {
169 	int i;
170 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_ANY)) return 1;
171 	if (! server_conf->ssl) {
172 		ci_write(self->ci, "%s NO TLS not available\r\n", self->tag);
173 		return 1;
174 	}
175 	if (self->ci->sock->ssl_state) {
176 		ci_write(self->ci, "%s NO TLS already active\r\n", self->tag);
177 		return 1;
178 	}
179 	ci_write(self->ci, "%s OK Begin TLS now\r\n", self->tag);
180 	i = ci_starttls(self->ci);
181 
182 	Capa_remove(self->capa, "STARTTLS");
183 	Capa_remove(self->capa, "LOGINDISABLED");
184 
185 	if (i < 0) i = 0;
186 
187 	if (i == 0) return 3; /* done */
188 
189 	return i;
190 }
191 
192 /*
193  * _ic_noop()
194  *
195  * performs No operation
196  */
197 
_ic_noop_enter(dm_thread_data * D)198 void _ic_noop_enter(dm_thread_data *D)
199 {
200 	SESSION_GET;
201 	if (self->state == CLIENTSTATE_SELECTED)
202 		dbmail_imap_session_mailbox_status(self, TRUE);
203 	SESSION_OK;
204 	SESSION_RETURN;
205 }
206 
207 
_ic_noop(ImapSession * self)208 int _ic_noop(ImapSession *self)
209 {
210 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_ANY)) return 1;
211 	dm_thread_data_push((gpointer)self, _ic_noop_enter, _ic_cb_leave, NULL);
212 	return 0;
213 }
214 
215 /*
216  * _ic_logout()
217  *
218  * prepares logout from IMAP-server
219  */
_ic_logout(ImapSession * self)220 int _ic_logout(ImapSession *self)
221 {
222 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_ANY)) return 1;
223 	dbmail_imap_session_set_state(self, CLIENTSTATE_LOGOUT);
224 	if (self->userid)
225 		TRACE(TRACE_NOTICE, "[%p] userid:[%" PRIu64 "]", self, self->userid);
226 	return 2;
227 }
228 
_ic_id_enter(dm_thread_data * D)229 void _ic_id_enter(dm_thread_data *D)
230 {
231 	SESSION_GET;
232 	struct utsname buf;
233 	memset(&buf, 0, sizeof(buf));
234 	uname(&buf);
235 	dbmail_imap_session_buff_printf(self, "* ID (\"name\" \"dbmail\" \"version\" \"%s\""
236 		" \"os\" \"%s\" \"os-version\" \"%s\")\r\n", DM_VERSION, &buf.sysname, &buf.release);
237 	SESSION_OK;
238 	SESSION_RETURN;
239 }
240 
_ic_id(ImapSession * self)241 int _ic_id(ImapSession *self)
242 {
243 	if (!check_state_and_args(self, 1, 0, CLIENTSTATE_ANY)) return 1;
244 	dm_thread_data_push((gpointer)self, _ic_id_enter, _ic_cb_leave, NULL);
245 	return 0;
246 }
247 /*
248  * PRE-AUTHENTICATED STATE COMMANDS
249  * login, authenticate
250  */
251 
252 /* _ic_login()
253  *
254  * Performs login-request handling.
255  */
_ic_login(ImapSession * self)256 int _ic_login(ImapSession *self)
257 {
258 	return _ic_authenticate(self);
259 }
260 
261 
262 /* _ic_authenticate()
263  *
264  * performs authentication using LOGIN mechanism:
265  */
_ic_authenticate_enter(dm_thread_data * D)266 void _ic_authenticate_enter(dm_thread_data *D)
267 {
268 	int err;
269 	SESSION_GET;
270 	const char *username = NULL;
271 	const char *password = NULL;
272 
273 	if (self->args[self->args_idx] && self->args[self->args_idx+1]) {
274 		username = p_string_str(self->args[self->args_idx]);
275 		password = p_string_str(self->args[self->args_idx+1]);
276 	}
277 
278 	if ((err = dbmail_imap_session_handle_auth(self,username,password))) {
279 		D->status = err;
280 		SESSION_RETURN;
281 	}
282 	if (imap_before_smtp)
283 		db_log_ip(self->ci->src_ip);
284 
285 	if (self->state != CLIENTSTATE_ERROR) {
286 		if (self->ci->auth)
287 			username = Cram_getUsername(self->ci->auth);
288 		dbmail_imap_session_buff_printf(self,
289 				"%s OK [CAPABILITY %s] User %s authenticated\r\n",
290 				self->tag, Capa_as_string(self->capa), username);
291 	}
292 	SESSION_RETURN;
293 }
294 
_ic_authenticate(ImapSession * self)295 int _ic_authenticate(ImapSession *self)
296 {
297 	if (self->command_type == IMAP_COMM_AUTH) {
298 		if (!check_state_and_args(self, 1, 3, CLIENTSTATE_NON_AUTHENTICATED)) return 1;
299 		/* check authentication method */
300 		if ( (! MATCH(p_string_str(self->args[self->args_idx]), "login")) && \
301 			(! MATCH(p_string_str(self->args[self->args_idx]), "cram-md5")) ) {
302 			dbmail_imap_session_buff_printf(self, "%s NO Invalid authentication mechanism specified\r\n", self->tag);
303 			return 1;
304 		}
305 		self->args_idx++;
306 	} else {
307 		if (!check_state_and_args(self, 2, 2, CLIENTSTATE_NON_AUTHENTICATED)) return 1;
308 	}
309 
310 	if (Capa_match(self->preauth_capa, "LOGINDISABLED") && (! self->ci->sock->ssl_state)) {
311 		dbmail_imap_session_buff_printf(self, "%s NO try STARTTLS first\r\n", self->tag);
312 		return 1;
313 	}
314 	dm_thread_data_push((gpointer)self, _ic_authenticate_enter, _ic_cb_leave, NULL);
315 	return 0;
316 }
317 
318 
319 /*
320  * AUTHENTICATED STATE COMMANDS
321  * select, examine, create, delete, rename, subscribe,
322  * unsubscribe, list, lsub, status, append
323  */
324 
325 /* _ic_select()
326  *
327  * select a specified mailbox
328  */
mailbox_first_unseen(gpointer key,gpointer value,gpointer data)329 static gboolean mailbox_first_unseen(gpointer key, gpointer value, gpointer data)
330 {
331 	MessageInfo *msginfo = (MessageInfo *)value;
332 	if (msginfo->flags[IMAP_FLAG_SEEN])
333 	       	return FALSE;
334 	*(uint64_t *)data = *(uint64_t *)key;
335 	return TRUE;
336 }
337 
imap_session_mailbox_close(ImapSession * self)338 static int imap_session_mailbox_close(ImapSession *self)
339 {
340 	dbmail_imap_session_set_state(self,CLIENTSTATE_AUTHENTICATED);
341 	if (self->mailbox) {
342 		if (self->mailbox->mbstate)
343 			MailboxState_clear_recent(self->mailbox->mbstate);
344 
345 		dbmail_mailbox_free(self->mailbox);
346 		self->mailbox = NULL;
347 		if (self->enabled.qresync &&
348 				((self->command_type == IMAP_COMM_SELECT) || \
349 				(self->command_type == IMAP_COMM_EXAMINE))) {
350 			dbmail_imap_session_buff_printf(self,
351 				       	"* OK [CLOSED]\r\n");
352 		}
353 	}
354 
355 	return 0;
356 }
357 
mailbox_check_acl(ImapSession * self,MailboxState_T S,ACLRight acl)358 static int mailbox_check_acl(ImapSession *self, MailboxState_T S, ACLRight acl)
359 {
360 	int access = acl_has_right(S, self->userid, acl);
361 	if (access < 0) {
362 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
363 		return -1;
364 	}
365 	if (access == 0) {
366 		dbmail_imap_session_buff_printf(self, "%s NO permission denied\r\n", self->tag);
367 		return 1;
368 	}
369 	TRACE(TRACE_DEBUG,"access granted");
370 	return 0;
371 
372 }
373 
374 
imap_session_mailbox_open(ImapSession * self,const char * mailbox)375 static int imap_session_mailbox_open(ImapSession * self, const char * mailbox)
376 {
377 	uint64_t mailbox_idnr = 0;
378 
379 	/* get the mailbox_idnr */
380 	if (db_findmailbox(mailbox, self->userid, &mailbox_idnr)) { /* ignored */ }
381 
382 	/* create missing INBOX for this authenticated user */
383 	if ((! mailbox_idnr ) && (strcasecmp(mailbox, "INBOX")==0)) {
384 		int err = db_createmailbox("INBOX", self->userid, &mailbox_idnr);
385 		TRACE(TRACE_INFO, "[%p] [%d] Auto-creating INBOX for user id [%" PRIu64 "]",
386 				self, err, self->userid);
387 	}
388 
389 	if (! mailbox_idnr) {
390 		dbmail_imap_session_buff_printf(self, "%s NO specified mailbox does not exist\r\n", self->tag);
391 		return 1; /* error */
392 	}
393 
394 	/* new mailbox structure */
395 	self->mailbox = dbmail_mailbox_new(self->pool, mailbox_idnr);
396 
397 	/* fetch mailbox metadata */
398 	self->mailbox->mbstate = dbmail_imap_session_mbxinfo_lookup(self, mailbox_idnr);
399 
400 	/* check if user has right to select mailbox */
401 	if (mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_READ) == 1) {
402 		dbmail_imap_session_set_state(self,CLIENTSTATE_AUTHENTICATED);
403 		return DM_EGENERAL;
404 	}
405 
406 	if (self->command_type == IMAP_COMM_SELECT) {
407 		// SELECT
408 		MailboxState_setPermission(self->mailbox->mbstate,IMAPPERM_READWRITE);
409 	} else {
410 		// EXAMINE
411 		MailboxState_setPermission(self->mailbox->mbstate,IMAPPERM_READ);
412 	}
413 
414 	/* check if mailbox is selectable */
415 	if (MailboxState_noSelect(self->mailbox->mbstate))
416 		return DM_EGENERAL;
417 
418 	/* build list of recent messages */
419 	MailboxState_build_recent(self->mailbox->mbstate);
420 
421 	self->mailbox->condstore = self->enabled.condstore;
422 	self->mailbox->qresync = self->enabled.qresync;
423 
424 	return 0;
425 }
426 
validate_arg(const char * arg)427 static int validate_arg(const char *arg)
428 {
429 	/* check for invalid characters */
430 	int i;
431 	const char valid[] = "0123456789,:";
432 	for (i = 0; arg[i]; i++) {
433 		if (!strchr(valid, arg[i])) {
434 			return 1;
435 		}
436 	}
437 	return 0;
438 }
439 
440 
_ic_select_parse_args(ImapSession * self)441 static int _ic_select_parse_args(ImapSession *self)
442 {
443 	int i, idx;
444 	int paren = 0;
445 	uint64_t uidvalidity = 0;
446 	uint64_t modseq = 0;
447 	char *endptr;
448 	String_T known_uids = NULL;
449 	String_T known_seqset = NULL;
450 	String_T known_uidset = NULL;
451 
452 	idx = self->args_idx;
453 	while (self->args[idx++])
454 		;
455 	idx--;
456 
457 	if (idx == 1)
458 		return 0;
459 
460 	if (idx == 4) {
461 		if (Capa_match(self->capa, "CONDSTORE") && \
462 				(MATCH(p_string_str(self->args[1]),"(")) && \
463 				(MATCH(p_string_str(self->args[2]), "condstore")) && \
464 				(MATCH(p_string_str(self->args[3]), ")"))) {
465 			self->enabled.condstore = true;
466 			return 0;
467 		}
468 	}
469 	for (i = 1; i < idx; i++) {
470 		const char *arg = p_string_str(self->args[i]);
471 		if (MATCH(arg, "(")) {
472 			paren++;
473 			continue;
474 		}
475 		if (MATCH(arg, ")")) {
476 			paren--;
477 			continue;
478 		}
479 		switch (i) {
480 			case 2:
481 				if (! MATCH(arg, "qresync")) {
482 					TRACE(TRACE_DEBUG, "unknown argument [%s]", arg);
483 					return 1;
484 				}
485 				break;
486 			case 4:
487 				if ((uidvalidity = strtoull(arg, &endptr, 10))) {
488 					if (*endptr != '\0')
489 						return 1;
490 					TRACE(TRACE_DEBUG, "uidvalidity [%" PRIu64 "]", uidvalidity);
491 				}
492 				break;
493 			case 5:
494 				if ((modseq = strtoull(arg, &endptr, 10))) {
495 					if (*endptr != '\0')
496 						return 1;
497 					TRACE(TRACE_DEBUG, "mod-sequence [%" PRIu64 "]", modseq);
498 				}
499 				break;
500 			case 6:
501 				if (validate_arg(arg))
502 					return 1;
503 				known_uids = self->args[i];
504 				break;
505 			case 8:
506 				if (validate_arg(arg))
507 					return 1;
508 				known_seqset = self->args[i];
509 				break;
510 			case 9:
511 				if (validate_arg(arg))
512 					return 1;
513 				known_uidset = self->args[i];
514 				break;
515 		}
516 	}
517 
518 	if (paren) {
519 		TRACE(TRACE_DEBUG, "unbalanced parenthesis");
520 		return 1;
521 	}
522 
523 	if (known_seqset && (! known_uidset)) {
524 		TRACE(TRACE_DEBUG, "known uidset missing");
525 		return 1;
526 	}
527 
528 	if (! (uidvalidity && modseq)) {
529 		TRACE(TRACE_DEBUG, "required arguments missing");
530 		return 1;
531 	}
532 
533 	self->qresync.uidvalidity = uidvalidity;
534 	self->qresync.modseq = modseq;
535 	self->qresync.known_uids = known_uids;
536 	self->qresync.known_seqset = known_seqset;
537 	self->qresync.known_uidset = known_uidset;
538 
539 	return 0;
540 }
541 
_do_fetch_updates(uint64_t * id,gpointer UNUSED value,dm_thread_data * D)542 static gboolean _do_fetch_updates(uint64_t *id, gpointer UNUSED value, dm_thread_data *D)
543 {
544 	ImapSession *self = D->session;
545 	MessageInfo *msginfo = g_tree_lookup(
546 			MailboxState_getMsginfo(self->mailbox->mbstate), id);
547 	if (! msginfo)
548 		return TRUE;
549 
550 	_fetch_update(self, msginfo, true, true);
551 
552 	return FALSE;
553 }
554 
555 struct expunged_helper {
556 	GString *response;
557 	GTree *msgs;
558 	GTree *expunged;
559 	GTree *known_seqset;
560 	GTree *known_uidset;
561 	qresync_args *qresync;
562 	uint64_t start_expunged;
563 	uint64_t last_expunged;
564 	gboolean prev_expunged;
565 };
566 
_get_expunged(uint64_t * id,gpointer UNUSED value,struct expunged_helper * data)567 static gboolean _get_expunged(uint64_t *id, gpointer UNUSED value, struct expunged_helper *data)
568 {
569 	MessageInfo *msg = g_tree_lookup(data->msgs, id);
570 	gboolean expunged;
571 
572 	if (! msg)
573 		return TRUE;
574 
575 	//
576 	if (data->known_uidset && data->known_seqset) {
577 		uint64_t *msn = NULL, *knownuid = NULL;
578 		if ((msn = g_tree_lookup(data->known_uidset, id)))
579 			knownuid = g_tree_lookup(data->known_seqset, msn);
580 
581 		if (((!msn) || (!knownuid)) || (msn && knownuid && (*id != *knownuid)))
582 			return TRUE;
583 	}
584 
585 	if ((msg->seq > data->qresync->modseq) && (msg->status == MESSAGE_STATUS_DELETE)) {
586 		g_tree_insert(data->expunged, id, id);
587 		expunged = true;
588 	} else {
589 		expunged = false;
590 	}
591 
592 	if (!expunged) {
593 		if (data->prev_expunged && (data->start_expunged < data->last_expunged)) {
594 			g_string_append_printf(data->response, ":%" PRIu64, data->last_expunged);
595 		}
596 		data->prev_expunged = expunged;
597 		return FALSE;
598 	}
599 
600 	if (! data->prev_expunged) {
601 		if (data->last_expunged) {
602 			g_string_append_printf(data->response, ",%" PRIu64, *id);
603 		} else {
604 			data->start_expunged = *id;
605 			g_string_append_printf(data->response, "%" PRIu64, *id);
606 		}
607 	}
608 
609 	data->last_expunged = *id;
610 	data->prev_expunged = expunged;
611 
612 	return FALSE;
613 }
614 
MailboxState_getExpunged(MailboxState_T M,qresync_args * qresync,char ** out)615 static GTree *MailboxState_getExpunged(MailboxState_T M, qresync_args *qresync, char **out)
616 {
617 	struct expunged_helper data;
618 
619 	data.response = g_string_new("");
620 	data.start_expunged = 0;
621 	data.last_expunged = 0;
622 	data.prev_expunged = false;
623 	data.qresync = qresync;
624 	data.msgs = MailboxState_getMsginfo(M);
625 	data.expunged = g_tree_new_full((GCompareDataFunc)ucmpdata, NULL, NULL, NULL);
626 	data.known_seqset = NULL;
627 	data.known_uidset = NULL;
628 
629 	if (qresync->known_seqset && qresync->known_uidset) {
630 		data.known_seqset = MailboxState_get_set(
631 				M, p_string_str(qresync->known_seqset), FALSE);
632 
633 		data.known_uidset = MailboxState_get_set(
634 				M, p_string_str(qresync->known_uidset), TRUE);
635 	}
636 
637 	g_tree_foreach(data.msgs, (GTraverseFunc) _get_expunged, &data);
638 
639 	g_tree_destroy(data.known_seqset);
640 	g_tree_destroy(data.known_uidset);
641 
642 	TRACE(TRACE_DEBUG, "vanished [%d] messages", g_tree_nnodes(data.expunged));
643 	if (data.prev_expunged && (data.start_expunged < data.last_expunged)) {
644 		g_string_append_printf(data.response, ":%" PRIu64, data.last_expunged);
645 	}
646 
647 	TRACE(TRACE_DEBUG, "vanished (earlier) %s", data.response->str);
648 
649 	*out = data.response->str;
650 
651 	g_string_free(data.response, FALSE);
652 
653 	return data.expunged;
654 }
655 
_ic_select_enter(dm_thread_data * D)656 static void _ic_select_enter(dm_thread_data *D)
657 {
658 	int err;
659 	char *flags;
660 	const char *okarg;
661 	MailboxState_T S;
662 	SESSION_GET;
663 
664 	if (_ic_select_parse_args(self)) {
665 		dbmail_imap_session_buff_printf(self, "%s BAD invalid parameter\r\n",
666 				self->tag);
667 		D->status = 1;
668 		SESSION_RETURN;
669 	}
670 
671 	/* close the currently opened mailbox */
672 	imap_session_mailbox_close(self);
673 
674 	if ((err = imap_session_mailbox_open(self, p_string_str(self->args[self->args_idx])))) {
675 		D->status = err;
676 		SESSION_RETURN;
677 	}
678 
679 	dbmail_imap_session_set_state(self,CLIENTSTATE_SELECTED);
680 
681 	S = self->mailbox->mbstate;
682 
683 	dbmail_imap_session_buff_printf(self, "* %u EXISTS\r\n", MailboxState_getExists(S));
684 	dbmail_imap_session_buff_printf(self, "* %u RECENT\r\n", MailboxState_getRecent(S));
685 
686 	/* flags */
687 	flags = MailboxState_flags(S);
688 	dbmail_imap_session_buff_printf(self, "* FLAGS (%s)\r\n", flags);
689 	dbmail_imap_session_buff_printf(self, "* OK [PERMANENTFLAGS (%s \\*)] Flags allowed.\r\n", flags);
690 	g_free(flags);
691 
692 	/* UIDNEXT */
693 	dbmail_imap_session_buff_printf(self, "* OK [UIDNEXT %" PRIu64 "] Predicted next UID\r\n",
694 			MailboxState_getUidnext(S));
695 
696 	/* UID */
697 	dbmail_imap_session_buff_printf(self, "* OK [UIDVALIDITY %" PRIu64 "] UID value\r\n",
698 			MailboxState_getId(S));
699 
700 	/* MODSEQ */
701 	if (Capa_match(self->capa, "CONDSTORE")) {
702 		dbmail_imap_session_buff_printf(self, "* OK [HIGHESTMODSEQ %" PRIu64 "] Highest\r\n",
703 				MailboxState_getSeq(S));
704 	}
705 	/* UNSEEN first element*/
706 	int command_select_allow_unseen = config_get_value_default_int("command_select_allow_unseen", "IMAP", 1);
707 	if(self->command_type == IMAP_COMM_SELECT && command_select_allow_unseen == 1){
708 		if (MailboxState_getExists(S)) {
709 			/* show msn of first unseen msg (if present) */
710 			GTree *uids = MailboxState_getIds(S);
711 			GTree *info = MailboxState_getMsginfo(S);
712 			uint64_t key = 0, *msn = NULL;
713 			g_tree_foreach(info, (GTraverseFunc)mailbox_first_unseen, &key);
714 			if ( (key > 0) && (msn = g_tree_lookup(uids, &key))) {
715 				dbmail_imap_session_buff_printf(self, "* OK [UNSEEN %" PRIu64 "] first unseen message\r\n", *msn);
716 			}
717 		}
718 	}
719 	if (self->command_type == IMAP_COMM_SELECT) {
720 		okarg = "READ-WRITE";
721 		MailboxState_flush_recent(S);
722 	} else {
723 		okarg = "READ-ONLY";
724 	}
725 
726 	if (self->qresync.uidvalidity == MailboxState_getId(S)) {
727 		TRACE(TRACE_DEBUG, "process QRESYNC arguments");
728 		GTree *changed;
729 		const char *set;
730 		gboolean uid = self->use_uid;
731 		self->mailbox->modseq = self->qresync.modseq;
732 		self->use_uid = TRUE;
733 		// report EXPUNGEs
734 		char *out = NULL;
735 		GTree *vanished = MailboxState_getExpunged(self->mailbox->mbstate, &self->qresync, &out);
736 		TRACE(TRACE_DEBUG, "vanished [%u] messages", g_tree_nnodes(vanished));
737 		if (g_tree_nnodes(vanished))
738 			dbmail_imap_session_buff_printf(self, "* VANISHED (EARLIER) %s\r\n",
739 					out);
740 		g_free(out);
741 		g_tree_destroy(vanished);
742 
743 		// report FLAG changes
744 		if (self->qresync.known_uids) {
745 			set = p_string_str(self->qresync.known_uids);
746 		} else {
747 			set = "1:*";
748 		}
749 		changed = dbmail_mailbox_get_set(self->mailbox, set, TRUE);
750 		TRACE(TRACE_DEBUG, "messages changed since [%" PRIu64 "] [%u]",
751 				self->mailbox->modseq, g_tree_nnodes(changed));
752 
753 		g_tree_foreach(changed, (GTraverseFunc) _do_fetch_updates, D);
754 		g_tree_destroy(changed);
755 		// done reporting
756 		self->use_uid = uid;
757 
758 	}
759 
760 	dbmail_imap_session_buff_printf(self, "%s OK [%s] %s completed%s\r\n",
761 			self->tag,
762 			okarg,
763 			self->command,
764 			self->mailbox->condstore?", CONDSTORE is now enabled": "");
765 
766 	SESSION_RETURN;
767 }
768 
_ic_select(ImapSession * self)769 int _ic_select(ImapSession *self)
770 {
771 	if (!check_state_and_args(self, 1, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
772 	dm_thread_data_push((gpointer)self, _ic_select_enter, _ic_cb_leave, NULL);
773 	return 0;
774 }
775 
776 
_ic_examine(ImapSession * self)777 int _ic_examine(ImapSession *self)
778 {
779 	return _ic_select(self);
780 }
781 
782 
_ic_enable_enter(dm_thread_data * D)783 static void _ic_enable_enter(dm_thread_data *D)
784 {
785 	String_T capability;
786 	SESSION_GET;
787 
788 	while ((capability = self->args[self->args_idx++])) {
789 		const char *s = p_string_str(capability);
790 		gboolean changed = false;
791 		if (MATCH(s, "CONDSTORE") || MATCH(s, "QRESYNC")) {
792 			if (Capa_match(self->capa, s)) {
793 				if (MATCH(s, "CONDSTORE")) {
794 					if (! self->enabled.condstore)
795 						changed = true;
796 					self->enabled.condstore = 1;
797 				}
798 				if (MATCH(s, "QRESYNC")) {
799 					if (! self->enabled.qresync)
800 						changed = true;
801 					self->enabled.qresync = 1;
802 				}
803 				if (changed)
804 					dbmail_imap_session_buff_printf(self,
805 							"* ENABLED %s\r\n", s);
806 			}
807 		}
808 	}
809 
810 	SESSION_OK;
811 	SESSION_RETURN;
812 }
813 
_ic_enable(ImapSession * self)814 int _ic_enable(ImapSession *self)
815 {
816 	if (!check_state_and_args(self, 1, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
817 	dm_thread_data_push((gpointer)self, _ic_enable_enter, _ic_cb_leave, NULL);
818 	return 0;
819 }
820 
821 
822 /*
823  * _ic_create()
824  *
825  * create a mailbox
826  */
827 
_ic_create_enter(dm_thread_data * D)828 void _ic_create_enter(dm_thread_data *D)
829 {
830 	/* Create the mailbox and its parents. */
831 	int result;
832 	uint64_t mboxid;
833 	const char *message;
834 	SESSION_GET;
835 
836 	result = db_mailbox_create_with_parents(p_string_str(self->args[self->args_idx]), BOX_IMAP, self->userid, &mboxid, &message);
837 
838 	if (result > 0)
839 		dbmail_imap_session_buff_printf(self, "%s NO %s\r\n", self->tag, message);
840 	else if (result < 0)
841 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
842 	else
843 		SESSION_OK;
844 
845 	SESSION_RETURN;
846 }
847 
848 
_ic_create(ImapSession * self)849 int _ic_create(ImapSession *self)
850 {
851 	if (!check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
852 	dm_thread_data_push((gpointer)self, _ic_create_enter, _ic_cb_leave, NULL);
853 	return DM_SUCCESS;
854 }
855 
856 
857 /* _ic_delete()
858  *
859  * deletes a specified mailbox
860  */
imap_session_mailbox_check_acl(ImapSession * self,uint64_t idnr,ACLRight acl)861 static int imap_session_mailbox_check_acl(ImapSession * self, uint64_t idnr,  ACLRight acl)
862 {
863 	int result;
864 	MailboxState_T S = dbmail_imap_session_mbxinfo_lookup(self, idnr);
865 	if ((result = mailbox_check_acl(self, S, acl)) == 1)
866 		dbmail_imap_session_set_state(self,CLIENTSTATE_AUTHENTICATED);
867 	return result;
868 }
869 
_ic_delete_enter(dm_thread_data * D)870 void _ic_delete_enter(dm_thread_data *D)
871 {
872 	int result;
873 	uint64_t mailbox_idnr;
874 	GList *children = NULL;
875 	SESSION_GET;
876 	const char *mailbox = p_string_str(self->args[0]);
877 	unsigned nchildren = 0;
878 
879 	/* check if there is an attempt to delete inbox */
880 	if (MATCH(mailbox, "INBOX")) {
881 		dbmail_imap_session_buff_printf(self, "%s NO cannot delete special mailbox INBOX\r\n", self->tag);
882 		D->status=1;
883 		SESSION_RETURN;
884 	}
885 
886 	if (! (db_findmailbox(mailbox, self->userid, &mailbox_idnr)) ) {
887 		dbmail_imap_session_buff_printf(self, "%s NO mailbox doesn't exists\r\n", self->tag);
888 		D->status=1;
889 		SESSION_RETURN;
890 	}
891 
892 	/* Check if the user has ACL delete rights to this mailbox */
893 	if ((result = imap_session_mailbox_check_acl(self, mailbox_idnr, ACL_RIGHT_DELETE))) {
894 		D->status=result;
895 		SESSION_RETURN;
896 	}
897 
898 	/* check for children of this mailbox */
899 	if ((result = db_listmailboxchildren(mailbox_idnr, self->userid, &children)) == DM_EQUERY) {
900 		TRACE(TRACE_ERR, "[%p] cannot retrieve list of mailbox children", self);
901 		dbmail_imap_session_buff_printf(self, "* BYE dbase/memory error\r\n");
902 		D->status= -1;
903 		SESSION_RETURN;
904 	}
905 
906 	children = g_list_first(children);
907 	nchildren = g_list_length(children);
908 	g_list_destroy(children);
909 
910 	if (nchildren > 0) {
911 		TRACE(TRACE_DEBUG, "mailbox has children [%d]", nchildren);
912 		/* mailbox has inferior names; error if \noselect specified */
913 
914 
915 		result = db_isselectable(mailbox_idnr);
916 		if (result == FALSE) {
917 			dbmail_imap_session_buff_printf(self, "%s NO mailbox is non-selectable\r\n", self->tag);
918 			SESSION_RETURN;
919 		}
920 		if (result == DM_EQUERY) {
921 			dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
922 			D->status= -1;	/* fatal */
923 			SESSION_RETURN;
924 		}
925 
926 		/* mailbox has inferior names; remove all msgs and set noselect flag */
927 		{
928 			Connection_T c; volatile int t = DM_SUCCESS;
929 			uint64_t mailbox_size;
930 			MailboxState_T S = dbmail_imap_session_mbxinfo_lookup(self, mailbox_idnr);
931 
932 			if (! mailbox_is_writable(mailbox_idnr)) {
933 				D->status=DM_EQUERY;
934 				SESSION_RETURN;
935 			}
936 
937 			if (db_get_mailbox_size(mailbox_idnr, 0, &mailbox_size) == DM_EQUERY) {
938 				D->status=DM_EQUERY;
939 				SESSION_RETURN;
940 			}
941 
942 			/* update messages in this mailbox: mark as deleted (status MESSAGE_STATUS_PURGE) */
943 			c = db_con_get();
944 			TRY
945 				db_begin_transaction(c);
946 				db_exec(c, "UPDATE %smessages SET status=%d WHERE mailbox_idnr = %" PRIu64 "", DBPFX, MESSAGE_STATUS_PURGE, mailbox_idnr);
947 				db_exec(c, "UPDATE %smailboxes SET no_select = 1 WHERE mailbox_idnr = %" PRIu64 "", DBPFX, mailbox_idnr);
948 				db_commit_transaction(c);
949 			CATCH(SQLException)
950 				LOG_SQLERROR;
951 				db_rollback_transaction(c);
952 				t = DM_EQUERY;
953 			FINALLY
954 				db_con_close(c);
955 			END_TRY;
956 
957 			if (t == DM_EQUERY) {
958 				D->status=t;
959 				SESSION_RETURN;
960 			}
961 
962 			MailboxState_setNoSelect(S, TRUE);
963 			db_mailbox_seq_update(mailbox_idnr, 0);
964 			if (! dm_quota_user_dec(self->userid, mailbox_size)) {
965 				D->status=DM_EQUERY;
966 				SESSION_RETURN;
967 			}
968 		}
969 
970 		/* check if this was the currently selected mailbox */
971 		if (self->mailbox && self->mailbox->mbstate && (mailbox_idnr == MailboxState_getId(self->mailbox->mbstate)))
972 			dbmail_imap_session_set_state(self,CLIENTSTATE_AUTHENTICATED);
973 
974 		/* ok done */
975 		SESSION_OK;
976 		SESSION_RETURN;
977 	}
978 
979 	/* ok remove mailbox */
980 	if (db_delete_mailbox(mailbox_idnr, 0, 1)) {
981 		dbmail_imap_session_buff_printf(self,"%s NO DELETE failed\r\n", self->tag);
982 		D->status=DM_EGENERAL;
983 		SESSION_RETURN;
984 	}
985 
986 	/* check if this was the currently selected mailbox */
987 	if (self->mailbox && self->mailbox->mbstate && (mailbox_idnr == MailboxState_getId(self->mailbox->mbstate)))
988 		dbmail_imap_session_set_state(self, CLIENTSTATE_AUTHENTICATED);
989 
990 	SESSION_OK;
991 	SESSION_RETURN;
992 }
993 
_ic_delete(ImapSession * self)994 int _ic_delete(ImapSession *self)
995 {
996 	if (!check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
997 	dm_thread_data_push((gpointer)self, _ic_delete_enter, _ic_cb_leave, NULL);
998 	return 0;
999 }
1000 /* _ic_rename()
1001  *
1002  * renames a specified mailbox
1003  */
mailbox_rename(MailboxState_T M,const char * newname)1004 static int mailbox_rename(MailboxState_T M, const char *newname)
1005 {
1006 	if ( (db_setmailboxname(MailboxState_getId(M), newname)) == DM_EQUERY) return DM_EQUERY;
1007 	MailboxState_setName(M, newname);
1008 	return DM_SUCCESS;
1009 }
_ic_rename_enter(dm_thread_data * D)1010 void _ic_rename_enter(dm_thread_data *D)
1011 {
1012 	SESSION_GET;
1013 	GList *children = NULL;
1014 	uint64_t mboxid, newmboxid;
1015 	uint64_t parentmboxid = 0;
1016 	size_t oldnamelen;
1017 	int i, result;
1018 	MailboxState_T M;
1019 	const char *oldname, *newname;
1020 
1021 	oldname = p_string_str(self->args[0]);
1022 	newname = p_string_str(self->args[1]);
1023 
1024 
1025 	if (! (db_findmailbox(oldname, self->userid, &mboxid))) {
1026 		dbmail_imap_session_buff_printf(self, "%s NO mailbox does not exist\r\n", self->tag);
1027 		D->status = 1;
1028 		SESSION_RETURN;
1029 	}
1030 
1031 	/* check if new name is valid */
1032         if (!checkmailboxname(newname)) {
1033 	        dbmail_imap_session_buff_printf(self, "%s NO new mailbox name contains invalid characters\r\n", self->tag);
1034 		D->status = 1;
1035 		SESSION_RETURN;
1036         }
1037 
1038 	if ((db_findmailbox(newname, self->userid, &newmboxid))) {
1039 		dbmail_imap_session_buff_printf(self, "%s NO new mailbox already exists\r\n", self->tag);
1040 		D->status = 1;
1041 		SESSION_RETURN;
1042 	}
1043 
1044 	oldnamelen = strlen(oldname);
1045 
1046 	/* check if new name would invade structure as in
1047 	 * test (exists)
1048 	 * rename test test/testing
1049 	 * would create test/testing but delete test
1050 	 */
1051 	if (strncasecmp(oldname, newname, (int) oldnamelen) == 0 &&
1052 	    strlen(newname) > oldnamelen && newname[oldnamelen] == '/') {
1053 		dbmail_imap_session_buff_printf(self, "%s NO new mailbox would invade mailbox structure\r\n", self->tag);
1054 		D->status = 1;
1055 		SESSION_RETURN;
1056 	}
1057 
1058 	/* check if structure of new name is valid */
1059 	/* i.e. only last part (after last '/' can be nonexistent) */
1060 	for (i = strlen(newname) - 1; i >= 0 && newname[i] != '/'; i--);
1061 	char tmpname[IMAP_MAX_MAILBOX_NAMELEN];
1062 	memset(tmpname, 0, sizeof(tmpname));
1063 	g_strlcpy(tmpname, newname, IMAP_MAX_MAILBOX_NAMELEN);
1064 	if (i >= 0) {
1065 		tmpname[i] = '\0';	/* note: original char was '/' */
1066 
1067 		if (! db_findmailbox(tmpname, self->userid, &parentmboxid)) {
1068 			/* parent mailbox does not exist */
1069 			dbmail_imap_session_buff_printf(self, "%s NO new mailbox would invade mailbox structure\r\n", self->tag);
1070 			D->status = 1;
1071 			SESSION_RETURN;
1072 		}
1073 	}
1074 
1075 	/* Check if the user has ACL delete rights to old name,
1076 	 * and create rights to the parent of the new name, or
1077 	 * if the user just owns both mailboxes. */
1078 	if ((result = imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_DELETE))) {
1079 		D->status = result;
1080 		SESSION_RETURN;
1081 	}
1082 
1083 	if (!parentmboxid) {
1084 		TRACE(TRACE_DEBUG, "[%p] Destination is a top-level mailbox; not checking right to CREATE.", self);
1085 	} else {
1086 		TRACE(TRACE_DEBUG, "[%p] Checking right to CREATE under [%" PRIu64 "]", self, parentmboxid);
1087 		if ((result = imap_session_mailbox_check_acl(self, parentmboxid, ACL_RIGHT_CREATE))) {
1088 			D->status = result;
1089 			SESSION_RETURN;
1090 		}
1091 
1092 		TRACE(TRACE_DEBUG, "[%p] We have the right to CREATE under [%" PRIu64 "]", self, parentmboxid);
1093 	}
1094 
1095 	/* check if it is INBOX to be renamed */
1096 	if (MATCH(oldname, "INBOX")) {
1097 		/* ok, renaming inbox */
1098 		/* this means creating a new mailbox and moving all the INBOX msgs to the new mailbox */
1099 		/* inferior names of INBOX are left unchanged */
1100 		result = db_createmailbox(newname, self->userid, &newmboxid);
1101 		if (result == -1) {
1102 			dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
1103 			D->status = DM_EQUERY;
1104 			SESSION_RETURN;
1105 		}
1106 
1107 		result = db_movemsg(newmboxid, mboxid);
1108 		if (result == -1) {
1109 			dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
1110 			D->status = result;
1111 			SESSION_RETURN;
1112 		}
1113 
1114 		SESSION_OK;
1115 		SESSION_RETURN;
1116 	}
1117 
1118 	/* check for inferior names */
1119 	result = db_listmailboxchildren(mboxid, self->userid, &children);
1120 	if (result == -1) {
1121 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
1122 		D->status = result;
1123 		SESSION_RETURN;
1124 	}
1125 
1126 	/* replace name for each child */
1127 	children = g_list_first(children);
1128 	while (children) {
1129 		char realnewname[IMAP_MAX_MAILBOX_NAMELEN];
1130 		uint64_t childid = *(uint64_t *)children->data;
1131 		const char *tname;
1132 		M = dbmail_imap_session_mbxinfo_lookup(self, childid);
1133 		tname = MailboxState_getName(M);
1134 
1135 		g_snprintf(realnewname, IMAP_MAX_MAILBOX_NAMELEN, "%s%s", newname, &tname[oldnamelen]);
1136 		if ((mailbox_rename(M, realnewname)) != DM_SUCCESS) {
1137 			dbmail_imap_session_buff_printf(self, "* BYE error renaming mailbox\r\n");
1138 			g_list_destroy(children);
1139 			D->status = DM_EGENERAL;
1140 			SESSION_RETURN;
1141 		}
1142 		if (! g_list_next(children)) break;
1143 		children = g_list_next(children);
1144 	}
1145 
1146 	if (children) g_list_destroy(children);
1147 
1148 	/* now replace name */
1149 	M = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
1150 	if ((mailbox_rename(M, newname)) != DM_SUCCESS) {
1151 		dbmail_imap_session_buff_printf(self, "* BYE error renaming mailbox\r\n");
1152 		D->status = DM_EGENERAL;
1153 		SESSION_RETURN;
1154 	}
1155 
1156 	SESSION_OK;
1157 	SESSION_RETURN;
1158 }
1159 
_ic_rename(ImapSession * self)1160 int _ic_rename(ImapSession *self)
1161 {
1162 	if (!check_state_and_args(self, 2, 2, CLIENTSTATE_AUTHENTICATED)) return 1;
1163 	dm_thread_data_push((gpointer)self, _ic_rename_enter, _ic_cb_leave, NULL);
1164 	return 0;
1165 }
1166 
1167 
1168 
1169 /*
1170  * _ic_subscribe()
1171  *
1172  * subscribe to a specified mailbox
1173  */
_ic_subscribe_enter(dm_thread_data * D)1174 void _ic_subscribe_enter(dm_thread_data *D)
1175 {
1176 	SESSION_GET;
1177 	uint64_t mboxid;
1178 	int result = 0;
1179 	const char *mailbox = p_string_str(self->args[0]);
1180 
1181 	if (! (db_findmailbox(mailbox, self->userid, &mboxid))) {
1182 		dbmail_imap_session_buff_printf(self, "%s OK %s on mailbox that does not exist\r\n", self->tag, self->command);
1183 		SESSION_RETURN;
1184 	}
1185 
1186 	/* check for the lookup-right. RFC is unclear about which right to
1187 	   use, so I guessed it should be lookup */
1188 
1189 	if (imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_LOOKUP)) {
1190 		D->status = 1;
1191 		SESSION_RETURN;
1192 	}
1193 
1194 	if (self->command_type == IMAP_COMM_SUBSCRIBE) {
1195 		result = db_subscribe(mboxid, self->userid);
1196 	} else {
1197 		result = db_unsubscribe(mboxid, self->userid);
1198 	}
1199 
1200 	if (result == DM_EQUERY) {
1201 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
1202 		D->status = DM_EQUERY;
1203 		SESSION_RETURN;
1204 	}
1205 
1206 	SESSION_OK;
1207 	SESSION_RETURN;
1208 
1209 }
_ic_subscribe(ImapSession * self)1210 int _ic_subscribe(ImapSession *self)
1211 {
1212 	if (!check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
1213 	dm_thread_data_push((gpointer)self, _ic_subscribe_enter, _ic_cb_leave, NULL);
1214 	return 0;
1215 }
1216 
_ic_unsubscribe(ImapSession * self)1217 int _ic_unsubscribe(ImapSession *self)
1218 {
1219 	return _ic_subscribe(self);
1220 }
1221 
1222 /**
1223  * Write out one element of found folders (contains found hierarchy too)
1224  *
1225  * This is called for each found folder in a loop.
1226  */
_ic_list_write_out_found_folder(gpointer UNUSED key,MailboxState_T M,ImapSession * self)1227 static gboolean _ic_list_write_out_found_folder(gpointer UNUSED key, MailboxState_T M, ImapSession *self)
1228 {
1229 	GList *plist = NULL;
1230 	char *pstring = NULL;
1231 	if (MailboxState_noSelect(M))
1232 		plist = g_list_append(plist, "\\noselect");
1233 	if (MailboxState_noInferiors(M))
1234 		plist = g_list_append(plist, "\\noinferiors");
1235 	if (MailboxState_noChildren(M))
1236 		plist = g_list_append(plist, "\\hasnochildren");
1237 	else
1238 		plist = g_list_append(plist, "\\haschildren");
1239 
1240 	/* show */
1241 	pstring = dbmail_imap_plist_as_string(plist);
1242 	dbmail_imap_session_buff_printf(self, "* %s %s \"%s\" \"%s\"\r\n", self->command,
1243 			pstring, MAILBOX_SEPARATOR, MailboxState_getName(M));
1244 
1245 	g_list_free(g_list_first(plist));
1246 	g_free(pstring);
1247 
1248 	return FALSE;
1249 }
1250 
free_mailboxstate(void * data)1251 void free_mailboxstate(void *data)
1252 {
1253 	MailboxState_T M = (MailboxState_T)data;
1254 	MailboxState_free(&M);
1255 }
1256 
1257 /*
1258  * _ic_list()
1259  *
1260  * executes a list command
1261  */
_ic_list_enter(dm_thread_data * D)1262 void _ic_list_enter(dm_thread_data *D)
1263 {
1264 	SESSION_GET;
1265 	int list_is_lsub = 0;
1266 	GList *children = NULL;
1267 	// store the found real folders
1268 	GTree *found_folders = NULL;
1269 	// found hierarchy elements should have lower priority than real folders
1270 	// that's why store them separately in another GTree
1271 	// this is to not to let them mask out real folders if they are found first
1272 	GTree *found_hierarchy = NULL;
1273 	MailboxState_T M = NULL;
1274 	unsigned i;
1275 	char pattern[255];
1276 	char mailbox[IMAP_MAX_MAILBOX_NAMELEN];
1277 	const char *refname;
1278 
1279 	/* check if self->args are both empty strings, i.e. A001 LIST "" ""
1280 	   this has special meaning; show root & delimiter */
1281 	if (p_string_len(self->args[0]) == 0 && p_string_len(self->args[1]) == 0) {
1282 		dbmail_imap_session_buff_printf(self, "* %s (\\NoSelect) \"/\" \"\"\r\n", self->command);
1283 		SESSION_OK;
1284 		SESSION_RETURN;
1285 	}
1286 
1287 	/* check the reference name, should contain only accepted mailboxname chars */
1288 	refname = p_string_str(self->args[0]);
1289 	for (i = 0; refname[i]; i++) {
1290 		if (index(AcceptedMailboxnameChars, refname[i]) == NULL) {
1291 			dbmail_imap_session_buff_printf(self, "%s BAD reference name contains invalid characters\r\n", self->tag);
1292 			D->status = 1;
1293 			SESSION_RETURN;
1294 		}
1295 	}
1296 	memset(pattern, 0, sizeof(pattern));
1297 	g_strlcat(pattern, refname, 255);
1298 	g_strlcat(pattern, p_string_str(self->args[1]), 255);
1299 
1300 	TRACE(TRACE_INFO, "[%p] search with pattern: [%s]", self, pattern);
1301 
1302 	if (self->command_type == IMAP_COMM_LSUB) list_is_lsub = 1;
1303 
1304 	D->status = db_findmailbox_by_regex(self->userid, pattern, &children, list_is_lsub);
1305 	if (D->status == -1) {
1306 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
1307 		SESSION_RETURN;
1308 	} else if (D->status == 1) {
1309 		dbmail_imap_session_buff_printf(self, "%s BAD invalid pattern specified\r\n", self->tag);
1310 		SESSION_RETURN;
1311 	}
1312 
1313 	found_folders = g_tree_new_full((GCompareDataFunc)dm_strcmpdata,NULL,g_free,free_mailboxstate);
1314 	found_hierarchy = g_tree_new_full((GCompareDataFunc)dm_strcmpdata,NULL,g_free,free_mailboxstate);
1315 
1316 	while ((! D->status) && children) {
1317 		gboolean show = FALSE;
1318 		// determine whether the found element is part of a hierarchy
1319 		// if yes, it will be added to a separate tree (to have lower priority)
1320 		gboolean hierarchy_element = FALSE;
1321 
1322 		memset(&mailbox, 0, IMAP_MAX_MAILBOX_NAMELEN);
1323 
1324 		uint64_t mailbox_id = *(uint64_t *)children->data;
1325 		if ( (D->status = db_getmailboxname(mailbox_id, self->userid, mailbox)) != DM_SUCCESS) {
1326 			break;
1327 		}
1328 
1329 		// avoid fully loading mailbox here
1330 		M = MailboxState_new(self->pool, 0);
1331 		MailboxState_setId(M, mailbox_id);
1332 		MailboxState_info(M);
1333 		MailboxState_setName(M, mailbox);
1334 
1335 		/* Enforce match of mailbox to pattern. */
1336 		TRACE(TRACE_DEBUG,"test if [%s] matches [%s]", mailbox, pattern);
1337 		if (! listex_match(pattern, mailbox, MAILBOX_SEPARATOR, 0)) {
1338 			TRACE(TRACE_DEBUG,"mailbox [%s] doesn't match pattern [%s]", mailbox, pattern);
1339 			if (g_str_has_suffix(pattern,"%")) {
1340 				TRACE(TRACE_DEBUG,"searching for matching hierarchy");
1341 				/*
1342 				   If the "%" wildcard is the last character of a mailbox name argument, matching levels
1343 				   of hierarchy are also returned.  If these levels of hierarchy are not also selectable
1344 				   mailboxes, they are returned with the \Noselect mailbox name attribute
1345 				   */
1346 
1347 				// split mailbox name by delimiters
1348 				char *m = NULL, **p = g_strsplit(mailbox,MAILBOX_SEPARATOR,0);
1349 				int l = g_strv_length(p);
1350 				// cut each part off from the name in each iteration
1351 				// and check whether that partial name matches
1352 				// that indicates a matching hierarchy
1353 				while (l > 1) {
1354 					if (p[l]) {
1355 						g_free(p[l]);
1356 						p[l] = NULL;
1357 					}
1358 					m = g_strjoinv(MAILBOX_SEPARATOR, p);
1359 					if (listex_match(pattern, m, MAILBOX_SEPARATOR, 0)) {
1360 						TRACE(TRACE_DEBUG,"partial hierarchy [%s] matches [%s]", m, pattern);
1361 
1362 						MailboxState_setName(M, m);
1363 						MailboxState_setNoSelect(M, TRUE);
1364 						MailboxState_setNoChildren(M, FALSE);
1365 						show = TRUE;
1366 						hierarchy_element = TRUE;
1367 						g_free(m);
1368 						break;
1369 					} else {
1370 						TRACE(TRACE_DEBUG,"partial hierarchy [%s] doesn't match [%s]", m, pattern);
1371 					}
1372 					g_free(m);
1373 					l--;
1374 				}
1375 				g_strfreev(p);
1376 			}
1377 		} else {
1378 			TRACE(TRACE_DEBUG,"[%s] does match [%s]", mailbox, pattern);
1379 			show = TRUE;
1380 		}
1381 
1382 		// add the result if either:
1383 		//  - it's not a hierarchy element (so real folder) AND it cannot be found among real folders
1384 		//  - it's a hierarchy element AND it cannot be found among hierarchy elements
1385 		if (show && MailboxState_getName(M)) {
1386 			char *s = g_strdup(MailboxState_getName(M));
1387 			if (! hierarchy_element)
1388 				g_tree_insert(found_folders, (gpointer)s, M);
1389 			else
1390 				g_tree_insert(found_hierarchy, (gpointer)s, M);
1391 		} else {
1392 			MailboxState_free(&M);
1393 		}
1394 
1395 		if (! g_list_next(children)) break;
1396 		children = g_list_next(children);
1397 	}
1398 
1399 	TRACE(TRACE_DEBUG,"copying found hierarchy to found_folders");
1400 	g_tree_merge(found_folders, found_hierarchy, IST_SUBSEARCH_OR);
1401 
1402 	TRACE(TRACE_DEBUG,"writing out found_folders");
1403 	g_tree_foreach(found_folders, (GTraverseFunc)_ic_list_write_out_found_folder, self);
1404 
1405 	if (found_hierarchy) g_tree_destroy(found_hierarchy);
1406 	if (found_folders) g_tree_destroy(found_folders);
1407 	if (children) g_list_destroy(children);
1408 
1409 	if (! D->status) dbmail_imap_session_buff_printf(self, "%s OK %s completed\r\n", self->tag, self->command);
1410 
1411 	SESSION_RETURN;
1412 }
1413 
_ic_list(ImapSession * self)1414 int _ic_list(ImapSession *self)
1415 {
1416 
1417 	if (!check_state_and_args(self, 2, 2, CLIENTSTATE_AUTHENTICATED)) return 1;
1418 	dm_thread_data_push((gpointer)self, _ic_list_enter, _ic_cb_leave, NULL);
1419 	return 0;
1420 }
1421 
1422 /*
1423  * _ic_lsub()
1424  *
1425  * list subscribed mailboxes
1426  */
_ic_lsub(ImapSession * self)1427 int _ic_lsub(ImapSession *self)
1428 {
1429 	return _ic_list(self);
1430 }
1431 
1432 
1433 /*
1434  * _ic_status()
1435  *
1436  * inquire the status of a mailbox
1437  */
_ic_status_enter(dm_thread_data * D)1438 static void _ic_status_enter(dm_thread_data *D)
1439 {
1440 	SESSION_GET;
1441 	MailboxState_T M;
1442 	uint64_t id;
1443 	int i, endfound, result;
1444 	GList *plst = NULL;
1445 	gchar *pstring, *astring;
1446 
1447 	if (p_string_str(self->args[1])[0] != '(') {
1448 		dbmail_imap_session_buff_printf(self, "%s BAD argument list should be parenthesed\r\n", self->tag);
1449 		D->status = 1;
1450 		SESSION_RETURN;
1451 	}
1452 
1453 	/* check final arg: should be ')' and no new '(' in between */
1454 	for (i = 2, endfound = 0; self->args[i]; i++) {
1455 		if (p_string_str(self->args[i])[0] == ')') {
1456 			endfound = i;
1457 			break;
1458 		} else if (p_string_str(self->args[i])[0] == '(') {
1459 			dbmail_imap_session_buff_printf(self, "%s BAD too many parentheses specified\r\n", self->tag);
1460 			D->status = 1;
1461 			SESSION_RETURN;
1462 		}
1463 	}
1464 
1465 	if (endfound == 2) {
1466 		dbmail_imap_session_buff_printf(self, "%s BAD argument list empty\r\n", self->tag);
1467 		D->status = 1;
1468 		SESSION_RETURN;
1469 	}
1470 	if (self->args[endfound + 1]) {
1471 		dbmail_imap_session_buff_printf(self, "%s BAD argument list too long\r\n", self->tag);
1472 		D->status = 1;
1473 		SESSION_RETURN;
1474 	}
1475 
1476 	/* check if mailbox exists */
1477 	if (! db_findmailbox(p_string_str(self->args[0]), self->userid, &id)) {
1478 		/* create missing INBOX for this authenticated user */
1479 		if ((! id ) && (MATCH(p_string_str(self->args[0]), "INBOX"))) {
1480 			TRACE(TRACE_INFO, "[%p] Auto-creating INBOX for user id [%" PRIu64 "]", self, self->userid);
1481 			db_createmailbox("INBOX", self->userid, &id);
1482 		}
1483 		if (! id) {
1484 			dbmail_imap_session_buff_printf(self, "%s NO specified mailbox does not exist\r\n", self->tag);
1485 			D->status = 1;
1486 			SESSION_RETURN;
1487 		}
1488 	}
1489 
1490 	// avoid fully loading mailbox here
1491 	M = MailboxState_new(self->pool, 0);
1492 	MailboxState_setId(M, id);
1493 	if (MailboxState_info(M)) {
1494 		dbmail_imap_session_buff_printf(self, "%s NO specified mailbox does not exist\r\n", self->tag);
1495 		D->status = 1;
1496 		MailboxState_free(&M);
1497 		SESSION_RETURN;
1498 	}
1499 
1500 	MailboxState_setName(M, p_string_str(self->args[0]));
1501 	MailboxState_count(M);
1502 
1503 	if ((result = mailbox_check_acl(self, M, ACL_RIGHT_READ))) {
1504 		D->status = result;
1505 		MailboxState_free(&M);
1506 		SESSION_RETURN;
1507 	}
1508 
1509 	for (i = 2; self->args[i]; i++) {
1510 		const char *attr = p_string_str(self->args[i]);
1511 		if (MATCH(attr, "messages"))
1512 			plst = g_list_append_printf(plst,"MESSAGES %u", MailboxState_getExists(M));
1513 		else if (MATCH(attr, "recent"))
1514 			plst = g_list_append_printf(plst,"RECENT %u", MailboxState_getRecent(M));
1515 		else if (MATCH(attr, "unseen"))
1516 			plst = g_list_append_printf(plst,"UNSEEN %u", MailboxState_getUnseen(M));
1517 		else if (MATCH(attr, "uidnext"))
1518 			plst = g_list_append_printf(plst,"UIDNEXT %" PRIu64 "", MailboxState_getUidnext(M));
1519 		else if (MATCH(attr, "uidvalidity"))
1520 			plst = g_list_append_printf(plst,"UIDVALIDITY %" PRIu64 "", MailboxState_getId(M));
1521 		else if (Capa_match(self->capa, "CONDSTORE") && MATCH(attr, "highestmodseq")) {
1522 			plst = g_list_append_printf(plst,"HIGHESTMODSEQ %" PRIu64, MailboxState_getSeq(M));
1523 			self->enabled.condstore = true;
1524 		}
1525 		else if (MATCH(attr, ")"))
1526 			break;
1527 		else {
1528 			dbmail_imap_session_buff_printf(self, "\r\n%s BAD option '%s' specified\r\n",
1529 				self->tag, attr);
1530 			D->status = 1;
1531 			MailboxState_free(&M);
1532 			SESSION_RETURN;
1533 		}
1534 	}
1535 	astring = dbmail_imap_astring_as_string(p_string_str(self->args[0]));
1536 	pstring = dbmail_imap_plist_as_string(plst);
1537 	g_list_destroy(plst);
1538 
1539 	dbmail_imap_session_buff_printf(self, "* STATUS %s %s\r\n", astring, pstring);
1540 	g_free(astring); g_free(pstring);
1541 	MailboxState_free(&M);
1542 
1543 	SESSION_OK;
1544 	SESSION_RETURN;
1545 }
1546 
_ic_status(ImapSession * self)1547 int _ic_status(ImapSession *self)
1548 {
1549 	if (!check_state_and_args(self, 3, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
1550 	dm_thread_data_push((gpointer)self, _ic_status_enter, _ic_cb_leave, NULL);
1551 	return 0;
1552 }
1553 
1554 /* _ic_idle
1555  *
1556  */
1557 
1558 #define IDLE_TIMEOUT 30
_ic_idle(ImapSession * self)1559 int _ic_idle(ImapSession *self)
1560 {
1561 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
1562 
1563 	int idle_timeout = IDLE_TIMEOUT;
1564 	Field_T val;
1565 
1566 	ci_cork(self->ci);
1567 	GETCONFIGVALUE("idle_timeout", "IMAP", val);
1568 	if ( strlen(val) && (idle_timeout = atoi(val)) <= 0 ) {
1569 		TRACE(TRACE_ERR, "[%p] illegal value for idle_timeout [%s]", self, val);
1570 		idle_timeout = IDLE_TIMEOUT;
1571 	}
1572 
1573 	TRACE(TRACE_DEBUG,"[%p] start IDLE [%s]", self, self->tag);
1574 	self->command_state = IDLE;
1575 	dbmail_imap_session_buff_printf(self, "+ idling\r\n");
1576 	dbmail_imap_session_mailbox_status(self,TRUE);
1577 	dbmail_imap_session_buff_flush(self);
1578 
1579 	self->ci->timeout.tv_sec = idle_timeout;
1580 	ci_uncork(self->ci);
1581 
1582 	return 0;
1583 }
1584 
1585 /* _ic_append()
1586  *
1587  * append a message to a mailbox
1588  */
1589 
_ic_append_enter(dm_thread_data * D)1590 void _ic_append_enter(dm_thread_data *D)
1591 {
1592 	uint64_t mboxid, message_id = 0;
1593 	int i, j, result;
1594 	const char *internal_date = NULL;
1595 	int flaglist[IMAP_NFLAGS], flagcount = 0;
1596 	GList *keywords = NULL;
1597 	MailboxState_T M;
1598 	SESSION_GET;
1599 	const char *message;
1600 	gboolean recent = TRUE;
1601 	MessageInfo *info;
1602 
1603 	memset(flaglist,0,sizeof(flaglist));
1604 
1605 	/* find the mailbox to place the message */
1606 	if (! db_findmailbox(p_string_str(self->args[0]), self->userid, &mboxid)) {
1607 		if ((strcasecmp(p_string_str(self->args[0]), "INBOX")==0)) {
1608 			int err = db_createmailbox("INBOX", self->userid, &mboxid);
1609 			TRACE(TRACE_INFO, "[%p] [%d] Auto-creating INBOX for user id [%" PRIu64 "]",
1610 					self, err, self->userid);
1611 		}
1612 
1613 		if (! mboxid) {
1614 			dbmail_imap_session_buff_printf(self, "%s NO [TRYCREATE]\r\n", self->tag);
1615 			D->status = 1;
1616 			SESSION_RETURN;
1617 		}
1618 	}
1619 
1620 	M = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
1621 
1622 	/* check if user has right to append to  mailbox */
1623 	if ((result = imap_session_mailbox_check_acl(self, mboxid, ACL_RIGHT_INSERT))) {
1624 		D->status = result;
1625 		SESSION_RETURN;
1626 	}
1627 
1628 	i = 1;
1629 
1630 	/* check if a flag list has been specified */
1631 	if (p_string_str(self->args[i])[0] == '(') {
1632 		/* ok fetch the flags specified */
1633 		TRACE(TRACE_DEBUG, "[%p] flag list found:", self);
1634 
1635 		i++;
1636 		while (self->args[i] && p_string_str(self->args[i])[0] != ')') {
1637 			const char *arg = p_string_str(self->args[i]);
1638 			TRACE(TRACE_DEBUG, "[%p] [%s]", self, arg);
1639 			for (j = 0; j < IMAP_NFLAGS; j++) {
1640 				if (MATCH(arg, imap_flag_desc_escaped[j])) {
1641 					flaglist[j] = 1;
1642 					flagcount++;
1643 					break;
1644 				}
1645 			}
1646 			if (j == IMAP_NFLAGS) {
1647 				TRACE(TRACE_DEBUG,"[%p] found keyword [%s]", self, arg);
1648 				keywords = g_list_append(keywords,g_strdup(arg));
1649 				flagcount++;
1650 			}
1651 
1652 			i++;
1653 		}
1654 
1655 		i++;
1656 		TRACE(TRACE_DEBUG, "[%p] )", self);
1657 	}
1658 
1659 	if (!self->args[i]) {
1660 		TRACE(TRACE_INFO, "[%p] unexpected end of arguments", self);
1661 		dbmail_imap_session_buff_printf(self, "%s BAD invalid arguments specified to APPEND\r\n", self->tag);
1662 		D->status = 1;
1663 		SESSION_RETURN;
1664 	}
1665 
1666 	/** check ACL's for STORE */
1667 	if (flaglist[IMAP_FLAG_SEEN] == 1) {
1668 		if ((result = mailbox_check_acl(self, M, ACL_RIGHT_SEEN))) {
1669 			D->status = result;
1670 			SESSION_RETURN;
1671 		}
1672 	}
1673 	if (flaglist[IMAP_FLAG_DELETED] == 1) {
1674 		if ((result = mailbox_check_acl(self, M, ACL_RIGHT_DELETED))) {
1675 			D->status = result;
1676 			SESSION_RETURN;
1677 		}
1678 	}
1679 	if (flaglist[IMAP_FLAG_ANSWERED] == 1 ||
1680 	    flaglist[IMAP_FLAG_FLAGGED] == 1 ||
1681 	    flaglist[IMAP_FLAG_RECENT] == 1 ||
1682 	    flaglist[IMAP_FLAG_DRAFT] == 1 ||
1683 	    g_list_length(keywords) > 0) {
1684 		if ((result = mailbox_check_acl(self, M, ACL_RIGHT_WRITE))) {
1685 			D->status = result;
1686 			SESSION_RETURN;
1687 		}
1688 	}
1689 
1690 
1691 	/* there could be a literal date here, check if the next argument exists
1692 	 * if so, assume this is the literal date.
1693 	 */
1694 	if (self->args[i + 1]) {
1695 		internal_date = p_string_str(self->args[i]);
1696 		i++;
1697 		TRACE(TRACE_DEBUG, "[%p] internal date [%s] found, next arg [%s]",
1698 				self, internal_date, p_string_str(self->args[i]));
1699 	}
1700 
1701 	if (self->state == CLIENTSTATE_SELECTED && self->mailbox->id == mboxid) {
1702 		recent = FALSE;
1703 	}
1704 
1705 	message = p_string_str(self->args[i]);
1706 
1707 	D->status = db_append_msg(message, mboxid, self->userid, (char *)internal_date, &message_id, recent);
1708 
1709 	switch (D->status) {
1710 	case -1:
1711 		TRACE(TRACE_ERR, "[%p] error appending msg", self);
1712 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error storing message\r\n");
1713 		D->status=1;
1714 		SESSION_RETURN;
1715 		break;
1716 
1717 	case -2:
1718 		TRACE(TRACE_INFO, "[%p] quotum would exceed", self);
1719 		dbmail_imap_session_buff_printf(self, "%s NO not enough quotum left\r\n", self->tag);
1720 		D->status=1;
1721 		SESSION_RETURN;
1722 		break;
1723 
1724 	case TRUE:
1725 		TRACE(TRACE_ERR, "[%p] faulty msg", self);
1726 		dbmail_imap_session_buff_printf(self, "%s NO invalid message specified\r\n", self->tag);
1727 		SESSION_RETURN;
1728 		break;
1729 	case FALSE:
1730 		if (flagcount) {
1731 			if (db_set_msgflag(message_id, flaglist, keywords, IMAPFA_ADD, 0, NULL) < 0)
1732 				TRACE(TRACE_ERR, "[%p] error setting flags for message [%" PRIu64 "]", self, message_id);
1733 			else
1734 				db_mailbox_seq_update(mboxid, message_id);
1735 		}
1736 		break;
1737 	}
1738 
1739 	if (message_id && self->state == CLIENTSTATE_SELECTED && self->mailbox->id == mboxid) {
1740 		dbmail_imap_session_mailbox_status(self, TRUE);
1741 	}
1742 
1743 	// MessageInfo
1744 	info = g_new0(MessageInfo,1);
1745 	info->uid = message_id;
1746 	info->mailbox_id = mboxid;
1747 	for (flagcount = 0; flagcount < IMAP_NFLAGS; flagcount++)
1748 		info->flags[flagcount] = flaglist[flagcount];
1749 	info->flags[IMAP_FLAG_RECENT] = 1;
1750 	strncpy(info->internaldate,
1751 			internal_date?internal_date:"01-Jan-1970 00:00:01 +0100",
1752 		       	IMAP_INTERNALDATE_LEN-1);
1753 	info->rfcsize = strlen(message);
1754 	info->keywords = keywords;
1755 
1756 	M = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
1757 	MailboxState_addMsginfo(M, message_id, info);
1758 
1759 	char buffer[1024];
1760 	memset(buffer, 0, sizeof(buffer));
1761 	g_snprintf(buffer, 1023, "APPENDUID %" PRIu64 " %" PRIu64, mboxid, message_id);
1762 	SESSION_OK_WITH_RESP_CODE(buffer);
1763 	SESSION_RETURN;
1764 }
1765 
_ic_append(ImapSession * self)1766 int _ic_append(ImapSession *self)
1767 {
1768 	if (!check_state_and_args(self, 2, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
1769 	dm_thread_data_push((gpointer)self, _ic_append_enter, _ic_cb_leave, NULL);
1770 	return 0;
1771 }
1772 
1773 /*
1774  * SELECTED-STATE COMMANDS
1775  * sort, check, close, expunge, search, fetch, store, copy, uid
1776  */
1777 
1778 /* _ic_check()
1779  *
1780  * request a checkpoint for the selected mailbox
1781  * (equivalent to NOOP)
1782  */
_ic_check_enter(dm_thread_data * D)1783 static void _ic_check_enter(dm_thread_data *D)
1784 {
1785 	SESSION_GET;
1786 	dbmail_imap_session_mailbox_status(self, TRUE);
1787 	SESSION_OK;
1788 	SESSION_RETURN;
1789 }
1790 
_ic_check(ImapSession * self)1791 int _ic_check(ImapSession *self)
1792 {
1793 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_SELECTED)) return 1;
1794 	dm_thread_data_push((gpointer)self, _ic_check_enter, _ic_cb_leave, NULL);
1795 	return 0;
1796 }
1797 
1798 
1799 /* _ic_close()
1800  *
1801  * expunge deleted messages from selected mailbox & return to AUTH state
1802  * do not show expunge-output
1803  */
_ic_close_enter(dm_thread_data * D)1804 static void _ic_close_enter(dm_thread_data *D)
1805 {
1806 	SESSION_GET;
1807 	int result = acl_has_right(self->mailbox->mbstate, self->userid, ACL_RIGHT_EXPUNGE);
1808 	uint64_t modseq = 0;
1809 	if (result < 0) {
1810 		dbmail_imap_session_buff_printf(self, "* BYE Internal database error\r\n");
1811 		D->status=result;
1812 		SESSION_RETURN;
1813 	}
1814 	/* only perform the expunge if the user has the right to do it */
1815 	if (result == 1) {
1816 		if (MailboxState_getPermission(self->mailbox->mbstate) == IMAPPERM_READWRITE)
1817 			dbmail_imap_session_mailbox_expunge(self, NULL, &modseq);
1818 		imap_session_mailbox_close(self);
1819 	}
1820 
1821 	dbmail_imap_session_set_state(self, CLIENTSTATE_AUTHENTICATED);
1822 
1823 	if (self->enabled.qresync && modseq) {
1824 		char *response = g_strdup_printf("HIGHESTMODSEQ %" PRIu64, modseq);
1825 		SESSION_OK_WITH_RESP_CODE(response);
1826 		g_free(response);
1827 	} else {
1828 		SESSION_OK;
1829 	}
1830 
1831 	SESSION_RETURN;
1832 }
1833 
_ic_close(ImapSession * self)1834 int _ic_close(ImapSession *self)
1835 {
1836 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_SELECTED)) return 1;
1837 	dm_thread_data_push((gpointer)self, _ic_close_enter, _ic_cb_leave, NULL);
1838 	return 0;
1839 }
1840 
1841 /* _ic_unselect
1842  *
1843  * non-expunging close for select mailbox and return to AUTH state
1844  */
_ic_unselect_enter(dm_thread_data * D)1845 static void _ic_unselect_enter(dm_thread_data *D)
1846 {
1847 	SESSION_GET;
1848 	imap_session_mailbox_close(self);
1849 
1850 	SESSION_OK;
1851 	SESSION_RETURN;
1852 }
1853 
_ic_unselect(ImapSession * self)1854 int _ic_unselect(ImapSession *self)
1855 {
1856 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_SELECTED)) return 1;
1857 	dm_thread_data_push((gpointer)self, _ic_unselect_enter, _ic_cb_leave, NULL);
1858 	return 0;
1859 }
1860 
1861 /* _ic_expunge()
1862  *
1863  * expunge deleted messages from selected mailbox
1864  * show expunge output per message
1865  */
1866 
_ic_expunge_enter(dm_thread_data * D)1867 static void _ic_expunge_enter(dm_thread_data *D)
1868 {
1869 	const char *set = NULL;
1870 	int result;
1871 	uint64_t modseq = 0;
1872 	SESSION_GET;
1873 
1874 	if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_EXPUNGE))) {
1875 		D->status = result;
1876 		SESSION_RETURN;
1877 	}
1878 
1879 	if (self->use_uid)
1880 		set = p_string_str(self->args[self->args_idx]);
1881 
1882 	if (dbmail_imap_session_mailbox_expunge(self, set, &modseq) != DM_SUCCESS) {
1883 		dbmail_imap_session_buff_printf(self, "* BYE expunge failed\r\n");
1884 		D->status = DM_EQUERY;
1885 		SESSION_RETURN;
1886 	}
1887 
1888 	if (self->enabled.qresync && modseq) {
1889 		char *response = g_strdup_printf("HIGHESTMODSEQ %" PRIu64, modseq);
1890 		SESSION_OK_WITH_RESP_CODE(response);
1891 		g_free(response);
1892 	} else {
1893 		SESSION_OK;
1894 	}
1895 	SESSION_RETURN;
1896 }
1897 
_ic_expunge(ImapSession * self)1898 int _ic_expunge(ImapSession *self)
1899 {
1900 	if (self->use_uid) {
1901 		if (!check_state_and_args(self, 1, 1, CLIENTSTATE_SELECTED)) return 1;
1902 	} else {
1903 		if (!check_state_and_args(self, 0, 0, CLIENTSTATE_SELECTED)) return 1;
1904 	}
1905 
1906 	if (MailboxState_getPermission(self->mailbox->mbstate) != IMAPPERM_READWRITE) {
1907 		dbmail_imap_session_buff_printf(self, "%s NO you do not have write permission on this folder\r\n", self->tag);
1908 		return 1;
1909 	}
1910 
1911 	dm_thread_data_push((gpointer)self, _ic_expunge_enter, _ic_cb_leave, NULL);
1912 	return 0;
1913 }
1914 
1915 
1916 /*
1917  * _ic_search()
1918  *
1919  * search the selected mailbox for messages
1920  *
1921  */
sorted_search_enter(dm_thread_data * D)1922 static void sorted_search_enter(dm_thread_data *D)
1923 {
1924 	SESSION_GET;
1925 	DbmailMailbox *mb;
1926 	int result = 0;
1927 	gchar *s = NULL;
1928 	const gchar *cmd;
1929 
1930 	search_order order = self->order;
1931 
1932 	if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_READ))) {
1933 		D->status = result;
1934 		SESSION_RETURN;
1935 	}
1936 
1937 	if (self->state == CLIENTSTATE_SELECTED)
1938 		dbmail_imap_session_mailbox_status(self, TRUE);
1939 
1940 	mb = self->mailbox;
1941 	switch(order) {
1942 		case SEARCH_SORTED:
1943 			cmd = "SORT";
1944 			break;
1945 		case SEARCH_UNORDERED:
1946 			cmd = "SEARCH";
1947 			break;
1948 		case SEARCH_THREAD_REFERENCES:
1949 		case SEARCH_THREAD_ORDEREDSUBJECT:
1950 			cmd = "THREAD";
1951 			break;
1952 		default:// shouldn't happen
1953 			cmd = "NO";
1954 			break;
1955 	}
1956 
1957 	if (MailboxState_getExists(mb->mbstate) > 0) {
1958 		dbmail_mailbox_set_uid(mb,self->use_uid);
1959 
1960 		if (dbmail_mailbox_build_imap_search(mb, self->args, &(self->args_idx), order) < 0) {
1961 			dbmail_imap_session_buff_printf(self, "%s BAD invalid arguments to %s\r\n",
1962 				self->tag, cmd);
1963 			D->status = 1;
1964 			SESSION_RETURN;
1965 		}
1966 		dbmail_mailbox_search(mb);
1967 		/* ok, display results */
1968 		switch(order) {
1969 			case SEARCH_SORTED:
1970 				dbmail_mailbox_sort(mb);
1971 				s = dbmail_mailbox_sorted_as_string(mb);
1972 			break;
1973 			case SEARCH_UNORDERED:
1974 				s = dbmail_mailbox_ids_as_string(mb, FALSE, " ");
1975 			break;
1976 			case SEARCH_THREAD_ORDEREDSUBJECT:
1977 				s = dbmail_mailbox_orderedsubject(mb);
1978 			break;
1979 			case SEARCH_THREAD_REFERENCES:
1980 				s = NULL; // TODO: unsupported
1981 			break;
1982 		}
1983 	} else {
1984 		TRACE(TRACE_DEBUG, "empty mailbox?");
1985 	}
1986 
1987 	if (s) {
1988 		dbmail_imap_session_buff_printf(self, "* %s %s\r\n", cmd, s);
1989 		g_free(s);
1990 	} else {
1991 		dbmail_imap_session_buff_printf(self, "* %s\r\n", cmd);
1992 	}
1993 
1994 	SESSION_OK;
1995 	SESSION_RETURN;
1996 }
1997 
sorted_search(ImapSession * self,search_order order)1998 static int sorted_search(ImapSession *self, search_order order)
1999 {
2000 	if (!check_state_and_args(self, 1, 0, CLIENTSTATE_SELECTED)) return 1;
2001 	self->order = order;
2002 	dm_thread_data_push((gpointer)self, sorted_search_enter, _ic_cb_leave, NULL);
2003 	return 0;
2004 }
2005 
_ic_search(ImapSession * self)2006 int _ic_search(ImapSession *self)
2007 {
2008 	return sorted_search(self,SEARCH_UNORDERED);
2009 }
2010 
_ic_sort(ImapSession * self)2011 int _ic_sort(ImapSession *self)
2012 {
2013 	return sorted_search(self,SEARCH_SORTED);
2014 }
2015 
_ic_thread(ImapSession * self)2016 int _ic_thread(ImapSession *self)
2017 {
2018 	if (MATCH(p_string_str(self->args[self->args_idx]),"ORDEREDSUBJECT"))
2019 		return sorted_search(self,SEARCH_THREAD_ORDEREDSUBJECT);
2020 	if (MATCH(p_string_str(self->args[self->args_idx]),"REFERENCES"))
2021 		dbmail_imap_session_buff_printf(self, "%s BAD THREAD=REFERENCES not supported\r\n",self->tag);
2022 		//return sorted_search(self,SEARCH_THREAD_REFERENCES);
2023 
2024 	return 1;
2025 }
2026 
_dm_imapsession_get_ids(ImapSession * self,const char * set)2027 int _dm_imapsession_get_ids(ImapSession *self, const char *set)
2028 {
2029 	gboolean found = FALSE;
2030 
2031 	dbmail_mailbox_set_uid(self->mailbox,self->use_uid);
2032 
2033 	if (self->ids) {
2034 		g_tree_destroy(self->ids);
2035 		self->ids = NULL;
2036 	}
2037 
2038 	self->ids = dbmail_mailbox_get_set(self->mailbox, set, self->use_uid);
2039 
2040 	found = ( self->ids && (g_tree_nnodes(self->ids) > 0) );
2041 
2042 	if ( (! self->use_uid) && (! found)) {
2043 		dbmail_imap_session_buff_printf(self, "%s BAD invalid sequence in msn set [%s]\r\n", self->tag, set);
2044 		return DM_EGENERAL;
2045 	}
2046 
2047 	if (self->use_uid && (! self->ids)) { // empty tree IS valid
2048 		dbmail_imap_session_buff_printf(self, "%s BAD invalid sequence in uid set [%s]\r\n", self->tag, set);
2049 		return DM_EGENERAL;
2050 	}
2051 
2052 	return DM_SUCCESS;
2053 }
2054 
2055 
2056 
2057 /*
2058  * _ic_fetch()
2059  *
2060  * fetch message(s) from the selected mailbox
2061  */
2062 
2063 
_ic_fetch_enter(dm_thread_data * D)2064 static void _ic_fetch_enter(dm_thread_data *D)
2065 {
2066 	SESSION_GET;
2067 	int result, state, setidx;
2068 
2069 	self->fi->bodyfetch = p_list_new(self->pool);
2070 	self->fi->getUID = self->use_uid;
2071 
2072 	setidx = self->args_idx;
2073 	TRACE(TRACE_DEBUG, "id-set: [%s]", p_string_str(self->args[self->args_idx]));
2074 	self->args_idx++; //skip on past this for the fetch_parse_args coming next...
2075 
2076 	state = 1;
2077 	do {
2078 		if ((state=dbmail_imap_session_fetch_parse_args(self)) == -2) {
2079 			dbmail_imap_session_buff_printf(self, "%s BAD invalid argument list to fetch\r\n", self->tag);
2080 			D->status = 1;
2081 			SESSION_RETURN;
2082 		}
2083 		self->args_idx++;
2084 	} while (state > 0);
2085 
2086 	if (self->fi->vanished && (! self->fi->changedsince)) {
2087 		dbmail_imap_session_buff_printf(self, "%s BAD invalid argument list to fetch\r\n", self->tag);
2088 		D->status = 1;
2089 		SESSION_RETURN;
2090 	}
2091 
2092 	if (self->fi->vanished) {
2093 		self->qresync.modseq = self->fi->changedsince;
2094 		char *out = NULL;
2095 		GTree *vanished = MailboxState_getExpunged(self->mailbox->mbstate, &self->qresync, &out);
2096 		if (g_tree_nnodes(vanished)) {
2097 			dbmail_imap_session_buff_printf(self,
2098 				       	"* VANISHED (EARLIER) %s\r\n", out);
2099 		}
2100 		g_free(out);
2101 		g_tree_destroy(vanished);
2102 	}
2103 
2104 	dbmail_imap_session_mailbox_status(self, FALSE);
2105 
2106 	if ((result = _dm_imapsession_get_ids(self, p_string_str(self->args[setidx]))) == DM_SUCCESS) {
2107 		self->ids_list = g_tree_keys(self->ids);
2108 		result = dbmail_imap_session_fetch_get_items(self);
2109 	}
2110 
2111 	dbmail_imap_session_fetch_free(self, FALSE);
2112 	dbmail_imap_session_args_free(self, FALSE);
2113 
2114 	MailboxState_flush_recent(self->mailbox->mbstate);
2115 
2116 	if (result) {
2117 		D->status = result;
2118 		SESSION_RETURN;
2119 	}
2120 
2121 	SESSION_OK;
2122 	SESSION_RETURN;
2123 }
2124 
_ic_fetch(ImapSession * self)2125 int _ic_fetch(ImapSession *self)
2126 {
2127 	if (!check_state_and_args (self, 2, 0, CLIENTSTATE_SELECTED)) return 1;
2128 	dm_thread_data_push((gpointer)self, _ic_fetch_enter, _ic_cb_leave, NULL);
2129 	return 0;
2130 }
2131 
2132 /*
2133  * _ic_store()
2134  *
2135  * alter message-associated data in selected mailbox
2136  */
2137 
_fetch_update(ImapSession * self,MessageInfo * msginfo,gboolean showmodseq,gboolean showflags)2138 void _fetch_update(ImapSession *self, MessageInfo *msginfo, gboolean showmodseq, gboolean showflags)
2139 {
2140 	gboolean needspace = false;
2141 
2142 	uint64_t *msn = g_tree_lookup(MailboxState_getIds(self->mailbox->mbstate), &msginfo->uid);
2143 
2144 	dbmail_imap_session_buff_printf(self,"* %" PRIu64 " FETCH (", *msn);
2145 	if (self->use_uid) {
2146 		dbmail_imap_session_buff_printf(self, "UID %" PRIu64 , msginfo->uid);
2147 		needspace = true;
2148 	}
2149 
2150 	if (showflags) {
2151 		GList *sublist = MailboxState_message_flags(self->mailbox->mbstate, msginfo);
2152 		char *s = dbmail_imap_plist_as_string(sublist);
2153 		g_list_destroy(sublist);
2154 		if (needspace) dbmail_imap_session_buff_printf(self, " ");
2155 		dbmail_imap_session_buff_printf(self, "FLAGS %s", s);
2156 		g_free(s);
2157 		needspace = true;
2158 	}
2159 	if (showmodseq) {
2160 		if (needspace) dbmail_imap_session_buff_printf(self, " ");
2161 		dbmail_imap_session_buff_printf(self, "MODSEQ (%" PRIu64 ")", msginfo->seq);
2162 	}
2163 
2164 	dbmail_imap_session_buff_printf(self, ")\r\n");
2165 }
2166 
_do_store(uint64_t * id,gpointer UNUSED value,dm_thread_data * D)2167 static gboolean _do_store(uint64_t *id, gpointer UNUSED value, dm_thread_data *D)
2168 {
2169 	ImapSession *self = D->session;
2170 	struct cmd_t *cmd = self->cmd;
2171 
2172 	MessageInfo *msginfo = NULL;
2173 	int i;
2174 	int changed = 0;
2175 
2176 	if (self->mailbox && MailboxState_getMsginfo(self->mailbox->mbstate))
2177 		msginfo = g_tree_lookup(MailboxState_getMsginfo(self->mailbox->mbstate), id);
2178 
2179 	if (! msginfo)
2180 		return TRUE;
2181 
2182 
2183 	if (MailboxState_getPermission(self->mailbox->mbstate) == IMAPPERM_READWRITE) {
2184 		changed = db_set_msgflag(*id, cmd->flaglist, cmd->keywords, cmd->action, cmd->unchangedsince, msginfo);
2185 		if (changed < 0) {
2186 			dbmail_imap_session_buff_printf(self, "\r\n* BYE internal dbase error\r\n");
2187 			D->status = TRUE;
2188 			return TRUE;
2189 		} else if (changed) {
2190 			db_message_set_seq(*id, cmd->seq);
2191 			msginfo->seq = cmd->seq;
2192 		} else {
2193 			self->ids_list = g_list_prepend(self->ids_list, id);
2194 		}
2195 	}
2196 
2197 	// Set the system flags
2198 	for (i = 0; i < IMAP_NFLAGS; i++) {
2199 
2200 		if (i == IMAP_FLAG_RECENT) // Skip recent_flag
2201 			continue;
2202 
2203 		switch (cmd->action) {
2204 			case IMAPFA_ADD:
2205 				if (cmd->flaglist[i])
2206 					msginfo->flags[i] = 1;
2207 			break;
2208 			case IMAPFA_REMOVE:
2209 				if (cmd->flaglist[i])
2210 					msginfo->flags[i] = 0;
2211 			break;
2212 			case IMAPFA_REPLACE:
2213 				if (cmd->flaglist[i])
2214 					msginfo->flags[i] = 1;
2215 				else
2216 					msginfo->flags[i] = 0;
2217 			break;
2218 		}
2219 	}
2220 
2221 	// Set the user keywords as labels
2222 	g_list_merge(&(msginfo->keywords), cmd->keywords, cmd->action, (GCompareFunc)g_ascii_strcasecmp);
2223 
2224 	// reporting callback
2225 	if ((! cmd->silent) || changed > 0) {
2226 		gboolean showmodseq = (changed && (cmd->unchangedsince || self->mailbox->condstore));
2227 		gboolean showflags = (! cmd->silent);
2228 		_fetch_update(self, msginfo, showmodseq, showflags);
2229 	}
2230 
2231 	return FALSE;
2232 }
2233 
_ic_store_enter(dm_thread_data * D)2234 static void _ic_store_enter(dm_thread_data *D)
2235 {
2236 	SESSION_GET;
2237 	int result, j, k;
2238 	struct cmd_t cmd;
2239 	gboolean update = FALSE;
2240 	const char *token = NULL;
2241 	gboolean needflags = false;
2242 	int startflags = 0, endflags = 0;
2243 	String_T buffer = NULL;
2244 
2245 	k = self->args_idx;
2246 
2247 	memset(&cmd, 0, sizeof(cmd));
2248 	cmd.silent = FALSE;
2249 
2250 	/* retrieve action type */
2251 	for (k = self->args_idx+1; self->args[k]; k++) {
2252 
2253 		token = p_string_str(self->args[k]);
2254 		if (cmd.action == IMAPFA_NONE) {
2255 			if (MATCH(token, "flags")) {
2256 				cmd.action = IMAPFA_REPLACE;
2257 			} else if (MATCH(token, "flags.silent")) {
2258 				cmd.action = IMAPFA_REPLACE;
2259 				cmd.silent = TRUE;
2260 			} else if (MATCH(token, "+flags")) {
2261 				cmd.action = IMAPFA_ADD;
2262 			} else if (MATCH(token, "+flags.silent")) {
2263 				cmd.action = IMAPFA_ADD;
2264 				cmd.silent = TRUE;
2265 			} else if (MATCH(token, "-flags")) {
2266 				cmd.action = IMAPFA_REMOVE;
2267 			} else if (MATCH(token, "-flags.silent")) {
2268 				cmd.action = IMAPFA_REMOVE;
2269 				cmd.silent = TRUE;
2270 			}
2271 			if (cmd.action != IMAPFA_NONE) {
2272 				needflags = true;
2273 				continue;
2274 			}
2275 		}
2276 		if (needflags) {
2277 			if ( (! startflags) && (MATCH(token, "("))) {
2278 				startflags = k+1;
2279 				continue;
2280 			} else if (! startflags) {
2281 				startflags = k;
2282 				continue;
2283 			} else if (startflags && (MATCH(token, ")") || MATCH(token, "("))) {
2284 				needflags = false;
2285 				endflags = k-1;
2286 			}
2287 		}
2288 		if (! needflags) {
2289 			if (MATCH(token, "(")) {
2290 				char *end;
2291 				if (self->args[k+1] && self->args[k+2]) {
2292 					if ((! Capa_match(self->capa, "CONDSTORE")) || (! MATCH(p_string_str(self->args[k+1]), "UNCHANGEDSINCE"))) {
2293 						cmd.action = IMAPFA_NONE;
2294 						break;
2295 					}
2296 					errno = 0;
2297 					cmd.unchangedsince = dm_strtoull(p_string_str(self->args[k+2]), &end, 10);
2298 					if (p_string_str(self->args[k+2]) == end) {
2299 						cmd.action = IMAPFA_NONE;
2300 						break;
2301 					}
2302 					k += 2;
2303 					self->mailbox->condstore = true;
2304 				}
2305 			}
2306 		}
2307 	}
2308 	if (! endflags)
2309 		endflags = k-1;
2310 
2311 
2312 	if (cmd.action == IMAPFA_NONE) {
2313 		dbmail_imap_session_buff_printf(self, "%s BAD invalid STORE action specified\r\n", self->tag);
2314 		D->status = 1;
2315 		SESSION_RETURN;
2316 	}
2317 
2318 	/* multiple flags should be parenthesed */
2319 	if (needflags && (!startflags)) {
2320 		dbmail_imap_session_buff_printf(self, "%s BAD invalid argument(s) to STORE\r\n", self->tag);
2321 		D->status = 1;
2322 		SESSION_RETURN;
2323 	}
2324 
2325 
2326 	/* now fetch flag list */
2327 	for (k = startflags; k <= endflags; k++) {
2328 		for (j = 0; j < IMAP_NFLAGS; j++) {
2329 			/* storing the recent flag explicitely is not allowed */
2330 			if (MATCH(p_string_str(self->args[k]),"\\Recent")) {
2331 				dbmail_imap_session_buff_printf(self, "%s BAD invalid flag list to STORE command\r\n", self->tag);
2332 				D->status = 1;
2333 				SESSION_RETURN;
2334 			}
2335 
2336 			if (MATCH(p_string_str(self->args[k]), imap_flag_desc_escaped[j])) {
2337 				cmd.flaglist[j] = 1;
2338 				break;
2339 			}
2340 		}
2341 
2342 		if (j == IMAP_NFLAGS) {
2343 			const char *kw = p_string_str(self->args[k]);
2344 			cmd.keywords = g_list_append(cmd.keywords,g_strdup(kw));
2345 			if (! MailboxState_hasKeyword(self->mailbox->mbstate, kw)) {
2346 				MailboxState_addKeyword(self->mailbox->mbstate, kw);
2347 				update = TRUE;
2348 			}
2349 		}
2350 	}
2351 
2352 	/** check ACL's for STORE */
2353 	if (cmd.flaglist[IMAP_FLAG_SEEN] == 1) {
2354 		if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_SEEN))) {
2355 			dbmail_imap_session_buff_printf(self, "%s NO access denied\r\n", self->tag);
2356 			D->status = result;
2357 			g_list_destroy(cmd.keywords);
2358 			SESSION_RETURN;
2359 		}
2360 	}
2361 	if (cmd.flaglist[IMAP_FLAG_DELETED] == 1) {
2362 		if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_DELETED))) {
2363 			dbmail_imap_session_buff_printf(self, "%s NO access denied\r\n", self->tag);
2364 			D->status = result;
2365 			g_list_destroy(cmd.keywords);
2366 			SESSION_RETURN;
2367 		}
2368 	}
2369 	if (cmd.flaglist[IMAP_FLAG_ANSWERED] == 1 ||
2370 	    cmd.flaglist[IMAP_FLAG_FLAGGED] == 1 ||
2371 	    cmd.flaglist[IMAP_FLAG_DRAFT] == 1 ||
2372 	    g_list_length(cmd.keywords) > 0 ) {
2373 		if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_WRITE))) {
2374 			dbmail_imap_session_buff_printf(self, "%s NO access denied\r\n", self->tag);
2375 			D->status = result;
2376 			g_list_destroy(cmd.keywords);
2377 			SESSION_RETURN;
2378 		}
2379 	}
2380 	/* end of ACL checking. If we get here without returning, the user has
2381 	   the right to store the flags */
2382 
2383 	self->cmd = &cmd;
2384 
2385 	if ( update ) {
2386 		char *flags = MailboxState_flags(self->mailbox->mbstate);
2387 		dbmail_imap_session_buff_printf(self, "* FLAGS (%s)\r\n", flags);
2388 		dbmail_imap_session_buff_printf(self, "* OK [PERMANENTFLAGS (%s \\*)] Flags allowed.\r\n", flags);
2389 		g_free(flags);
2390 	}
2391 
2392 	if ((result = _dm_imapsession_get_ids(self, p_string_str(self->args[self->args_idx]))) == DM_SUCCESS) {
2393 		if (self->ids) {
2394 			uint64_t seq = db_mailbox_seq_update(MailboxState_getId(self->mailbox->mbstate), 0);
2395 			cmd.seq = seq;
2396 			g_tree_foreach(self->ids, (GTraverseFunc) _do_store, D);
2397 		}
2398 	}
2399 
2400 	g_list_destroy(cmd.keywords);
2401 
2402 	if (result || D->status) {
2403 		if (result) D->status = result;
2404 		SESSION_RETURN;
2405 	}
2406 
2407 	if (self->ids_list) {
2408 		GString *failed_ids = g_list_join_u64(self->ids_list, ",");
2409 		buffer = p_string_new(self->pool, "");
2410 		//according to RFC7162 section 3.1.3.0 MODIFIED keyword should be used as respnse like
2411 		//"... OK [MODIFIED 7,9] ..." not "...OK [MODIFIED [7,9]]..."
2412 		p_string_printf(buffer, "MODIFIED %s", failed_ids->str);
2413 		g_string_free(failed_ids, TRUE);
2414 		g_list_free(g_list_first(self->ids_list));
2415 		self->ids_list = NULL;
2416 		SESSION_OK_WITH_RESP_CODE(p_string_str(buffer));
2417 		p_string_free(buffer, TRUE);
2418 	} else {
2419 		SESSION_OK;
2420 	}
2421 
2422 	SESSION_RETURN;
2423 }
2424 
_ic_store(ImapSession * self)2425 int _ic_store(ImapSession *self)
2426 {
2427 	if (!check_state_and_args (self, 2, 0, CLIENTSTATE_SELECTED)) return 1;
2428 	dm_thread_data_push((gpointer)self, _ic_store_enter, _ic_cb_leave, NULL);
2429 	return 0;
2430 }
2431 
2432 /*
2433  * _ic_copy()
2434  *
2435  * copy a message to another mailbox
2436  */
2437 
_do_copy(uint64_t * id,gpointer UNUSED value,ImapSession * self)2438 static gboolean _do_copy(uint64_t *id, gpointer UNUSED value, ImapSession *self)
2439 {
2440 	struct cmd_t *cmd = self->cmd;
2441 	uint64_t newid = 0;
2442 	int result;
2443 	uint64_t *new_ids_element = NULL;
2444 	if (!g_tree_lookup(self->mailbox->mbstate->msginfo, id)){
2445 		TRACE(TRACE_WARNING,"Copy message [%ld] failed security issue, trying to copy message that are not in this mailbox",*id);
2446 		//dbmail_imap_session_buff_printf(self, "%s NO security issue, trying to copy message that are not in this mailbox\r\n",self->tag);
2447 		return FALSE;
2448 	}
2449 	result = db_copymsg(*id, cmd->mailbox_id, self->userid, &newid, TRUE);
2450 	db_message_set_seq(*id, cmd->seq);
2451 	if (result == -1) {
2452 		/* uid not found, according to RFC 3501 section 6.4.8, should continue  */
2453 		TRACE(TRACE_WARNING,"Copy message [%ld] failed due to missing in database. continue.",*id);
2454 		/* continue operation, do not close or send various info on connection */
2455 		//dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
2456 		//return TRUE;
2457 		return FALSE;
2458 	}
2459 	if (result == -2) {
2460 		TRACE(TRACE_WARNING,"Copy message [%ld] failed due to `%s NO quotum would exceed`",*id,self->tag);
2461 		dbmail_imap_session_buff_printf(self, "%s NO quotum would exceed\r\n", self->tag);
2462 		return TRUE;
2463 	}
2464 	// insert the new uid to the new_ids collection
2465 	// reserve memory for the new element in new_ids
2466 	new_ids_element = g_new0(uint64_t,1);
2467 	*new_ids_element = newid;
2468 	TRACE(TRACE_DEBUG, "copied uid %" PRIu64 " -> %" PRIu64, *id, *new_ids_element);
2469 	// prepending is faster then appending
2470 	self->new_ids = g_list_prepend(self->new_ids, new_ids_element);
2471 	return FALSE;
2472 }
2473 
2474 
_ic_copy_enter(dm_thread_data * D)2475 static void _ic_copy_enter(dm_thread_data *D)
2476 {
2477 	SESSION_GET;
2478 	uint64_t destmboxid;
2479 	int result;
2480 	MailboxState_T S;
2481 	struct cmd_t cmd;
2482 	const char *src, *dst;
2483 
2484 	src = p_string_str(self->args[self->args_idx]);
2485 	dst = p_string_str(self->args[self->args_idx+1]);
2486 
2487 	GList *old_ids;
2488 	GString *old_ids_buff;
2489 	GString *new_ids_buff;
2490 
2491 	String_T buffer;
2492 
2493 	/* check if destination mailbox exists */
2494 	if (! db_findmailbox(dst, self->userid, &destmboxid)) {
2495 		dbmail_imap_session_buff_printf(self, "%s NO [TRYCREATE] specified mailbox does not exist\r\n", self->tag);
2496 		D->status = 1;
2497 		SESSION_RETURN;
2498 	}
2499 	// check if user has right to COPY from source mailbox
2500 	if ((result = mailbox_check_acl(self, self->mailbox->mbstate, ACL_RIGHT_READ))) {
2501 		D->status = result;
2502 		SESSION_RETURN;
2503 	}
2504 
2505 	// check if user has right to COPY to destination mailbox
2506 	S = dbmail_imap_session_mbxinfo_lookup(self, destmboxid);
2507 	if ((result = mailbox_check_acl(self, S, ACL_RIGHT_INSERT))) {
2508 		D->status = result;
2509 		SESSION_RETURN;
2510 	}
2511 
2512 	memset(&cmd, 0, sizeof(cmd));
2513 	cmd.mailbox_id = destmboxid;
2514 	self->cmd = &cmd;
2515 	if ((result = _dm_imapsession_get_ids(self, src)) == DM_SUCCESS) {
2516 		if (self->ids) {
2517 			cmd.seq = db_mailbox_seq_update(destmboxid, 0);
2518 			g_tree_foreach(self->ids, (GTraverseFunc) _do_copy, self);
2519 		}
2520 	}
2521 	self->cmd = NULL;
2522 
2523 	if (result) {
2524 		D->status = result;
2525 		SESSION_RETURN;
2526 	}
2527 
2528 	if (MailboxState_getId(self->mailbox->mbstate) == destmboxid)
2529 		dbmail_imap_session_mailbox_status(self, TRUE);
2530 
2531 	self->new_ids = g_list_reverse(self->new_ids);
2532 
2533 	old_ids = g_tree_keys(self->ids);
2534 	old_ids_buff = g_list_join_u64(old_ids,",");
2535 	g_list_free(g_list_first(old_ids));
2536 
2537 	new_ids_buff = g_list_join_u64(self->new_ids,",");
2538 
2539 	buffer = p_string_new(self->pool, "");
2540 	p_string_printf(buffer, "COPYUID %" PRIu64 " %s %s", destmboxid, old_ids_buff->str, new_ids_buff->str);
2541 
2542 	g_string_free(new_ids_buff,TRUE);
2543 	g_string_free(old_ids_buff,TRUE);
2544 
2545 	SESSION_OK_WITH_RESP_CODE(p_string_str(buffer));
2546 	p_string_free(buffer, TRUE);
2547 
2548 	g_list_destroy(self->new_ids);
2549 	self->new_ids = NULL;
2550 
2551 	SESSION_RETURN;
2552 }
2553 
_ic_copy(ImapSession * self)2554 int _ic_copy(ImapSession *self)
2555 {
2556 	if (!check_state_and_args(self, 2, 2, CLIENTSTATE_SELECTED)) return 1;
2557 	dm_thread_data_push((gpointer)self, _ic_copy_enter, _ic_cb_leave, NULL);
2558 	return 0;
2559 }
2560 
2561 /*
2562  * _ic_uid()
2563  *
2564  * fetch/store/copy/search message UID's
2565  */
_ic_uid(ImapSession * self)2566 int _ic_uid(ImapSession *self)
2567 {
2568 	int result;
2569 	const char *command;
2570 
2571 	if (self->state != CLIENTSTATE_SELECTED) {
2572 		dbmail_imap_session_buff_printf(self, "%s BAD UID command received in invalid state\r\n", self->tag);
2573 		return 1;
2574 	}
2575 
2576 	if (! (self->args[0] && self->args[self->args_idx]) ) {
2577 		dbmail_imap_session_buff_printf(self, "%s BAD missing argument(s) to UID\r\n", self->tag);
2578 		return 1;
2579 	}
2580 
2581 	self->use_uid = 1;	/* set global var to make clear we will be using UID's */
2582 
2583 	/* ACL rights for UID are handled by the other functions called below */
2584 	command = p_string_str(self->args[self->args_idx]);
2585 	if (MATCH(command, "fetch")) {
2586 		dbmail_imap_session_set_command(self, command);
2587 		self->args_idx++;
2588 		result = _ic_fetch(self);
2589 	} else if (MATCH(command, "copy")) {
2590 		dbmail_imap_session_set_command(self, command);
2591 		self->args_idx++;
2592 		result = _ic_copy(self);
2593 	} else if (MATCH(command, "store")) {
2594 		dbmail_imap_session_set_command(self, command);
2595 		self->args_idx++;
2596 		result = _ic_store(self);
2597 	} else if (MATCH(command, "search")) {
2598 		dbmail_imap_session_set_command(self, command);
2599 		self->args_idx++;
2600 		result = _ic_search(self);
2601 	} else if (MATCH(command, "sort")) {
2602 		dbmail_imap_session_set_command(self, command);
2603 		self->args_idx++;
2604 		result = _ic_sort(self);
2605 	} else if (MATCH(command, "thread")) {
2606 		dbmail_imap_session_set_command(self, command);
2607 		self->args_idx++;
2608 		result = _ic_thread(self);
2609 	} else if (MATCH(command, "expunge")) {
2610 		dbmail_imap_session_set_command(self, command);
2611 		self->args_idx++;
2612 		result = _ic_expunge(self);
2613 	} else {
2614 		dbmail_imap_session_buff_printf(self, "%s BAD invalid UID command\r\n", self->tag);
2615 		result = 1;
2616 		self->use_uid = 0;
2617 	}
2618 
2619 	return result;
2620 }
2621 
2622 
2623 /* Helper function for _ic_getquotaroot() and _ic_getquota().
2624  * Send all resource limits in `quota'.
2625  */
send_quota(ImapSession * self,Quota_T quota)2626 void send_quota(ImapSession *self, Quota_T quota)
2627 {
2628 	uint64_t usage, limit;
2629 
2630 	limit = quota_get_limit(quota);
2631 	usage = quota_get_usage(quota);
2632 
2633 	if (limit > 0) {
2634 		usage = usage / 1024;
2635 		limit = limit / 1024;
2636 		dbmail_imap_session_buff_printf(self,
2637 				"* QUOTA \"\" (STORAGE %" PRIu64 " %" PRIu64 ")\r\n",
2638 				usage, limit);
2639 	}
2640 }
2641 
2642 /*
2643  * _ic_getquotaroot()
2644  *
2645  * get quota root and send quota
2646  */
_ic_getquotaroot_enter(dm_thread_data * D)2647 static void _ic_getquotaroot_enter(dm_thread_data *D)
2648 {
2649 	Quota_T quota;
2650 	char *errormsg;
2651 	SESSION_GET;
2652 
2653 	if (! (quota = quota_get_quota(self->userid, "", &errormsg))) {
2654 		dbmail_imap_session_buff_printf(self, "%s NO %s\r\n", self->tag, errormsg);
2655 		D->status=1;
2656 		SESSION_RETURN;
2657 	}
2658 
2659 	dbmail_imap_session_buff_printf(self, "* QUOTAROOT \"%s\" \"%s\"\r\n",
2660 			p_string_str(self->args[self->args_idx]),
2661 			quota_get_root(quota));
2662 
2663 	send_quota(self, quota);
2664 	quota_free(&quota);
2665 
2666 	SESSION_OK;
2667 	SESSION_RETURN;
2668 }
2669 
_ic_getquotaroot(ImapSession * self)2670 int _ic_getquotaroot(ImapSession *self)
2671 {
2672 	if (! check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
2673 	dm_thread_data_push((gpointer)self, _ic_getquotaroot_enter, _ic_cb_leave, NULL);
2674 	return 0;
2675 }
2676 
2677 /*
2678  * _ic_getquot()
2679  *
2680  * get quota
2681  */
_ic_getquota_enter(dm_thread_data * D)2682 static void _ic_getquota_enter(dm_thread_data *D)
2683 {
2684 	Quota_T quota;
2685 	char *errormsg;
2686 	SESSION_GET;
2687 
2688 	if (! (quota = quota_get_quota(self->userid, p_string_str(self->args[self->args_idx]), &errormsg))) {
2689 		dbmail_imap_session_buff_printf(self, "%s NO %s\r\n", self->tag, errormsg);
2690 		D->status=1;
2691 		SESSION_RETURN;
2692 	}
2693 
2694 	send_quota(self, quota);
2695 	quota_free(&quota);
2696 
2697 	SESSION_OK;
2698 	SESSION_RETURN;
2699 }
2700 
_ic_getquota(ImapSession * self)2701 int _ic_getquota(ImapSession *self)
2702 {
2703 	if (! check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
2704 	dm_thread_data_push((gpointer)self, _ic_getquota_enter, _ic_cb_leave, NULL);
2705 	return 0;
2706 }
2707 
2708 /* returns -1 on error, 0 if user or mailbox not found and 1 otherwise */
imap_acl_pre_administer(const char * mailboxname,const char * username,uint64_t executing_userid,uint64_t * mboxid,uint64_t * target_userid)2709 static int imap_acl_pre_administer(const char *mailboxname,
2710 				   const char *username,
2711 				   uint64_t executing_userid,
2712 				   uint64_t * mboxid, uint64_t * target_userid)
2713 {
2714 	if (! db_findmailbox(mailboxname, executing_userid, mboxid))
2715 		return FALSE;
2716 	return auth_user_exists(username, target_userid);
2717 }
2718 
_ic_setacl_enter(dm_thread_data * D)2719 static void _ic_setacl_enter(dm_thread_data *D)
2720 {
2721 	/* SETACL mailboxname identifier mod_rights */
2722 	int result;
2723 	uint64_t mboxid;
2724 	uint64_t targetuserid;
2725 	MailboxState_T S;
2726 	SESSION_GET;
2727 
2728 	const char *mailbox = p_string_str(self->args[self->args_idx]);
2729 	const char *usernam = p_string_str(self->args[self->args_idx+1]);
2730 	const char *rights = p_string_str(self->args[self->args_idx+2]);
2731 	result = imap_acl_pre_administer(mailbox, usernam, self->userid, &mboxid, &targetuserid);
2732 	if (result == -1) {
2733 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2734 		D->status = -1;
2735 		SESSION_RETURN;
2736 	} else if (result == 0) {
2737 		dbmail_imap_session_buff_printf(self, "%s NO SETACL failure: can't set acl\r\n", self->tag);
2738 		D->status = 1;
2739 		SESSION_RETURN;
2740 	}
2741 	// has the rights to 'administer' this mailbox?
2742 	S = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
2743 	if ((result = mailbox_check_acl(self, S, ACL_RIGHT_ADMINISTER))) {
2744 		D->status = result;
2745 		SESSION_RETURN;
2746 	}
2747 
2748 	// set the new acl
2749 	if (acl_set_rights(targetuserid, mboxid, rights) < 0) {
2750 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2751 		D->status = -1;
2752 		SESSION_RETURN;
2753 	}
2754 
2755 	SESSION_OK;
2756 	SESSION_RETURN;
2757 }
2758 
_ic_setacl(ImapSession * self)2759 int _ic_setacl(ImapSession *self)
2760 {
2761 	if (!check_state_and_args(self, 3, 3, CLIENTSTATE_AUTHENTICATED)) return 1;
2762 	dm_thread_data_push((gpointer)self, _ic_setacl_enter, _ic_cb_leave, NULL);
2763 	return 0;
2764 }
2765 
2766 
_ic_deleteacl_enter(dm_thread_data * D)2767 static void _ic_deleteacl_enter(dm_thread_data *D)
2768 {
2769 	// DELETEACL mailboxname identifier
2770 	uint64_t mboxid, targetuserid;
2771 	MailboxState_T S;
2772 	int result;
2773 	SESSION_GET;
2774 	const char *mailbox = p_string_str(self->args[self->args_idx]);
2775 	const char *usernam = p_string_str(self->args[self->args_idx+1]);
2776 
2777 	if (imap_acl_pre_administer(mailbox, usernam, self->userid, &mboxid, &targetuserid) == -1) {
2778 		dbmail_imap_session_buff_printf(self, "* BYE internal dbase error\r\n");
2779 		D->status = -1;
2780 		SESSION_RETURN;
2781 	}
2782 
2783 	S = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
2784 	if ((result = mailbox_check_acl(self, S, ACL_RIGHT_ADMINISTER))) {
2785 		D->status = result;
2786 		SESSION_RETURN;
2787 	}
2788 
2789 	// set the new acl
2790 	if (acl_delete_acl(targetuserid, mboxid) < 0) {
2791 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2792 		D->status = -1;
2793 		SESSION_RETURN;
2794 	}
2795 
2796 	SESSION_OK;
2797 	SESSION_RETURN;
2798 }
2799 
_ic_deleteacl(ImapSession * self)2800 int _ic_deleteacl(ImapSession *self)
2801 {
2802 	if (!check_state_and_args(self, 2, 2, CLIENTSTATE_AUTHENTICATED)) return 1;
2803 	dm_thread_data_push((gpointer)self, _ic_deleteacl_enter, _ic_cb_leave, NULL);
2804 	return 0;
2805 }
2806 
_ic_getacl_enter(dm_thread_data * D)2807 static void _ic_getacl_enter(dm_thread_data *D)
2808 {
2809 	/* GETACL mailboxname */
2810 	uint64_t mboxid;
2811 	char *acl_string;
2812 	SESSION_GET;
2813 	const char *mailbox = p_string_str(self->args[self->args_idx]);
2814 
2815 	if (! db_findmailbox(mailbox, self->userid, &mboxid)) {
2816 		dbmail_imap_session_buff_printf(self, "%s NO GETACL failure: can't get acl\r\n", self->tag);
2817 		D->status = 1;
2818 		SESSION_RETURN;
2819 	}
2820 	// get acl string (string of identifier-rights pairs)
2821 	if (!(acl_string = acl_get_acl(mboxid))) {
2822 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2823 		D->status = -1;
2824 		SESSION_RETURN;
2825 	}
2826 
2827 	dbmail_imap_session_buff_printf(self, "* ACL \"%s\" %s\r\n", mailbox, acl_string);
2828 	g_free(acl_string);
2829 
2830 	SESSION_OK;
2831 	SESSION_RETURN;
2832 }
2833 
_ic_getacl(ImapSession * self)2834 int _ic_getacl(ImapSession *self)
2835 {
2836 	if (!check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
2837 	dm_thread_data_push((gpointer)self, _ic_getacl_enter, _ic_cb_leave, NULL);
2838 	return 0;
2839 }
2840 
_ic_listrights_enter(dm_thread_data * D)2841 static void _ic_listrights_enter(dm_thread_data *D)
2842 {
2843 	/* LISTRIGHTS mailboxname identifier */
2844 	int result;
2845 	uint64_t mboxid;
2846 	uint64_t targetuserid;
2847 	const char *listrights_string;
2848 	MailboxState_T S;
2849 	SESSION_GET;
2850 
2851 	const char *mailbox = p_string_str(self->args[self->args_idx]);
2852 	const char *usernam = p_string_str(self->args[self->args_idx+1]);
2853 	result = imap_acl_pre_administer(mailbox, usernam, self->userid, &mboxid, &targetuserid);
2854 	if (result == -1) {
2855 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2856 		D->status = -1;
2857 		SESSION_RETURN;
2858 	} else if (result == 0) {
2859 		dbmail_imap_session_buff_printf(self, "%s, NO LISTRIGHTS failure: can't set acl\r\n", self->tag);
2860 		D->status = 1;
2861 		SESSION_RETURN;
2862 	}
2863 	// has the rights to 'administer' this mailbox?
2864 	S = dbmail_imap_session_mbxinfo_lookup(self, mboxid);
2865 	if ((result = mailbox_check_acl(self, S, ACL_RIGHT_ADMINISTER))) {
2866 		D->status=1;
2867 		SESSION_RETURN;
2868 	}
2869 
2870 	// set the new acl
2871 	if (!(listrights_string = acl_listrights(targetuserid, mboxid))) {
2872 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2873 		D->status=-1;
2874 		SESSION_RETURN;
2875 	}
2876 
2877 	dbmail_imap_session_buff_printf(self, "* LISTRIGHTS \"%s\" %s %s\r\n",
2878 		mailbox, usernam, listrights_string);
2879 
2880 	SESSION_OK;
2881 	SESSION_RETURN;
2882 }
2883 
_ic_listrights(ImapSession * self)2884 int _ic_listrights(ImapSession *self)
2885 {
2886 	if (!check_state_and_args(self, 2, 2, CLIENTSTATE_AUTHENTICATED)) return 1;
2887 	dm_thread_data_push((gpointer)self, _ic_listrights_enter, _ic_cb_leave, NULL);
2888 	return 0;
2889 }
2890 
_ic_myrights_enter(dm_thread_data * D)2891 static void _ic_myrights_enter(dm_thread_data *D)
2892 {
2893 	/* MYRIGHTS mailboxname */
2894 	uint64_t mboxid;
2895 	char *myrights_string;
2896 	SESSION_GET;
2897 
2898 	if (! db_findmailbox(p_string_str(self->args[self->args_idx]), self->userid, &mboxid)) {
2899 		dbmail_imap_session_buff_printf(self, "%s NO MYRIGHTS failure: unknown mailbox\r\n", self->tag);
2900 		D->status=1;
2901 		SESSION_RETURN;
2902 	}
2903 
2904 	if (!(myrights_string = acl_myrights(self->userid, mboxid))) {
2905 		dbmail_imap_session_buff_printf(self, "* BYE internal database error\r\n");
2906 		D->status = -1;
2907 		SESSION_RETURN;
2908 	}
2909 
2910 	dbmail_imap_session_buff_printf(self, "* MYRIGHTS \"%s\" %s\r\n",
2911 		       	p_string_str(self->args[self->args_idx]), myrights_string);
2912 	g_free(myrights_string);
2913 
2914 	SESSION_OK;
2915 	SESSION_RETURN;
2916 }
2917 
_ic_myrights(ImapSession * self)2918 int _ic_myrights(ImapSession *self)
2919 {
2920 	if (!check_state_and_args(self, 1, 1, CLIENTSTATE_AUTHENTICATED)) return 1;
2921 	dm_thread_data_push((gpointer)self, _ic_myrights_enter, _ic_cb_leave, NULL);
2922 	return 0;
2923 }
2924 
_ic_namespace_enter(dm_thread_data * D)2925 static void _ic_namespace_enter(dm_thread_data *D)
2926 {
2927 	SESSION_GET;
2928 	dbmail_imap_session_buff_printf(self, "* NAMESPACE ((\"\" \"%s\")) ((\"%s\" \"%s\")) "
2929 		"((\"%s\" \"%s\"))\r\n",
2930 		MAILBOX_SEPARATOR, NAMESPACE_USER,
2931 		MAILBOX_SEPARATOR, NAMESPACE_PUBLIC, MAILBOX_SEPARATOR);
2932 
2933 	SESSION_OK;
2934 	SESSION_RETURN;
2935 }
2936 
_ic_namespace(ImapSession * self)2937 int _ic_namespace(ImapSession *self)
2938 {
2939 	if (!check_state_and_args(self, 0, 0, CLIENTSTATE_AUTHENTICATED)) return 1;
2940 	dm_thread_data_push((gpointer)self, _ic_namespace_enter, _ic_cb_leave, NULL);
2941 	return 0;
2942 }
2943 
2944 
2945