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("a);
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("a);
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