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