]> code.citadel.org Git - citadel.git/blob - citadel/serv_imap.c
* IMAP flag twiddling with STORE was broken because of the \Flag leading
[citadel.git] / citadel / serv_imap.c
1 /*
2  * $Id$ 
3  *
4  * IMAP server for the Citadel system
5  * Copyright (C) 2000-2002 by Art Cancro and others.
6  * This code is released under the terms of the GNU General Public License.
7  *
8  * WARNING: Mark Crispin is an idiot.  IMAP is the most brain-damaged protocol
9  * you will ever have the profound lack of pleasure to encounter.
10  */
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21
22 #if TIME_WITH_SYS_TIME
23 # include <sys/time.h>
24 # include <time.h>
25 #else
26 # if HAVE_SYS_TIME_H
27 #  include <sys/time.h>
28 # else
29 #  include <time.h>
30 # endif
31 #endif
32
33 #include <sys/wait.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <limits.h>
37 #include "citadel.h"
38 #include "server.h"
39 #include "sysdep_decls.h"
40 #include "citserver.h"
41 #include "support.h"
42 #include "config.h"
43 #include "serv_extensions.h"
44 #include "room_ops.h"
45 #include "user_ops.h"
46 #include "policy.h"
47 #include "database.h"
48 #include "msgbase.h"
49 #include "tools.h"
50 #include "internet_addressing.h"
51 #include "serv_imap.h"
52 #include "imap_tools.h"
53 #include "imap_fetch.h"
54 #include "imap_search.h"
55 #include "imap_store.h"
56 #include "imap_misc.h"
57
58 #ifdef HAVE_OPENSSL
59 #include "serv_crypto.h"
60 #endif
61
62 /* imap_rename() uses this struct containing list of rooms to rename */
63 struct irl {
64         struct irl *next;
65         char irl_oldroom[ROOMNAMELEN];
66         char irl_newroom[ROOMNAMELEN];
67         int irl_newfloor;
68 };
69
70 /* Data which is passed between imap_rename() and imap_rename_backend() */
71 struct irlparms {
72         char *oldname;
73         char *newname;
74         struct irl **irl;
75 };
76
77
78 /*
79  * If there is a message ID map in memory, free it
80  */
81 void imap_free_msgids(void)
82 {
83         if (IMAP->msgids != NULL) {
84                 free(IMAP->msgids);
85                 IMAP->msgids = NULL;
86                 IMAP->num_msgs = 0;
87         }
88         if (IMAP->flags != NULL) {
89                 free(IMAP->flags);
90                 IMAP->flags = NULL;
91         }
92 }
93
94
95 /*
96  * If there is a transmitted message in memory, free it
97  */
98 void imap_free_transmitted_message(void)
99 {
100         if (IMAP->transmitted_message != NULL) {
101                 free(IMAP->transmitted_message);
102                 IMAP->transmitted_message = NULL;
103                 IMAP->transmitted_length = 0;
104         }
105 }
106
107
108 /*
109  * Set the \\Seen flag for messages which aren't new
110  */
111 void imap_set_seen_flags(void)
112 {
113         struct visit vbuf;
114         int i;
115
116         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
117         if (IMAP->num_msgs > 0) {
118                 for (i = 0; i < IMAP->num_msgs; ++i) {
119                         if (is_msg_in_mset(vbuf.v_seen, IMAP->msgids[i])) {
120                                 IMAP->flags[i] |= IMAP_SEEN;
121                         }
122                         else {
123                                 IMAP->flags[i] |= IMAP_RECENT;
124                         }
125                         if (is_msg_in_mset
126                             (vbuf.v_answered, IMAP->msgids[i])) {
127                                 IMAP->flags[i] |= IMAP_ANSWERED;
128                         }
129                 }
130         }
131 }
132
133
134
135
136 /*
137  * Back end for imap_load_msgids()
138  *
139  * Optimization: instead of calling realloc() to add each message, we
140  * allocate space in the list for REALLOC_INCREMENT messages at a time.  This
141  * allows the mapping to proceed much faster.
142  */
143 void imap_add_single_msgid(long msgnum, void *userdata)
144 {
145
146         IMAP->num_msgs = IMAP->num_msgs + 1;
147         if (IMAP->msgids == NULL) {
148                 IMAP->msgids = malloc(IMAP->num_msgs * sizeof(long)
149                                       * REALLOC_INCREMENT);
150         } else if (IMAP->num_msgs % REALLOC_INCREMENT == 0) {
151                 IMAP->msgids = realloc(IMAP->msgids,
152                                        (IMAP->num_msgs +
153                                         REALLOC_INCREMENT) * sizeof(long));
154         }
155         if (IMAP->flags == NULL) {
156                 IMAP->flags = malloc(IMAP->num_msgs * sizeof(long)
157                                      * REALLOC_INCREMENT);
158         } else if (IMAP->num_msgs % REALLOC_INCREMENT == 0) {
159                 IMAP->flags = realloc(IMAP->flags,
160                                       (IMAP->num_msgs +
161                                        REALLOC_INCREMENT) * sizeof(long));
162         }
163         IMAP->msgids[IMAP->num_msgs - 1] = msgnum;
164         IMAP->flags[IMAP->num_msgs - 1] = 0;
165 }
166
167
168
169 /*
170  * Set up a message ID map for the current room (folder)
171  */
172 void imap_load_msgids(void)
173 {
174
175         if (IMAP->selected == 0) {
176                 lprintf(CTDL_ERR,
177                         "imap_load_msgids() can't run; no room selected\n");
178                 return;
179         }
180
181         imap_free_msgids();     /* If there was already a map, free it */
182
183         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL,
184                            imap_add_single_msgid, NULL);
185
186         imap_set_seen_flags();
187         lprintf(CTDL_DEBUG, "imap_load_msgids() mapped %d messages\n",
188                 IMAP->num_msgs);
189 }
190
191
192 /*
193  * Re-scan the selected room (folder) and see if it's been changed at all
194  */
195 void imap_rescan_msgids(void)
196 {
197
198         int original_num_msgs = 0;
199         long original_highest = 0L;
200         int i, j;
201         int message_still_exists;
202         struct cdbdata *cdbfr;
203         long *msglist = NULL;
204         int num_msgs = 0;
205         int num_recent = 0;
206
207
208         if (IMAP->selected == 0) {
209                 lprintf(CTDL_ERR,
210                         "imap_load_msgids() can't run; no room selected\n");
211                 return;
212         }
213
214         /* Load the *current* message list from disk, so we can compare it
215          * to what we have in memory.
216          */
217         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
218         if (cdbfr != NULL) {
219                 msglist = malloc(cdbfr->len);
220                 if (msglist == NULL) {
221                         lprintf(CTDL_CRIT, "malloc() failed\n");
222                         abort();
223                 }
224                 memcpy(msglist, cdbfr->ptr, (size_t)cdbfr->len);
225                 num_msgs = cdbfr->len / sizeof(long);
226                 cdb_free(cdbfr);
227         } else {
228                 num_msgs = 0;
229         }
230
231         /*
232          * Check to see if any of the messages we know about have been expunged
233          */
234         if (IMAP->num_msgs > 0)
235                 for (i = 0; i < IMAP->num_msgs; ++i) {
236
237                         message_still_exists = 0;
238                         if (num_msgs > 0)
239                                 for (j = 0; j < num_msgs; ++j) {
240                                         if (msglist[j] == IMAP->msgids[i]) {
241                                                 message_still_exists = 1;
242                                         }
243                                 }
244
245                         if (message_still_exists == 0) {
246                                 cprintf("* %d EXPUNGE\r\n", i + 1);
247
248                                 /* Here's some nice stupid nonsense.  When a message
249                                  * is expunged, we have to slide all the existing
250                                  * messages up in the message array.
251                                  */
252                                 --IMAP->num_msgs;
253                                 memcpy(&IMAP->msgids[i],
254                                        &IMAP->msgids[i + 1],
255                                        (sizeof(long) *
256                                         (IMAP->num_msgs - i)));
257                                 memcpy(&IMAP->flags[i],
258                                        &IMAP->flags[i + 1],
259                                        (sizeof(long) *
260                                         (IMAP->num_msgs - i)));
261
262                                 --i;
263                         }
264
265                 }
266
267         /*
268          * Remember how many messages were here before we re-scanned.
269          */
270         original_num_msgs = IMAP->num_msgs;
271         if (IMAP->num_msgs > 0) {
272                 original_highest = IMAP->msgids[IMAP->num_msgs - 1];
273         } else {
274                 original_highest = 0L;
275         }
276
277         /*
278          * Now peruse the room for *new* messages only.
279          */
280         if (num_msgs > 0)
281                 for (j = 0; j < num_msgs; ++j) {
282                         if (msglist[j] > original_highest) {
283                                 imap_add_single_msgid(msglist[j], NULL);
284                         }
285                 }
286         imap_set_seen_flags();
287
288         /*
289          * If new messages have arrived, tell the client about them.
290          */
291         if (IMAP->num_msgs > original_num_msgs) {
292
293                 for (j = 0; j < num_msgs; ++j) {
294                         if (IMAP->flags[j] & IMAP_RECENT) {
295                                 ++num_recent;
296                         }
297                 }
298
299                 cprintf("* %d EXISTS\r\n", IMAP->num_msgs);
300                 cprintf("* %d RECENT\r\n", num_recent);
301         }
302
303         if (num_msgs != 0)
304                 free(msglist);
305 }
306
307
308
309
310
311
312
313 /*
314  * This cleanup function blows away the temporary memory and files used by
315  * the IMAP server.
316  */
317 void imap_cleanup_function(void)
318 {
319
320         /* Don't do this stuff if this is not a IMAP session! */
321         if (CC->h_command_function != imap_command_loop)
322                 return;
323
324         /* If there is a mailbox selected, auto-expunge it. */
325         if (IMAP->selected) {
326                 imap_do_expunge();
327         }
328
329         lprintf(CTDL_DEBUG, "Performing IMAP cleanup hook\n");
330         imap_free_msgids();
331         imap_free_transmitted_message();
332
333         if (IMAP->cached_fetch != NULL) {
334                 fclose(IMAP->cached_fetch);
335                 IMAP->cached_fetch = NULL;
336                 IMAP->cached_msgnum = (-1);
337         }
338
339         if (IMAP->cached_body != NULL) {
340                 fclose(IMAP->cached_body);
341                 IMAP->cached_body = NULL;
342                 IMAP->cached_bodymsgnum = (-1);
343         }
344
345         lprintf(CTDL_DEBUG, "Finished IMAP cleanup hook\n");
346 }
347
348
349
350 /*
351  * Here's where our IMAP session begins its happy day.
352  */
353 void imap_greeting(void)
354 {
355
356         strcpy(CC->cs_clientname, "IMAP session");
357         CtdlAllocUserData(SYM_IMAP, sizeof(struct citimap));
358         IMAP->authstate = imap_as_normal;
359         IMAP->cached_fetch = NULL;
360         IMAP->cached_msgnum = (-1);
361
362         cprintf("* OK %s Citadel IMAP4rev1 server ready\r\n",
363                 config.c_fqdn);
364 }
365
366
367 /*
368  * implements the LOGIN command (ordinary username/password login)
369  */
370 void imap_login(int num_parms, char *parms[])
371 {
372         if (num_parms != 4) {
373                 cprintf("%s BAD incorrect number of parameters\r\n", parms[0]);
374                 return;
375         }
376
377         if (CtdlLoginExistingUser(parms[2]) == login_ok) {
378                 if (CtdlTryPassword(parms[3]) == pass_ok) {
379                         cprintf("%s OK login successful\r\n", parms[0]);
380                         return;
381                 }
382         }
383
384         cprintf("%s BAD Login incorrect\r\n", parms[0]);
385 }
386
387
388 /*
389  * Implements the AUTHENTICATE command
390  */
391 void imap_authenticate(int num_parms, char *parms[])
392 {
393         char buf[SIZ];
394
395         if (num_parms != 3) {
396                 cprintf("%s BAD incorrect number of parameters\r\n",
397                         parms[0]);
398                 return;
399         }
400
401         if (CC->logged_in) {
402                 cprintf("%s BAD Already logged in.\r\n", parms[0]);
403                 return;
404         }
405
406         if (!strcasecmp(parms[2], "LOGIN")) {
407                 CtdlEncodeBase64(buf, "Username:", 9);
408                 cprintf("+ %s\r\n", buf);
409                 IMAP->authstate = imap_as_expecting_username;
410                 strcpy(IMAP->authseq, parms[0]);
411                 return;
412         }
413
414         else {
415                 cprintf("%s NO AUTHENTICATE %s failed\r\n",
416                         parms[0], parms[1]);
417         }
418 }
419
420 void imap_auth_login_user(char *cmd)
421 {
422         char buf[SIZ];
423
424         CtdlDecodeBase64(buf, cmd, SIZ);
425         CtdlLoginExistingUser(buf);
426         CtdlEncodeBase64(buf, "Password:", 9);
427         cprintf("+ %s\r\n", buf);
428         IMAP->authstate = imap_as_expecting_password;
429         return;
430 }
431
432 void imap_auth_login_pass(char *cmd)
433 {
434         char buf[SIZ];
435
436         CtdlDecodeBase64(buf, cmd, SIZ);
437         if (CtdlTryPassword(buf) == pass_ok) {
438                 cprintf("%s OK authentication succeeded\r\n",
439                         IMAP->authseq);
440         } else {
441                 cprintf("%s NO authentication failed\r\n", IMAP->authseq);
442         }
443         IMAP->authstate = imap_as_normal;
444         return;
445 }
446
447
448 /*
449  * implements the CAPABILITY command
450  */
451 void imap_capability(int num_parms, char *parms[])
452 {
453         cprintf("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE AUTH=LOGIN");
454
455 #ifdef HAVE_OPENSSL
456         cprintf(" STARTTLS");
457 #endif
458
459         cprintf("\r\n");
460         cprintf("%s OK CAPABILITY completed\r\n", parms[0]);
461 }
462
463
464
465 /*
466  * implements the STARTTLS command (Citadel API version)
467  */
468 #ifdef HAVE_OPENSSL
469 void imap_starttls(int num_parms, char *parms[])
470 {
471         char ok_response[SIZ];
472         char nosup_response[SIZ];
473         char error_response[SIZ];
474
475         sprintf(ok_response,
476                 "%s OK begin TLS negotiation now\r\n",
477                 parms[0]);
478         sprintf(nosup_response,
479                 "%s NO TLS not supported here\r\n",
480                 parms[0]);
481         sprintf(error_response,
482                 "%s BAD Internal error\r\n",
483                 parms[0]);
484         CtdlStartTLS(ok_response, nosup_response, error_response);
485 }
486 #endif
487
488
489 /*
490  * implements the SELECT command
491  */
492 void imap_select(int num_parms, char *parms[])
493 {
494         char towhere[SIZ];
495         char augmented_roomname[ROOMNAMELEN];
496         int c = 0;
497         int ok = 0;
498         int ra = 0;
499         struct ctdlroom QRscratch;
500         int msgs, new;
501         int floornum;
502         int roomflags;
503         int i;
504
505         /* Convert the supplied folder name to a roomname */
506         i = imap_roomname(towhere, sizeof towhere, parms[2]);
507         if (i < 0) {
508                 cprintf("%s NO Invalid mailbox name.\r\n", parms[0]);
509                 IMAP->selected = 0;
510                 return;
511         }
512         floornum = (i & 0x00ff);
513         roomflags = (i & 0xff00);
514
515         /* First try a regular match */
516         c = getroom(&QRscratch, towhere);
517
518         /* Then try a mailbox name match */
519         if (c != 0) {
520                 MailboxName(augmented_roomname, sizeof augmented_roomname,
521                             &CC->user, towhere);
522                 c = getroom(&QRscratch, augmented_roomname);
523                 if (c == 0)
524                         strcpy(towhere, augmented_roomname);
525         }
526
527         /* If the room exists, check security/access */
528         if (c == 0) {
529                 /* See if there is an existing user/room relationship */
530                 CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
531
532                 /* normal clients have to pass through security */
533                 if (ra & UA_KNOWN) {
534                         ok = 1;
535                 }
536         }
537
538         /* Fail here if no such room */
539         if (!ok) {
540                 cprintf("%s NO ... no such room, or access denied\r\n",
541                         parms[0]);
542                 return;
543         }
544
545         /* If we already had some other folder selected, auto-expunge it */
546         imap_do_expunge();
547
548         /*
549          * usergoto() formally takes us to the desired room, happily returning
550          * the number of messages and number of new messages.
551          */
552         memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
553         usergoto(NULL, 0, 0, &msgs, &new);
554         IMAP->selected = 1;
555
556         if (!strcasecmp(parms[1], "EXAMINE")) {
557                 IMAP->readonly = 1;
558         } else {
559                 IMAP->readonly = 0;
560         }
561
562         imap_load_msgids();
563
564         cprintf("* %d EXISTS\r\n", msgs);
565         cprintf("* %d RECENT\r\n", new);
566
567         /* Note that \Deleted is a valid flag, but not a permanent flag,
568          * because we don't maintain its state across sessions.  Citadel
569          * automatically expunges mailboxes when they are de-selected.
570          */
571         cprintf("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
572         cprintf("* OK [PERMANENTFLAGS (\\Seen \\Answered)] "
573                 "permanent flags\r\n");
574
575         cprintf("* OK [UIDVALIDITY 0] UIDs valid\r\n");
576         cprintf("%s OK [%s] %s completed\r\n",
577                 parms[0],
578                 (IMAP->readonly ? "READ-ONLY" : "READ-WRITE"), parms[1]);
579 }
580
581
582
583 /*
584  * does the real work for expunge
585  */
586 int imap_do_expunge(void)
587 {
588         int i;
589         int num_expunged = 0;
590
591         lprintf(CTDL_DEBUG, "imap_do_expunge() called\n");
592         if (IMAP->selected == 0) {
593                 return (0);
594         }
595
596         if (IMAP->num_msgs > 0)
597                 for (i = 0; i < IMAP->num_msgs; ++i) {
598                         if (IMAP->flags[i] & IMAP_DELETED) {
599                                 CtdlDeleteMessages(CC->room.QRname,
600                                                    IMAP->msgids[i], "");
601                                 ++num_expunged;
602                         }
603                 }
604
605         if (num_expunged > 0) {
606                 imap_rescan_msgids();
607         }
608
609         lprintf(CTDL_DEBUG, "Expunged %d messages.\n", num_expunged);
610         return (num_expunged);
611 }
612
613
614 /*
615  * implements the EXPUNGE command syntax
616  */
617 void imap_expunge(int num_parms, char *parms[])
618 {
619         int num_expunged = 0;
620
621         num_expunged = imap_do_expunge();
622         cprintf("%s OK expunged %d messages.\r\n", parms[0], num_expunged);
623 }
624
625
626 /*
627  * implements the CLOSE command
628  */
629 void imap_close(int num_parms, char *parms[])
630 {
631
632         /* Yes, we always expunge on close. */
633         if (IMAP->selected) {
634                 imap_do_expunge();
635         }
636
637         IMAP->selected = 0;
638         IMAP->readonly = 0;
639         imap_free_msgids();
640         cprintf("%s OK CLOSE completed\r\n", parms[0]);
641 }
642
643
644 /*
645  * Implements the NAMESPACE command.
646  */
647 void imap_namespace(int num_parms, char *parms[])
648 {
649         int i;
650         struct floor *fl;
651         int floors = 0;
652         char buf[SIZ];
653
654         cprintf("* NAMESPACE ");
655
656         /* All personal folders are subordinate to INBOX. */
657         cprintf("((\"INBOX/\" \"/\")) ");
658
659         /* Other users' folders ... coming soon! FIXME */
660         cprintf("NIL ");
661
662         /* Show all floors as shared namespaces.  Neato! */
663         cprintf("(");
664         for (i = 0; i < MAXFLOORS; ++i) {
665                 fl = cgetfloor(i);
666                 if (fl->f_flags & F_INUSE) {
667                         if (floors > 0) cprintf(" ");
668                         cprintf("(");
669                         sprintf(buf, "%s/", fl->f_name);
670                         imap_strout(buf);
671                         cprintf(" \"/\")");
672                         ++floors;
673                 }
674         }
675         cprintf(")");
676
677         /* Wind it up with a newline and a completion message. */
678         cprintf("\r\n");
679         cprintf("%s OK NAMESPACE completed\r\n", parms[0]);
680 }
681
682
683
684 /*
685  * Used by LIST and LSUB to show the floors in the listing
686  */
687 void imap_list_floors(char *cmd, char *pattern)
688 {
689         int i;
690         struct floor *fl;
691
692         for (i = 0; i < MAXFLOORS; ++i) {
693                 fl = cgetfloor(i);
694                 if (fl->f_flags & F_INUSE) {
695                         if (imap_mailbox_matches_pattern
696                             (pattern, fl->f_name)) {
697                                 cprintf("* %s (\\NoSelect) \"/\" ", cmd);
698                                 imap_strout(fl->f_name);
699                                 cprintf("\r\n");
700                         }
701                 }
702         }
703 }
704
705
706
707 /*
708  * Back end for imap_lsub()
709  *
710  * IMAP "subscribed folder" is equivocated to Citadel "known rooms."  This
711  * may or may not be the desired behavior in the future.
712  */
713 void imap_lsub_listroom(struct ctdlroom *qrbuf, void *data)
714 {
715         char buf[SIZ];
716         int ra;
717         char *pattern;
718
719         pattern = (char *) data;
720
721         /* Only list rooms to which the user has access!! */
722         CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
723         if (ra & UA_KNOWN) {
724                 imap_mailboxname(buf, sizeof buf, qrbuf);
725                 if (imap_mailbox_matches_pattern(pattern, buf)) {
726                         cprintf("* LSUB () \"/\" ");
727                         imap_strout(buf);
728                         cprintf("\r\n");
729                 }
730         }
731 }
732
733
734 /*
735  * Implements the LSUB command
736  */
737 void imap_lsub(int num_parms, char *parms[])
738 {
739         char pattern[SIZ];
740         if (num_parms < 4) {
741                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
742                 return;
743         }
744         snprintf(pattern, sizeof pattern, "%s%s", parms[2], parms[3]);
745
746         if (strlen(parms[3]) == 0) {
747                 cprintf("* LIST (\\Noselect) \"/\" \"\"\r\n");
748         }
749
750         else {
751                 imap_list_floors("LSUB", pattern);
752                 ForEachRoom(imap_lsub_listroom, pattern);
753         }
754
755         cprintf("%s OK LSUB completed\r\n", parms[0]);
756 }
757
758
759
760 /*
761  * Back end for imap_list()
762  */
763 void imap_list_listroom(struct ctdlroom *qrbuf, void *data)
764 {
765         char buf[SIZ];
766         int ra;
767         char *pattern;
768
769         pattern = (char *) data;
770
771         /* Only list rooms to which the user has access!! */
772         CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL);
773         if ((ra & UA_KNOWN)
774             || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
775                 imap_mailboxname(buf, sizeof buf, qrbuf);
776                 if (imap_mailbox_matches_pattern(pattern, buf)) {
777                         cprintf("* LIST () \"/\" ");
778                         imap_strout(buf);
779                         cprintf("\r\n");
780                 }
781         }
782 }
783
784
785 /*
786  * Implements the LIST command
787  */
788 void imap_list(int num_parms, char *parms[])
789 {
790         char pattern[SIZ];
791         if (num_parms < 4) {
792                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
793                 return;
794         }
795         snprintf(pattern, sizeof pattern, "%s%s", parms[2], parms[3]);
796
797         if (strlen(parms[3]) == 0) {
798                 cprintf("* LIST (\\Noselect) \"/\" \"\"\r\n");
799         }
800
801         else {
802                 imap_list_floors("LIST", pattern);
803                 ForEachRoom(imap_list_listroom, pattern);
804         }
805
806         cprintf("%s OK LIST completed\r\n", parms[0]);
807 }
808
809
810
811 /*
812  * Implements the CREATE command
813  *
814  */
815 void imap_create(int num_parms, char *parms[])
816 {
817         int ret;
818         char roomname[ROOMNAMELEN];
819         int floornum;
820         int flags;
821         int newroomtype;
822
823         if (strchr(parms[2], '\\') != NULL) {
824                 cprintf("%s NO Invalid character in folder name\r\n",
825                         parms[0]);
826                 lprintf(CTDL_DEBUG, "invalid character in folder name\n");
827                 return;
828         }
829
830         ret = imap_roomname(roomname, sizeof roomname, parms[2]);
831         if (ret < 0) {
832                 cprintf("%s NO Invalid mailbox name or location\r\n",
833                         parms[0]);
834                 lprintf(CTDL_DEBUG, "invalid mailbox name or location\n");
835                 return;
836         }
837         floornum = (ret & 0x00ff);      /* lower 8 bits = floor number */
838         flags = (ret & 0xff00); /* upper 8 bits = flags        */
839
840         if (flags & IR_MAILBOX) {
841                 if (strncasecmp(parms[2], "INBOX/", 6)) {
842                         cprintf("%s NO Personal folders must be created under INBOX\r\n", parms[0]);
843                         lprintf(CTDL_DEBUG, "not subordinate to inbox\n");
844                         return;
845                 }
846         }
847
848         if (flags & IR_MAILBOX) {
849                 newroomtype = 4;        /* private mailbox */
850         } else {
851                 newroomtype = 0;        /* public folder */
852         }
853
854         lprintf(CTDL_INFO, "Create new room <%s> on floor <%d> with type <%d>\n",
855                 roomname, floornum, newroomtype);
856
857         ret = create_room(roomname, newroomtype, "", floornum, 1, 0, VIEW_BBS);
858         if (ret == 0) {
859                 cprintf
860                     ("%s NO Mailbox already exists, or create failed\r\n",
861                      parms[0]);
862         } else {
863                 cprintf("%s OK CREATE completed\r\n", parms[0]);
864         }
865         lprintf(CTDL_DEBUG, "imap_create() completed\n");
866 }
867
868
869 /*
870  * Locate a room by its IMAP folder name, and check access to it
871  */
872 int imap_grabroom(char *returned_roomname, char *foldername)
873 {
874         int ret;
875         char augmented_roomname[ROOMNAMELEN];
876         char roomname[ROOMNAMELEN];
877         int c;
878         struct ctdlroom QRscratch;
879         int ra;
880         int ok = 0;
881
882         ret = imap_roomname(roomname, sizeof roomname, foldername);
883         if (ret < 0) {
884                 return (1);
885         }
886
887         /* First try a regular match */
888         c = getroom(&QRscratch, roomname);
889
890         /* Then try a mailbox name match */
891         if (c != 0) {
892                 MailboxName(augmented_roomname, sizeof augmented_roomname,
893                             &CC->user, roomname);
894                 c = getroom(&QRscratch, augmented_roomname);
895                 if (c == 0)
896                         strcpy(roomname, augmented_roomname);
897         }
898
899         /* If the room exists, check security/access */
900         if (c == 0) {
901                 /* See if there is an existing user/room relationship */
902                 CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
903
904                 /* normal clients have to pass through security */
905                 if (ra & UA_KNOWN) {
906                         ok = 1;
907                 }
908         }
909
910         /* Fail here if no such room */
911         if (!ok) {
912                 strcpy(returned_roomname, "");
913                 return (2);
914         } else {
915                 strcpy(returned_roomname, QRscratch.QRname);
916                 return (0);
917         }
918 }
919
920
921 /*
922  * Implements the STATUS command (sort of)
923  *
924  */
925 void imap_status(int num_parms, char *parms[])
926 {
927         int ret;
928         char roomname[ROOMNAMELEN];
929         char buf[SIZ];
930         char savedroom[ROOMNAMELEN];
931         int msgs, new;
932
933         ret = imap_grabroom(roomname, parms[2]);
934         if (ret != 0) {
935                 cprintf
936                     ("%s NO Invalid mailbox name or location, or access denied\r\n",
937                      parms[0]);
938                 return;
939         }
940
941         /*
942          * usergoto() formally takes us to the desired room, happily returning
943          * the number of messages and number of new messages.  (If another
944          * folder is selected, save its name so we can return there!!!!!)
945          */
946         if (IMAP->selected) {
947                 strcpy(savedroom, CC->room.QRname);
948         }
949         usergoto(roomname, 0, 0, &msgs, &new);
950
951         /*
952          * Tell the client what it wants to know.  In fact, tell it *more* than
953          * it wants to know.  We happily IGnore the supplied status data item
954          * names and simply spew all possible data items.  It's far easier to
955          * code and probably saves us some processing time too.
956          */
957         imap_mailboxname(buf, sizeof buf, &CC->room);
958         cprintf("* STATUS ");
959         imap_strout(buf);
960         cprintf(" (MESSAGES %d ", msgs);
961         cprintf("RECENT %d ", new);     /* Initially, new==recent */
962         cprintf("UIDNEXT %ld ", CitControl.MMhighest + 1);
963         cprintf("UNSEEN %d)\r\n", new);
964
965         /*
966          * If another folder is selected, go back to that room so we can resume
967          * our happy day without violent explosions.
968          */
969         if (IMAP->selected) {
970                 usergoto(savedroom, 0, 0, &msgs, &new);
971         }
972
973         /*
974          * Oooh, look, we're done!
975          */
976         cprintf("%s OK STATUS completed\r\n", parms[0]);
977 }
978
979
980
981 /*
982  * Implements the SUBSCRIBE command
983  *
984  */
985 void imap_subscribe(int num_parms, char *parms[])
986 {
987         int ret;
988         char roomname[ROOMNAMELEN];
989         char savedroom[ROOMNAMELEN];
990         int msgs, new;
991
992         ret = imap_grabroom(roomname, parms[2]);
993         if (ret != 0) {
994                 cprintf
995                     ("%s NO Invalid mailbox name or location, or access denied\r\n",
996                      parms[0]);
997                 return;
998         }
999
1000         /*
1001          * usergoto() formally takes us to the desired room, which has the side
1002          * effect of marking the room as not-zapped ... exactly the effect
1003          * we're looking for.
1004          */
1005         if (IMAP->selected) {
1006                 strcpy(savedroom, CC->room.QRname);
1007         }
1008         usergoto(roomname, 0, 0, &msgs, &new);
1009
1010         /*
1011          * If another folder is selected, go back to that room so we can resume
1012          * our happy day without violent explosions.
1013          */
1014         if (IMAP->selected) {
1015                 usergoto(savedroom, 0, 0, &msgs, &new);
1016         }
1017
1018         cprintf("%s OK SUBSCRIBE completed\r\n", parms[0]);
1019 }
1020
1021
1022 /*
1023  * Implements the UNSUBSCRIBE command
1024  *
1025  */
1026 void imap_unsubscribe(int num_parms, char *parms[])
1027 {
1028         int ret;
1029         char roomname[ROOMNAMELEN];
1030         char savedroom[ROOMNAMELEN];
1031         int msgs, new;
1032
1033         ret = imap_grabroom(roomname, parms[2]);
1034         if (ret != 0) {
1035                 cprintf
1036                     ("%s NO Invalid mailbox name or location, or access denied\r\n",
1037                      parms[0]);
1038                 return;
1039         }
1040
1041         /*
1042          * usergoto() formally takes us to the desired room.
1043          */
1044         if (IMAP->selected) {
1045                 strcpy(savedroom, CC->room.QRname);
1046         }
1047         usergoto(roomname, 0, 0, &msgs, &new);
1048
1049         /* 
1050          * Now make the API call to zap the room
1051          */
1052         if (CtdlForgetThisRoom() == 0) {
1053                 cprintf("%s OK UNSUBSCRIBE completed\r\n", parms[0]);
1054         } else {
1055                 cprintf
1056                     ("%s NO You may not unsubscribe from this folder.\r\n",
1057                      parms[0]);
1058         }
1059
1060         /*
1061          * If another folder is selected, go back to that room so we can resume
1062          * our happy day without violent explosions.
1063          */
1064         if (IMAP->selected) {
1065                 usergoto(savedroom, 0, 0, &msgs, &new);
1066         }
1067 }
1068
1069
1070
1071 /*
1072  * Implements the DELETE command
1073  *
1074  */
1075 void imap_delete(int num_parms, char *parms[])
1076 {
1077         int ret;
1078         char roomname[ROOMNAMELEN];
1079         char savedroom[ROOMNAMELEN];
1080         int msgs, new;
1081
1082         ret = imap_grabroom(roomname, parms[2]);
1083         if (ret != 0) {
1084                 cprintf("%s NO Invalid mailbox name, or access denied\r\n",
1085                         parms[0]);
1086                 return;
1087         }
1088
1089         /*
1090          * usergoto() formally takes us to the desired room, happily returning
1091          * the number of messages and number of new messages.  (If another
1092          * folder is selected, save its name so we can return there!!!!!)
1093          */
1094         if (IMAP->selected) {
1095                 strcpy(savedroom, CC->room.QRname);
1096         }
1097         usergoto(roomname, 0, 0, &msgs, &new);
1098
1099         /*
1100          * Now delete the room.
1101          */
1102         if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
1103                 cprintf("%s OK DELETE completed\r\n", parms[0]);
1104                 delete_room(&CC->room);
1105         } else {
1106                 cprintf("%s NO Can't delete this folder.\r\n", parms[0]);
1107         }
1108
1109         /*
1110          * If another folder is selected, go back to that room so we can resume
1111          * our happy day without violent explosions.
1112          */
1113         if (IMAP->selected) {
1114                 usergoto(savedroom, 0, 0, &msgs, &new);
1115         }
1116 }
1117
1118
1119 /*
1120  * Back end function for imap_rename()
1121  */
1122 void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
1123 {
1124         char foldername[SIZ];
1125         char newfoldername[SIZ];
1126         char newroomname[ROOMNAMELEN];
1127         int newfloor = 0;
1128         struct irl *irlp = NULL;        /* scratch pointer */
1129         struct irlparms *irlparms;
1130
1131         irlparms = (struct irlparms *) data;
1132         imap_mailboxname(foldername, sizeof foldername, qrbuf);
1133
1134         /* Rename subfolders */
1135         if ((!strncasecmp(foldername, irlparms->oldname,
1136                           strlen(irlparms->oldname))
1137              && (foldername[strlen(irlparms->oldname)] == '/'))) {
1138
1139                 sprintf(newfoldername, "%s/%s",
1140                         irlparms->newname,
1141                         &foldername[strlen(irlparms->oldname) + 1]
1142                     );
1143
1144                 newfloor = imap_roomname(newroomname,
1145                                          sizeof newroomname,
1146                                          newfoldername) & 0xFF;
1147
1148                 irlp = (struct irl *) malloc(sizeof(struct irl));
1149                 strcpy(irlp->irl_newroom, newroomname);
1150                 strcpy(irlp->irl_oldroom, qrbuf->QRname);
1151                 irlp->irl_newfloor = newfloor;
1152                 irlp->next = *(irlparms->irl);
1153                 *(irlparms->irl) = irlp;
1154         }
1155 }
1156
1157
1158 /*
1159  * Implements the RENAME command
1160  *
1161  */
1162 void imap_rename(int num_parms, char *parms[])
1163 {
1164         char old_room[ROOMNAMELEN];
1165         char new_room[ROOMNAMELEN];
1166         int oldr, newr;
1167         int new_floor;
1168         int r;
1169         struct irl *irl = NULL; /* the list */
1170         struct irl *irlp = NULL;        /* scratch pointer */
1171         struct irlparms irlparms;
1172
1173         if (strchr(parms[3], '\\') != NULL) {
1174                 cprintf("%s NO Invalid character in folder name\r\n",
1175                         parms[0]);
1176                 return;
1177         }
1178
1179         oldr = imap_roomname(old_room, sizeof old_room, parms[2]);
1180         newr = imap_roomname(new_room, sizeof new_room, parms[3]);
1181         new_floor = (newr & 0xFF);
1182
1183         r = CtdlRenameRoom(old_room, new_room, new_floor);
1184
1185         if (r == crr_room_not_found) {
1186                 cprintf("%s NO Could not locate this folder\r\n",
1187                         parms[0]);
1188                 return;
1189         }
1190         if (r == crr_already_exists) {
1191                 cprintf("%s '%s' already exists.\r\n", parms[0], parms[2]);
1192                 return;
1193         }
1194         if (r == crr_noneditable) {
1195                 cprintf("%s This folder is not editable.\r\n", parms[0]);
1196                 return;
1197         }
1198         if (r == crr_invalid_floor) {
1199                 cprintf("%s Folder root does not exist.\r\n", parms[0]);
1200                 return;
1201         }
1202         if (r == crr_access_denied) {
1203                 cprintf("%s You do not have permission to edit "
1204                         "this folder.\r\n", parms[0]);
1205                 return;
1206         }
1207         if (r != crr_ok) {
1208                 cprintf("%s NO Rename failed - undefined error %d\r\n",
1209                         parms[0], r);
1210                 return;
1211         }
1212
1213
1214         /* If this is the INBOX, then RFC2060 says we have to just move the
1215          * contents.  In a Citadel environment it's easier to rename the room
1216          * (already did that) and create a new inbox.
1217          */
1218         if (!strcasecmp(parms[2], "INBOX")) {
1219                 create_room(MAILROOM, 4, "", 0, 1, 0, VIEW_BBS);
1220         }
1221
1222         /* Otherwise, do the subfolders.  Build a list of rooms to rename... */
1223         else {
1224                 irlparms.oldname = parms[2];
1225                 irlparms.newname = parms[3];
1226                 irlparms.irl = &irl;
1227                 ForEachRoom(imap_rename_backend, (void *) &irlparms);
1228
1229                 /* ... and now rename them. */
1230                 while (irl != NULL) {
1231                         r = CtdlRenameRoom(irl->irl_oldroom,
1232                                            irl->irl_newroom,
1233                                            irl->irl_newfloor);
1234                         if (r != crr_ok) {
1235                                 /* FIXME handle error returns better */
1236                                 lprintf(CTDL_ERR, "CtdlRenameRoom() error %d\n",
1237                                         r);
1238                         }
1239                         irlp = irl;
1240                         irl = irl->next;
1241                         free(irlp);
1242                 }
1243         }
1244
1245         cprintf("%s OK RENAME completed\r\n", parms[0]);
1246 }
1247
1248
1249
1250
1251 /* 
1252  * Main command loop for IMAP sessions.
1253  */
1254 void imap_command_loop(void)
1255 {
1256         char cmdbuf[SIZ];
1257         char *parms[SIZ];
1258         int num_parms;
1259
1260         CC->lastcmd = time(NULL);
1261         memset(cmdbuf, 0, sizeof cmdbuf);       /* Clear it, just in case */
1262         flush_output();
1263         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
1264                 lprintf(CTDL_ERR, "IMAP socket is broken.  Ending session.\r\n");
1265                 CC->kill_me = 1;
1266                 return;
1267         }
1268
1269         lprintf(CTDL_INFO, "IMAP: %s\n", cmdbuf);
1270         while (strlen(cmdbuf) < 5)
1271                 strcat(cmdbuf, " ");
1272
1273         /* strip off l/t whitespace and CRLF */
1274         if (cmdbuf[strlen(cmdbuf) - 1] == '\n')
1275                 cmdbuf[strlen(cmdbuf) - 1] = 0;
1276         if (cmdbuf[strlen(cmdbuf) - 1] == '\r')
1277                 cmdbuf[strlen(cmdbuf) - 1] = 0;
1278         striplt(cmdbuf);
1279
1280         /* If we're in the middle of a multi-line command, handle that */
1281         if (IMAP->authstate == imap_as_expecting_username) {
1282                 imap_auth_login_user(cmdbuf);
1283                 return;
1284         }
1285         if (IMAP->authstate == imap_as_expecting_password) {
1286                 imap_auth_login_pass(cmdbuf);
1287                 return;
1288         }
1289
1290         /* Ok, at this point we're in normal command mode.  The first thing
1291          * we do is print any incoming pages (yeah! we really do!)
1292          */
1293         imap_print_instant_messages();
1294
1295         /*
1296          * Before processing the command that was just entered... if we happen
1297          * to have a folder selected, we'd like to rescan that folder for new
1298          * messages, and for deletions/changes of existing messages.  This
1299          * could probably be optimized somehow, but IMAP sucks...
1300          */
1301         if (IMAP->selected) {
1302                 imap_rescan_msgids();
1303         }
1304
1305         /* Now for the command set. */
1306
1307         /* Grab the tag, command, and parameters.  Check syntax. */
1308         num_parms = imap_parameterize(parms, cmdbuf);
1309         if (num_parms < 2) {
1310                 cprintf("BAD syntax error\r\n");
1311         }
1312
1313         /* The commands below may be executed in any state */
1314
1315         else if ((!strcasecmp(parms[1], "NOOP"))
1316                  || (!strcasecmp(parms[1], "CHECK"))) {
1317                 cprintf("%s OK This command successfully did nothing.\r\n",
1318                         parms[0]);
1319         }
1320
1321         else if (!strcasecmp(parms[1], "LOGOUT")) {
1322                 if (IMAP->selected) {
1323                         imap_do_expunge();      /* yes, we auto-expunge */
1324                 }
1325                 cprintf("* BYE %s logging out\r\n", config.c_fqdn);
1326                 cprintf("%s OK thank you for using Citadel IMAP\r\n",
1327                         parms[0]);
1328                 CC->kill_me = 1;
1329                 return;
1330         }
1331
1332         else if (!strcasecmp(parms[1], "LOGIN")) {
1333                 imap_login(num_parms, parms);
1334         }
1335
1336         else if (!strcasecmp(parms[1], "AUTHENTICATE")) {
1337                 imap_authenticate(num_parms, parms);
1338         }
1339
1340         else if (!strcasecmp(parms[1], "CAPABILITY")) {
1341                 imap_capability(num_parms, parms);
1342         }
1343 #ifdef HAVE_OPENSSL
1344         else if (!strcasecmp(parms[1], "STARTTLS")) {
1345                 imap_starttls(num_parms, parms);
1346         }
1347 #endif
1348         else if (!CC->logged_in) {
1349                 cprintf("%s BAD Not logged in.\r\n", parms[0]);
1350         }
1351
1352         /* The commans below require a logged-in state */
1353
1354         else if (!strcasecmp(parms[1], "SELECT")) {
1355                 imap_select(num_parms, parms);
1356         }
1357
1358         else if (!strcasecmp(parms[1], "EXAMINE")) {
1359                 imap_select(num_parms, parms);
1360         }
1361
1362         else if (!strcasecmp(parms[1], "LSUB")) {
1363                 imap_lsub(num_parms, parms);
1364         }
1365
1366         else if (!strcasecmp(parms[1], "LIST")) {
1367                 imap_list(num_parms, parms);
1368         }
1369
1370         else if (!strcasecmp(parms[1], "CREATE")) {
1371                 imap_create(num_parms, parms);
1372         }
1373
1374         else if (!strcasecmp(parms[1], "DELETE")) {
1375                 imap_delete(num_parms, parms);
1376         }
1377
1378         else if (!strcasecmp(parms[1], "RENAME")) {
1379                 imap_rename(num_parms, parms);
1380         }
1381
1382         else if (!strcasecmp(parms[1], "STATUS")) {
1383                 imap_status(num_parms, parms);
1384         }
1385
1386         else if (!strcasecmp(parms[1], "SUBSCRIBE")) {
1387                 imap_subscribe(num_parms, parms);
1388         }
1389
1390         else if (!strcasecmp(parms[1], "UNSUBSCRIBE")) {
1391                 imap_unsubscribe(num_parms, parms);
1392         }
1393
1394         else if (!strcasecmp(parms[1], "APPEND")) {
1395                 imap_append(num_parms, parms);
1396         }
1397
1398         else if (!strcasecmp(parms[1], "NAMESPACE")) {
1399                 imap_namespace(num_parms, parms);
1400         }
1401
1402         else if (IMAP->selected == 0) {
1403                 cprintf("%s BAD no folder selected\r\n", parms[0]);
1404         }
1405
1406         /* The commands below require the SELECT state on a mailbox */
1407
1408         else if (!strcasecmp(parms[1], "FETCH")) {
1409                 imap_fetch(num_parms, parms);
1410         }
1411
1412         else if ((!strcasecmp(parms[1], "UID"))
1413                  && (!strcasecmp(parms[2], "FETCH"))) {
1414                 imap_uidfetch(num_parms, parms);
1415         }
1416
1417         else if (!strcasecmp(parms[1], "SEARCH")) {
1418                 imap_search(num_parms, parms);
1419         }
1420
1421         else if ((!strcasecmp(parms[1], "UID"))
1422                  && (!strcasecmp(parms[2], "SEARCH"))) {
1423                 imap_uidsearch(num_parms, parms);
1424         }
1425
1426         else if (!strcasecmp(parms[1], "STORE")) {
1427                 imap_store(num_parms, parms);
1428         }
1429
1430         else if ((!strcasecmp(parms[1], "UID"))
1431                  && (!strcasecmp(parms[2], "STORE"))) {
1432                 imap_uidstore(num_parms, parms);
1433         }
1434
1435         else if (!strcasecmp(parms[1], "COPY")) {
1436                 imap_copy(num_parms, parms);
1437         }
1438
1439         else if ((!strcasecmp(parms[1], "UID"))
1440                  && (!strcasecmp(parms[2], "COPY"))) {
1441                 imap_uidcopy(num_parms, parms);
1442         }
1443
1444         else if (!strcasecmp(parms[1], "EXPUNGE")) {
1445                 imap_expunge(num_parms, parms);
1446         }
1447
1448         else if (!strcasecmp(parms[1], "CLOSE")) {
1449                 imap_close(num_parms, parms);
1450         }
1451
1452         /* End of commands.  If we get here, the command is either invalid
1453          * or unimplemented.
1454          */
1455
1456         else {
1457                 cprintf("%s BAD command unrecognized\r\n", parms[0]);
1458         }
1459
1460         /* If the client transmitted a message we can free it now */
1461         imap_free_transmitted_message();
1462 }
1463
1464
1465
1466 /*
1467  * This function is called to register the IMAP extension with Citadel.
1468  */
1469 char *serv_imap_init(void)
1470 {
1471         CtdlRegisterServiceHook(config.c_imap_port,
1472                                 NULL, imap_greeting, imap_command_loop, NULL);
1473         CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP);
1474         return "$Id$";
1475 }