More removal of $Id$ tags
[citadel.git] / citadel / modules / imap / serv_imap.c
1 /*
2  * IMAP server for the Citadel system
3  * Copyright (C) 2000-2009 by Art Cancro and others.
4  * This code is released under the terms of the GNU General Public License.
5  *
6  * WARNING: the IMAP protocol is badly designed.  No implementation of it
7  * is perfect.  Indeed, with so much gratuitous complexity, *all* IMAP
8  * implementations have bugs.
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <sys/types.h>
34
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # if HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #include <sys/wait.h>
47 #include <ctype.h>
48 #include <string.h>
49 #include <limits.h>
50 #include <libcitadel.h>
51 #include "citadel.h"
52 #include "server.h"
53 #include "citserver.h"
54 #include "support.h"
55 #include "config.h"
56 #include "user_ops.h"
57 #include "database.h"
58 #include "msgbase.h"
59 #include "internet_addressing.h"
60 #include "imap_tools.h"
61 #include "serv_imap.h"
62 #include "imap_list.h"
63 #include "imap_fetch.h"
64 #include "imap_search.h"
65 #include "imap_store.h"
66 #include "imap_acl.h"
67 #include "imap_metadata.h"
68 #include "imap_misc.h"
69
70 #include "ctdl_module.h"
71
72
73 /* imap_rename() uses this struct containing list of rooms to rename */
74 struct irl {
75         struct irl *next;
76         char irl_oldroom[ROOMNAMELEN];
77         char irl_newroom[ROOMNAMELEN];
78         int irl_newfloor;
79 };
80
81 /* Data which is passed between imap_rename() and imap_rename_backend() */
82 struct irlparms {
83         char *oldname;
84         char *newname;
85         struct irl **irl;
86 };
87
88
89 /*
90  * If there is a message ID map in memory, free it
91  */
92 void imap_free_msgids(void)
93 {
94         if (IMAP->msgids != NULL) {
95                 free(IMAP->msgids);
96                 IMAP->msgids = NULL;
97                 IMAP->num_msgs = 0;
98                 IMAP->num_alloc = 0;
99         }
100         if (IMAP->flags != NULL) {
101                 free(IMAP->flags);
102                 IMAP->flags = NULL;
103         }
104         IMAP->last_mtime = (-1);
105 }
106
107
108 /*
109  * If there is a transmitted message in memory, free it
110  */
111 void imap_free_transmitted_message(void)
112 {
113         FreeStrBuf(&IMAP->TransmittedMessage);
114 }
115
116
117 /*
118  * Set the \Seen, \Recent. and \Answered flags, based on the sequence
119  * sets stored in the visit record for this user/room.  Note that we have
120  * to parse each sequence set manually here, because calling the utility
121  * function is_msg_in_sequence_set() over and over again is too expensive.
122  *
123  * first_msg should be set to 0 to rescan the flags for every message in the
124  * room, or some other value if we're only interested in an incremental
125  * update.
126  */
127 void imap_set_seen_flags(int first_msg)
128 {
129         visit vbuf;
130         int i;
131         int num_sets;
132         int s;
133         char setstr[64], lostr[64], histr[64];
134         long lo, hi;
135
136         if (IMAP->num_msgs < 1) return;
137         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
138
139         for (i = first_msg; i < IMAP->num_msgs; ++i) {
140                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SEEN;
141                 IMAP->flags[i] |= IMAP_RECENT;
142                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_ANSWERED;
143         }
144
145         /*
146          * Do the "\Seen" flag.
147          * (Any message not "\Seen" is considered "\Recent".)
148          */
149         num_sets = num_tokens(vbuf.v_seen, ',');
150         for (s=0; s<num_sets; ++s) {
151                 extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
152
153                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
154                 if (num_tokens(setstr, ':') >= 2) {
155                         extract_token(histr, setstr, 1, ':', sizeof histr);
156                         if (!strcmp(histr, "*")) {
157                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
158                         }
159                 } 
160                 else {
161                         strcpy(histr, lostr);
162                 }
163                 lo = atol(lostr);
164                 hi = atol(histr);
165
166                 for (i = first_msg; i < IMAP->num_msgs; ++i) {
167                         if ((IMAP->msgids[i] >= lo) && (IMAP->msgids[i] <= hi)){
168                                 IMAP->flags[i] |= IMAP_SEEN;
169                                 IMAP->flags[i] = IMAP->flags[i] & ~IMAP_RECENT;
170                         }
171                 }
172         }
173
174         /* Do the ANSWERED flag */
175         num_sets = num_tokens(vbuf.v_answered, ',');
176         for (s=0; s<num_sets; ++s) {
177                 extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
178
179                 extract_token(lostr, setstr, 0, ':', sizeof lostr);
180                 if (num_tokens(setstr, ':') >= 2) {
181                         extract_token(histr, setstr, 1, ':', sizeof histr);
182                         if (!strcmp(histr, "*")) {
183                                 snprintf(histr, sizeof histr, "%ld", LONG_MAX);
184                         }
185                 } 
186                 else {
187                         strcpy(histr, lostr);
188                 }
189                 lo = atol(lostr);
190                 hi = atol(histr);
191
192                 for (i = first_msg; i < IMAP->num_msgs; ++i) {
193                         if ((IMAP->msgids[i] >= lo) && (IMAP->msgids[i] <= hi)){
194                                 IMAP->flags[i] |= IMAP_ANSWERED;
195                         }
196                 }
197         }
198
199 }
200
201
202
203 /*
204  * Back end for imap_load_msgids()
205  *
206  * Optimization: instead of calling realloc() to add each message, we
207  * allocate space in the list for REALLOC_INCREMENT messages at a time.  This
208  * allows the mapping to proceed much faster.
209  */
210 void imap_add_single_msgid(long msgnum, void *userdata)
211 {
212
213         ++IMAP->num_msgs;
214         if (IMAP->num_msgs > IMAP->num_alloc) {
215                 IMAP->num_alloc += REALLOC_INCREMENT;
216                 IMAP->msgids = realloc(IMAP->msgids, (IMAP->num_alloc * sizeof(long)) );
217                 IMAP->flags = realloc(IMAP->flags, (IMAP->num_alloc * sizeof(long)) );
218         }
219         IMAP->msgids[IMAP->num_msgs - 1] = msgnum;
220         IMAP->flags[IMAP->num_msgs - 1] = 0;
221 }
222
223
224
225 /*
226  * Set up a message ID map for the current room (folder)
227  */
228 void imap_load_msgids(void)
229 {
230         struct cdbdata *cdbfr;
231
232         if (IMAP->selected == 0) {
233                 CtdlLogPrintf(CTDL_ERR,
234                         "imap_load_msgids() can't run; no room selected\n");
235                 return;
236         }
237
238         imap_free_msgids();     /* If there was already a map, free it */
239
240         /* Load the message list */
241         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
242         if (cdbfr != NULL) {
243                 IMAP->msgids = malloc(cdbfr->len);
244                 memcpy(IMAP->msgids, cdbfr->ptr, cdbfr->len);
245                 IMAP->num_msgs = cdbfr->len / sizeof(long);
246                 IMAP->num_alloc = cdbfr->len / sizeof(long);
247                 cdb_free(cdbfr);
248         }
249
250         if (IMAP->num_msgs) {
251                 IMAP->flags = malloc(IMAP->num_alloc * sizeof(long));
252                 memset(IMAP->flags, 0, (IMAP->num_alloc * sizeof(long)) );
253         }
254
255         imap_set_seen_flags(0);
256 }
257
258
259 /*
260  * Re-scan the selected room (folder) and see if it's been changed at all
261  */
262 void imap_rescan_msgids(void)
263 {
264
265         int original_num_msgs = 0;
266         long original_highest = 0L;
267         int i, j, jstart;
268         int message_still_exists;
269         struct cdbdata *cdbfr;
270         long *msglist = NULL;
271         int num_msgs = 0;
272         int num_recent = 0;
273
274         if (IMAP->selected == 0) {
275                 CtdlLogPrintf(CTDL_ERR, "imap_load_msgids() can't run; no room selected\n");
276                 return;
277         }
278
279         /*
280          * Check to see if the room's contents have changed.
281          * If not, we can avoid this rescan.
282          */
283         CtdlGetRoom(&CC->room, CC->room.QRname);
284         if (IMAP->last_mtime == CC->room.QRmtime) {     /* No changes! */
285                 return;
286         }
287
288         /* Load the *current* message list from disk, so we can compare it
289          * to what we have in memory.
290          */
291         cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
292         if (cdbfr != NULL) {
293                 msglist = malloc(cdbfr->len);
294                 if (msglist == NULL) {
295                         CtdlLogPrintf(CTDL_CRIT, "malloc() failed\n");
296                         abort();
297                 }
298                 memcpy(msglist, cdbfr->ptr, (size_t)cdbfr->len);
299                 num_msgs = cdbfr->len / sizeof(long);
300                 cdb_free(cdbfr);
301         } else {
302                 num_msgs = 0;
303         }
304
305         /*
306          * Check to see if any of the messages we know about have been expunged
307          */
308         if (IMAP->num_msgs > 0) {
309                 jstart = 0;
310                 for (i = 0; i < IMAP->num_msgs; ++i) {
311
312                         message_still_exists = 0;
313                         if (num_msgs > 0) {
314                                 for (j = jstart; j < num_msgs; ++j) {
315                                         if (msglist[j] == IMAP->msgids[i]) {
316                                                 message_still_exists = 1;
317                                                 jstart = j;
318                                                 break;
319                                         }
320                                 }
321                         }
322
323                         if (message_still_exists == 0) {
324                                 cprintf("* %d EXPUNGE\r\n", i + 1);
325
326                                 /* Here's some nice stupid nonsense.  When a
327                                  * message is expunged, we have to slide all
328                                  * the existing messages up in the message
329                                  * array.
330                                  */
331                                 --IMAP->num_msgs;
332                                 memcpy(&IMAP->msgids[i],
333                                        &IMAP->msgids[i + 1],
334                                        (sizeof(long) *
335                                         (IMAP->num_msgs - i)));
336                                 memcpy(&IMAP->flags[i],
337                                        &IMAP->flags[i + 1],
338                                        (sizeof(long) *
339                                         (IMAP->num_msgs - i)));
340
341                                 --i;
342                         }
343
344                 }
345         }
346
347         /*
348          * Remember how many messages were here before we re-scanned.
349          */
350         original_num_msgs = IMAP->num_msgs;
351         if (IMAP->num_msgs > 0) {
352                 original_highest = IMAP->msgids[IMAP->num_msgs - 1];
353         } else {
354                 original_highest = 0L;
355         }
356
357         /*
358          * Now peruse the room for *new* messages only.
359          * This logic is probably the cause of Bug # 368
360          * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
361          */
362         if (num_msgs > 0) {
363                 for (j = 0; j < num_msgs; ++j) {
364                         if (msglist[j] > original_highest) {
365                                 imap_add_single_msgid(msglist[j], NULL);
366                         }
367                 }
368         }
369         imap_set_seen_flags(original_num_msgs);
370
371         /*
372          * If new messages have arrived, tell the client about them.
373          */
374         if (IMAP->num_msgs > original_num_msgs) {
375
376                 for (j = 0; j < num_msgs; ++j) {
377                         if (IMAP->flags[j] & IMAP_RECENT) {
378                                 ++num_recent;
379                         }
380                 }
381
382                 cprintf("* %d EXISTS\r\n", IMAP->num_msgs);
383                 cprintf("* %d RECENT\r\n", num_recent);
384         }
385
386         if (num_msgs != 0) {
387                 free(msglist);
388         }
389         IMAP->last_mtime = CC->room.QRmtime;
390 }
391
392
393 /*
394  * This cleanup function blows away the temporary memory and files used by
395  * the IMAP server.
396  */
397 void imap_cleanup_function(void)
398 {
399
400         /* Don't do this stuff if this is not a IMAP session! */
401         if (CC->h_command_function != imap_command_loop)
402                 return;
403
404         /* If there is a mailbox selected, auto-expunge it. */
405         if (IMAP->selected) {
406                 imap_do_expunge();
407         }
408
409         CtdlLogPrintf(CTDL_DEBUG, "Performing IMAP cleanup hook\n");
410         imap_free_msgids();
411         imap_free_transmitted_message();
412
413         if (IMAP->cached_rfc822 != NULL) {
414                 FreeStrBuf(&IMAP->cached_rfc822);
415                 IMAP->cached_rfc822_msgnum = (-1);
416                 IMAP->cached_rfc822_withbody = 0;
417         }
418
419         if (IMAP->cached_body != NULL) {
420                 free(IMAP->cached_body);
421                 IMAP->cached_body = NULL;
422                 IMAP->cached_body_len = 0;
423                 IMAP->cached_bodymsgnum = (-1);
424         }
425         FreeStrBuf(&IMAP->Cmd.CmdBuf);
426         if (IMAP->Cmd.Params != NULL) free(IMAP->Cmd.Params);
427         free(IMAP);
428         CtdlLogPrintf(CTDL_DEBUG, "Finished IMAP cleanup hook\n");
429 }
430
431
432 /*
433  * Does the actual work of the CAPABILITY command (because we need to
434  * output this stuff in other places as well)
435  */
436 void imap_output_capability_string(void) {
437         cprintf("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
438
439 #ifdef HAVE_OPENSSL
440         if (!CC->redirect_ssl) cprintf(" STARTTLS");
441 #endif
442
443 #ifndef DISABLE_IMAP_ACL
444         cprintf(" ACL");
445 #endif
446
447         /* We are building a partial implementation of METADATA for the sole purpose
448          * of interoperating with the ical/vcard version of the Bynari Insight Connector.
449          * It is not a full RFC5464 implementation, but it should refuse non-Bynari
450          * metadata in a compatible and graceful way.
451          */
452         cprintf(" METADATA");
453
454         /*
455          * LIST-EXTENDED was originally going to be required by the METADATA extension.
456          * It was mercifully removed prior to the finalization of RFC5464.  We started
457          * implementing this but stopped when we learned that it would not be needed.
458          * If you uncomment this declaration you are responsible for writing a lot of new
459          * code.
460          *
461          * cprintf(" LIST-EXTENDED")
462          */
463 }
464
465
466 /*
467  * implements the CAPABILITY command
468  */
469 void imap_capability(int num_parms, ConstStr *Params)
470 {
471         cprintf("* ");
472         imap_output_capability_string();
473         cprintf("\r\n");
474         cprintf("%s OK CAPABILITY completed\r\n", Params[0].Key);
475 }
476
477
478 /*
479  * Implements the ID command (specified by RFC2971)
480  *
481  * We ignore the client-supplied information, and output a NIL response.
482  * Although this is technically a valid implementation of the extension, it
483  * is quite useless.  It exists only so that we may see which clients are
484  * making use of this extension.
485  * 
486  */
487 void imap_id(int num_parms, ConstStr *Params)
488 {
489         cprintf("* ID NIL\r\n");
490         cprintf("%s OK ID completed\r\n", Params[0].Key);
491 }
492
493
494 /*
495  * Here's where our IMAP session begins its happy day.
496  */
497 void imap_greeting(void)
498 {
499
500         strcpy(CC->cs_clientname, "IMAP session");
501         CC->session_specific_data = malloc(sizeof(citimap));
502         memset(IMAP, 0, sizeof(citimap));
503         IMAP->authstate = imap_as_normal;
504         IMAP->cached_rfc822_msgnum = (-1);
505         IMAP->cached_rfc822_withbody = 0;
506
507         if (CC->nologin)
508         {
509                 cprintf("* BYE; Server busy, try later\r\n");
510                 CC->kill_me = 1;
511                 return;
512         }
513         cprintf("* OK [");
514         imap_output_capability_string();
515         cprintf("] %s IMAP4rev1 %s ready\r\n", config.c_fqdn, CITADEL);
516 }
517
518
519 /*
520  * IMAPS is just like IMAP, except it goes crypto right away.
521  */
522 void imaps_greeting(void) {
523         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
524 #ifdef HAVE_OPENSSL
525         if (!CC->redirect_ssl) CC->kill_me = 1;         /* kill session if no crypto */
526 #endif
527         imap_greeting();
528 }
529
530
531 /*
532  * implements the LOGIN command (ordinary username/password login)
533  */
534 void imap_login(int num_parms, ConstStr *Params)
535 {
536
537         switch (num_parms) {
538         case 3:
539                 if (Params[2].Key[0] == '{') {
540                         cprintf("+ go ahead\r\n");
541                         IMAP->authstate = imap_as_expecting_multilineusername;
542                         strcpy(IMAP->authseq, Params[0].Key);
543                         return;
544                 }
545                 else {
546                         cprintf("%s BAD incorrect number of parameters\r\n", Params[0].Key);
547                         return;
548                 }
549         case 4:
550                 if (CtdlLoginExistingUser(NULL, Params[2].Key) == login_ok) {
551                         if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
552                                 cprintf("%s OK [", Params[0].Key);
553                                 imap_output_capability_string();
554                                 cprintf("] Hello, %s\r\n", CC->user.fullname);
555                                 return;
556                         }
557                         else
558                         {
559                                 cprintf("%s NO AUTHENTICATE %s failed\r\n",
560                                         Params[0].Key, Params[3].Key);
561                         }
562                 }
563
564                 cprintf("%s BAD Login incorrect\r\n", Params[0].Key);
565         default:
566                 cprintf("%s BAD incorrect number of parameters\r\n", Params[0].Key);
567                 return;
568         }
569
570 }
571
572
573 /*
574  * Implements the AUTHENTICATE command
575  */
576 void imap_authenticate(int num_parms, ConstStr *Params)
577 {
578         char UsrBuf[SIZ];
579
580         if (num_parms != 3) {
581                 cprintf("%s BAD incorrect number of parameters\r\n",
582                         Params[0].Key);
583                 return;
584         }
585
586         if (CC->logged_in) {
587                 cprintf("%s BAD Already logged in.\r\n", Params[0].Key);
588                 return;
589         }
590
591         if (!strcasecmp(Params[2].Key, "LOGIN")) {
592                 CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
593                 cprintf("+ %s\r\n", UsrBuf);
594                 IMAP->authstate = imap_as_expecting_username;
595                 strcpy(IMAP->authseq, Params[0].Key);
596                 return;
597         }
598
599         if (!strcasecmp(Params[2].Key, "PLAIN")) {
600                 // CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
601                 // cprintf("+ %s\r\n", UsrBuf);
602                 cprintf("+ \r\n");
603                 IMAP->authstate = imap_as_expecting_plainauth;
604                 strcpy(IMAP->authseq, Params[0].Key);
605                 return;
606         }
607
608         else {
609                 cprintf("%s NO AUTHENTICATE %s failed\r\n",
610                         Params[0].Key, Params[1].Key);
611         }
612 }
613
614
615 void imap_auth_plain(void)
616 {
617         const char *decoded_authstring;
618         char ident[256];
619         char user[256];
620         char pass[256];
621         int result;
622         long len;
623
624         memset(pass, 0, sizeof(pass));
625         StrBufDecodeBase64(IMAP->Cmd.CmdBuf);
626
627         decoded_authstring = ChrPtr(IMAP->Cmd.CmdBuf);
628         safestrncpy(ident, decoded_authstring, sizeof ident);
629         safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
630         len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
631         if (len < 0)
632                 len = sizeof(pass) - 1;
633
634         IMAP->authstate = imap_as_normal;
635
636         if (!IsEmptyStr(ident)) {
637                 result = CtdlLoginExistingUser(user, ident);
638         }
639         else {
640                 result = CtdlLoginExistingUser(NULL, user);
641         }
642
643         if (result == login_ok) {
644                 if (CtdlTryPassword(pass, len) == pass_ok) {
645                         cprintf("%s OK authentication succeeded\r\n", IMAP->authseq);
646                         return;
647                 }
648         }
649         cprintf("%s NO authentication failed\r\n", IMAP->authseq);
650 }
651
652
653 void imap_auth_login_user(long state)
654 {
655         char PWBuf[SIZ];
656         citimap *Imap = IMAP;
657
658         switch (state){
659         case imap_as_expecting_username:
660                 StrBufDecodeBase64(Imap->Cmd.CmdBuf);
661                 CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf));
662                 CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
663                 cprintf("+ %s\r\n", PWBuf);
664                 
665                 Imap->authstate = imap_as_expecting_password;
666                 return;
667         case imap_as_expecting_multilineusername:
668                 extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
669                 CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf));
670                 cprintf("+ go ahead\r\n");
671                 IMAP->authstate = imap_as_expecting_multilinepassword;
672                 return;
673         }
674 }
675
676
677 void imap_auth_login_pass(long state)
678 {
679         citimap *Imap = IMAP;
680         const char *pass = NULL;
681         long len = 0;
682
683         switch (state) {
684         default:
685         case imap_as_expecting_password:
686                 StrBufDecodeBase64(Imap->Cmd.CmdBuf);
687                 pass = ChrPtr(Imap->Cmd.CmdBuf);
688                 len = StrLength(Imap->Cmd.CmdBuf);
689                 break;
690         case imap_as_expecting_multilinepassword:
691                 pass = ChrPtr(Imap->Cmd.CmdBuf);
692                 len = StrLength(Imap->Cmd.CmdBuf);
693                 break;
694         }
695         if (len > USERNAME_SIZE)
696                 StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
697
698         if (CtdlTryPassword(pass, len) == pass_ok) {
699                 cprintf("%s OK authentication succeeded\r\n", IMAP->authseq);
700         } else {
701                 cprintf("%s NO authentication failed\r\n", IMAP->authseq);
702         }
703         IMAP->authstate = imap_as_normal;
704         return;
705 }
706
707
708 /*
709  * implements the STARTTLS command (Citadel API version)
710  */
711 void imap_starttls(int num_parms, ConstStr *Params)
712 {
713         char ok_response[SIZ];
714         char nosup_response[SIZ];
715         char error_response[SIZ];
716
717         snprintf(ok_response, SIZ,      "%s OK begin TLS negotiation now\r\n",  Params[0].Key);
718         snprintf(nosup_response, SIZ,   "%s NO TLS not supported here\r\n",     Params[0].Key);
719         snprintf(error_response, SIZ,   "%s BAD Internal error\r\n",            Params[0].Key);
720         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
721 }
722
723
724 /*
725  * implements the SELECT command
726  */
727 void imap_select(int num_parms, ConstStr *Params)
728 {
729         char towhere[ROOMNAMELEN];
730         char augmented_roomname[ROOMNAMELEN];
731         int c = 0;
732         int ok = 0;
733         int ra = 0;
734         struct ctdlroom QRscratch;
735         int msgs, new;
736         int floornum;
737         int roomflags;
738         int i;
739
740         /* Convert the supplied folder name to a roomname */
741         i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
742         if (i < 0) {
743                 cprintf("%s NO Invalid mailbox name.\r\n", Params[0].Key);
744                 IMAP->selected = 0;
745                 return;
746         }
747         floornum = (i & 0x00ff);
748         roomflags = (i & 0xff00);
749
750         /* First try a regular match */
751         c = CtdlGetRoom(&QRscratch, towhere);
752
753         /* Then try a mailbox name match */
754         if (c != 0) {
755                 CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
756                 c = CtdlGetRoom(&QRscratch, augmented_roomname);
757                 if (c == 0) {
758                         safestrncpy(towhere, augmented_roomname, sizeof(towhere));
759                 }
760         }
761
762         /* If the room exists, check security/access */
763         if (c == 0) {
764                 /* See if there is an existing user/room relationship */
765                 CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
766
767                 /* normal clients have to pass through security */
768                 if (ra & UA_KNOWN) {
769                         ok = 1;
770                 }
771         }
772
773         /* Fail here if no such room */
774         if (!ok) {
775                 cprintf("%s NO ... no such room, or access denied\r\n", Params[0].Key);
776                 return;
777         }
778
779         /* If we already had some other folder selected, auto-expunge it */
780         imap_do_expunge();
781
782         /*
783          * CtdlUserGoto() formally takes us to the desired room, happily returning
784          * the number of messages and number of new messages.
785          */
786         memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
787         CtdlUserGoto(NULL, 0, 0, &msgs, &new);
788         IMAP->selected = 1;
789
790         if (!strcasecmp(Params[1].Key, "EXAMINE")) {
791                 IMAP->readonly = 1;
792         } else {
793                 IMAP->readonly = 0;
794         }
795
796         imap_load_msgids();
797         IMAP->last_mtime = CC->room.QRmtime;
798
799         cprintf("* %d EXISTS\r\n", msgs);
800         cprintf("* %d RECENT\r\n", new);
801
802         cprintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
803         cprintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CitControl.MMhighest + 1);
804
805         /* Technically, \Deleted is a valid flag, but not a permanent flag,
806          * because we don't maintain its state across sessions.  Citadel
807          * automatically expunges mailboxes when they are de-selected.
808          * 
809          * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
810          * some clients (particularly Thunderbird) to misbehave -- they simply
811          * elect not to transmit the flag at all.  So we have to advertise
812          * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
813          */
814         cprintf("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
815         cprintf("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
816
817         cprintf("%s OK [%s] %s completed\r\n",
818                 Params[0].Key,
819                 (IMAP->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
820         );
821 }
822
823
824 /*
825  * Does the real work for expunge.
826  */
827 int imap_do_expunge(void)
828 {
829         int i;
830         int num_expunged = 0;
831         long *delmsgs = NULL;
832         int num_delmsgs = 0;
833
834         CtdlLogPrintf(CTDL_DEBUG, "imap_do_expunge() called\n");
835         if (IMAP->selected == 0) {
836                 return (0);
837         }
838
839         if (IMAP->num_msgs > 0) {
840                 delmsgs = malloc(IMAP->num_msgs * sizeof(long));
841                 for (i = 0; i < IMAP->num_msgs; ++i) {
842                         if (IMAP->flags[i] & IMAP_DELETED) {
843                                 delmsgs[num_delmsgs++] = IMAP->msgids[i];
844                         }
845                 }
846                 if (num_delmsgs > 0) {
847                         CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
848                 }
849                 num_expunged += num_delmsgs;
850                 free(delmsgs);
851         }
852
853         if (num_expunged > 0) {
854                 imap_rescan_msgids();
855         }
856
857         CtdlLogPrintf(CTDL_DEBUG, "Expunged %d messages from <%s>\n", num_expunged, CC->room.QRname);
858         return (num_expunged);
859 }
860
861
862 /*
863  * implements the EXPUNGE command syntax
864  */
865 void imap_expunge(int num_parms, ConstStr *Params)
866 {
867         int num_expunged = 0;
868
869         num_expunged = imap_do_expunge();
870         cprintf("%s OK expunged %d messages.\r\n", Params[0].Key, num_expunged);
871 }
872
873
874 /*
875  * implements the CLOSE command
876  */
877 void imap_close(int num_parms, ConstStr *Params)
878 {
879
880         /* Yes, we always expunge on close. */
881         if (IMAP->selected) {
882                 imap_do_expunge();
883         }
884
885         IMAP->selected = 0;
886         IMAP->readonly = 0;
887         imap_free_msgids();
888         cprintf("%s OK CLOSE completed\r\n", Params[0].Key);
889 }
890
891
892 /*
893  * Implements the NAMESPACE command.
894  */
895 void imap_namespace(int num_parms, ConstStr *Params)
896 {
897         int i;
898         struct floor *fl;
899         int floors = 0;
900         char Namespace[SIZ];
901
902         cprintf("* NAMESPACE ");
903
904         /* All personal folders are subordinate to INBOX. */
905         cprintf("((\"INBOX/\" \"/\")) ");
906
907         /* Other users' folders ... coming soon! FIXME */
908         cprintf("NIL ");
909
910         /* Show all floors as shared namespaces.  Neato! */
911         cprintf("(");
912         for (i = 0; i < MAXFLOORS; ++i) {
913                 fl = CtdlGetCachedFloor(i);
914                 if (fl->f_flags & F_INUSE) {
915                         if (floors > 0) cprintf(" ");
916                         cprintf("(");
917                         snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
918                         plain_imap_strout(Namespace);
919                         cprintf(" \"/\")");
920                         ++floors;
921                 }
922         }
923         cprintf(")");
924
925         /* Wind it up with a newline and a completion message. */
926         cprintf("\r\n");
927         cprintf("%s OK NAMESPACE completed\r\n", Params[0].Key);
928 }
929
930
931 /*
932  * Implements the CREATE command
933  *
934  */
935 void imap_create(int num_parms, ConstStr *Params)
936 {
937         int ret;
938         char roomname[ROOMNAMELEN];
939         int floornum;
940         int flags;
941         int newroomtype = 0;
942         int newroomview = 0;
943         char *notification_message = NULL;
944
945         if (num_parms < 3) {
946                 cprintf("%s NO A foder name must be specified\r\n", Params[0].Key);
947                 return;
948         }
949
950         if (strchr(Params[2].Key, '\\') != NULL) {
951                 cprintf("%s NO Invalid character in folder name\r\n", Params[0].Key);
952                 CtdlLogPrintf(CTDL_DEBUG, "invalid character in folder name\n");
953                 return;
954         }
955
956         ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
957         if (ret < 0) {
958                 cprintf("%s NO Invalid mailbox name or location\r\n",
959                         Params[0].Key);
960                 CtdlLogPrintf(CTDL_DEBUG, "invalid mailbox name or location\n");
961                 return;
962         }
963         floornum = (ret & 0x00ff);      /* lower 8 bits = floor number */
964         flags = (ret & 0xff00); /* upper 8 bits = flags        */
965
966         if (flags & IR_MAILBOX) {
967                 if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
968                         cprintf("%s NO Personal folders must be created under INBOX\r\n", Params[0].Key);
969                         CtdlLogPrintf(CTDL_DEBUG, "not subordinate to inbox\n");
970                         return;
971                 }
972         }
973
974         if (flags & IR_MAILBOX) {
975                 newroomtype = 4;                /* private mailbox */
976                 newroomview = VIEW_MAILBOX;
977         } else {
978                 newroomtype = 0;                /* public folder */
979                 newroomview = VIEW_BBS;
980         }
981
982         CtdlLogPrintf(CTDL_INFO, "Create new room <%s> on floor <%d> with type <%d>\n",
983                 roomname, floornum, newroomtype);
984
985         ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
986         if (ret == 0) {
987                 /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY!  BYNARI CONNECTOR DEPENDS ON IT! ***/
988                 cprintf("%s NO Mailbox already exists, or create failed\r\n", Params[0].Key);
989         } else {
990                 cprintf("%s OK CREATE completed\r\n", Params[0].Key);
991                 /* post a message in Aide> describing the new room */
992                 notification_message = malloc(1024);
993                 snprintf(notification_message, 1024,
994                         "A new room called \"%s\" has been created by %s%s%s%s\n",
995                         roomname,
996                         CC->user.fullname,
997                         ((ret & QR_MAILBOX) ? " [personal]" : ""),
998                         ((ret & QR_PRIVATE) ? " [private]" : ""),
999                         ((ret & QR_GUESSNAME) ? " [hidden]" : "")
1000                 );
1001                 CtdlAideMessage(notification_message, "Room Creation Message");
1002                 free(notification_message);
1003         }
1004         CtdlLogPrintf(CTDL_DEBUG, "imap_create() completed\n");
1005 }
1006
1007
1008 /*
1009  * Locate a room by its IMAP folder name, and check access to it.
1010  * If zapped_ok is nonzero, we can also look for the room in the zapped list.
1011  */
1012 int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
1013 {
1014         int ret;
1015         char augmented_roomname[ROOMNAMELEN];
1016         char roomname[ROOMNAMELEN];
1017         int c;
1018         struct ctdlroom QRscratch;
1019         int ra;
1020         int ok = 0;
1021
1022         ret = imap_roomname(roomname, sizeof roomname, foldername);
1023         if (ret < 0) {
1024                 return (1);
1025         }
1026
1027         /* First try a regular match */
1028         c = CtdlGetRoom(&QRscratch, roomname);
1029
1030         /* Then try a mailbox name match */
1031         if (c != 0) {
1032                 CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
1033                             &CC->user, roomname);
1034                 c = CtdlGetRoom(&QRscratch, augmented_roomname);
1035                 if (c == 0)
1036                         safestrncpy(roomname, augmented_roomname, sizeof(roomname));
1037         }
1038
1039         /* If the room exists, check security/access */
1040         if (c == 0) {
1041                 /* See if there is an existing user/room relationship */
1042                 CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
1043
1044                 /* normal clients have to pass through security */
1045                 if (ra & UA_KNOWN) {
1046                         ok = 1;
1047                 }
1048                 if ((zapped_ok) && (ra & UA_ZAPPED)) {
1049                         ok = 1;
1050                 }
1051         }
1052
1053         /* Fail here if no such room */
1054         if (!ok) {
1055                 strcpy(returned_roomname, "");
1056                 return (2);
1057         } else {
1058                 safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
1059                 return (0);
1060         }
1061 }
1062
1063
1064 /*
1065  * Implements the STATUS command (sort of)
1066  *
1067  */
1068 void imap_status(int num_parms, ConstStr *Params)
1069 {
1070         int ret;
1071         char roomname[ROOMNAMELEN];
1072         char imaproomname[SIZ];
1073         char savedroom[ROOMNAMELEN];
1074         int msgs, new;
1075
1076         ret = imap_grabroom(roomname, Params[2].Key, 1);
1077         if (ret != 0) {
1078                 cprintf
1079                     ("%s NO Invalid mailbox name or location, or access denied\r\n",
1080                      Params[0].Key);
1081                 return;
1082         }
1083
1084         /*
1085          * CtdlUserGoto() formally takes us to the desired room, happily returning
1086          * the number of messages and number of new messages.  (If another
1087          * folder is selected, save its name so we can return there!!!!!)
1088          */
1089         if (IMAP->selected) {
1090                 strcpy(savedroom, CC->room.QRname);
1091         }
1092         CtdlUserGoto(roomname, 0, 0, &msgs, &new);
1093
1094         /*
1095          * Tell the client what it wants to know.  In fact, tell it *more* than
1096          * it wants to know.  We happily IGnore the supplied status data item
1097          * names and simply spew all possible data items.  It's far easier to
1098          * code and probably saves us some processing time too.
1099          */
1100         imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
1101         cprintf("* STATUS ");
1102         plain_imap_strout(imaproomname);
1103         cprintf(" (MESSAGES %d ", msgs);
1104         cprintf("RECENT %d ", new);     /* Initially, new==recent */
1105         cprintf("UIDNEXT %ld ", CitControl.MMhighest + 1);
1106         cprintf("UNSEEN %d)\r\n", new);
1107
1108         /*
1109          * If another folder is selected, go back to that room so we can resume
1110          * our happy day without violent explosions.
1111          */
1112         if (IMAP->selected) {
1113                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
1114         }
1115
1116         /*
1117          * Oooh, look, we're done!
1118          */
1119         cprintf("%s OK STATUS completed\r\n", Params[0].Key);
1120 }
1121
1122
1123 /*
1124  * Implements the SUBSCRIBE command
1125  *
1126  */
1127 void imap_subscribe(int num_parms, ConstStr *Params)
1128 {
1129         int ret;
1130         char roomname[ROOMNAMELEN];
1131         char savedroom[ROOMNAMELEN];
1132         int msgs, new;
1133
1134         ret = imap_grabroom(roomname, Params[2].Key, 1);
1135         if (ret != 0) {
1136                 cprintf(
1137                         "%s NO Error %d: invalid mailbox name or location, or access denied\r\n",
1138                         Params[0].Key,
1139                         ret
1140                 );
1141                 return;
1142         }
1143
1144         /*
1145          * CtdlUserGoto() formally takes us to the desired room, which has the side
1146          * effect of marking the room as not-zapped ... exactly the effect
1147          * we're looking for.
1148          */
1149         if (IMAP->selected) {
1150                 strcpy(savedroom, CC->room.QRname);
1151         }
1152         CtdlUserGoto(roomname, 0, 0, &msgs, &new);
1153
1154         /*
1155          * If another folder is selected, go back to that room so we can resume
1156          * our happy day without violent explosions.
1157          */
1158         if (IMAP->selected) {
1159                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
1160         }
1161
1162         cprintf("%s OK SUBSCRIBE completed\r\n", Params[0].Key);
1163 }
1164
1165
1166 /*
1167  * Implements the UNSUBSCRIBE command
1168  *
1169  */
1170 void imap_unsubscribe(int num_parms, ConstStr *Params)
1171 {
1172         int ret;
1173         char roomname[ROOMNAMELEN];
1174         char savedroom[ROOMNAMELEN];
1175         int msgs, new;
1176
1177         ret = imap_grabroom(roomname, Params[2].Key, 1);
1178         if (ret != 0) {
1179                 cprintf
1180                     ("%s NO Invalid mailbox name or location, or access denied\r\n",
1181                      Params[0].Key);
1182                 return;
1183         }
1184
1185         /*
1186          * CtdlUserGoto() formally takes us to the desired room.
1187          */
1188         if (IMAP->selected) {
1189                 strcpy(savedroom, CC->room.QRname);
1190         }
1191         CtdlUserGoto(roomname, 0, 0, &msgs, &new);
1192
1193         /* 
1194          * Now make the API call to zap the room
1195          */
1196         if (CtdlForgetThisRoom() == 0) {
1197                 cprintf("%s OK UNSUBSCRIBE completed\r\n", Params[0].Key);
1198         } else {
1199                 cprintf
1200                     ("%s NO You may not unsubscribe from this folder.\r\n",
1201                      Params[0].Key);
1202         }
1203
1204         /*
1205          * If another folder is selected, go back to that room so we can resume
1206          * our happy day without violent explosions.
1207          */
1208         if (IMAP->selected) {
1209                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
1210         }
1211 }
1212
1213
1214 /*
1215  * Implements the DELETE command
1216  *
1217  */
1218 void imap_delete(int num_parms, ConstStr *Params)
1219 {
1220         int ret;
1221         char roomname[ROOMNAMELEN];
1222         char savedroom[ROOMNAMELEN];
1223         int msgs, new;
1224
1225         ret = imap_grabroom(roomname, Params[2].Key, 1);
1226         if (ret != 0) {
1227                 cprintf("%s NO Invalid mailbox name, or access denied\r\n",
1228                         Params[0].Key);
1229                 return;
1230         }
1231
1232         /*
1233          * CtdlUserGoto() formally takes us to the desired room, happily returning
1234          * the number of messages and number of new messages.  (If another
1235          * folder is selected, save its name so we can return there!!!!!)
1236          */
1237         if (IMAP->selected) {
1238                 strcpy(savedroom, CC->room.QRname);
1239         }
1240         CtdlUserGoto(roomname, 0, 0, &msgs, &new);
1241
1242         /*
1243          * Now delete the room.
1244          */
1245         if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
1246                 CtdlScheduleRoomForDeletion(&CC->room);
1247                 cprintf("%s OK DELETE completed\r\n", Params[0].Key);
1248         } else {
1249                 cprintf("%s NO Can't delete this folder.\r\n", Params[0].Key);
1250         }
1251
1252         /*
1253          * If another folder is selected, go back to that room so we can resume
1254          * our happy day without violent explosions.
1255          */
1256         if (IMAP->selected) {
1257                 CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
1258         }
1259 }
1260
1261
1262 /*
1263  * Back end function for imap_rename()
1264  */
1265 void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
1266 {
1267         char foldername[SIZ];
1268         char newfoldername[SIZ];
1269         char newroomname[ROOMNAMELEN];
1270         int newfloor = 0;
1271         struct irl *irlp = NULL;        /* scratch pointer */
1272         struct irlparms *irlparms;
1273
1274         irlparms = (struct irlparms *) data;
1275         imap_mailboxname(foldername, sizeof foldername, qrbuf);
1276
1277         /* Rename subfolders */
1278         if ((!strncasecmp(foldername, irlparms->oldname,
1279                           strlen(irlparms->oldname))
1280              && (foldername[strlen(irlparms->oldname)] == '/'))) {
1281
1282                 sprintf(newfoldername, "%s/%s",
1283                         irlparms->newname,
1284                         &foldername[strlen(irlparms->oldname) + 1]
1285                     );
1286
1287                 newfloor = imap_roomname(newroomname,
1288                                          sizeof newroomname,
1289                                          newfoldername) & 0xFF;
1290
1291                 irlp = (struct irl *) malloc(sizeof(struct irl));
1292                 strcpy(irlp->irl_newroom, newroomname);
1293                 strcpy(irlp->irl_oldroom, qrbuf->QRname);
1294                 irlp->irl_newfloor = newfloor;
1295                 irlp->next = *(irlparms->irl);
1296                 *(irlparms->irl) = irlp;
1297         }
1298 }
1299
1300
1301 /*
1302  * Implements the RENAME command
1303  *
1304  */
1305 void imap_rename(int num_parms, ConstStr *Params)
1306 {
1307         char old_room[ROOMNAMELEN];
1308         char new_room[ROOMNAMELEN];
1309         int oldr, newr;
1310         int new_floor;
1311         int r;
1312         struct irl *irl = NULL; /* the list */
1313         struct irl *irlp = NULL;        /* scratch pointer */
1314         struct irlparms irlparms;
1315         char aidemsg[1024];
1316
1317         if (strchr(Params[3].Key, '\\') != NULL) {
1318                 cprintf("%s NO Invalid character in folder name\r\n",
1319                         Params[0].Key);
1320                 return;
1321         }
1322
1323         oldr = imap_roomname(old_room, sizeof old_room, Params[2].Key);
1324         newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
1325         new_floor = (newr & 0xFF);
1326
1327         r = CtdlRenameRoom(old_room, new_room, new_floor);
1328
1329         if (r == crr_room_not_found) {
1330                 cprintf("%s NO Could not locate this folder\r\n",
1331                         Params[0].Key);
1332                 return;
1333         }
1334         if (r == crr_already_exists) {
1335                 cprintf("%s NO '%s' already exists.\r\n", Params[0].Key, Params[2].Key);
1336                 return;
1337         }
1338         if (r == crr_noneditable) {
1339                 cprintf("%s NO This folder is not editable.\r\n", Params[0].Key);
1340                 return;
1341         }
1342         if (r == crr_invalid_floor) {
1343                 cprintf("%s NO Folder root does not exist.\r\n", Params[0].Key);
1344                 return;
1345         }
1346         if (r == crr_access_denied) {
1347                 cprintf("%s NO You do not have permission to edit this folder.\r\n",
1348                         Params[0].Key);
1349                 return;
1350         }
1351         if (r != crr_ok) {
1352                 cprintf("%s NO Rename failed - undefined error %d\r\n",
1353                         Params[0].Key, r);
1354                 return;
1355         }
1356
1357         /* If this is the INBOX, then RFC2060 says we have to just move the
1358          * contents.  In a Citadel environment it's easier to rename the room
1359          * (already did that) and create a new inbox.
1360          */
1361         if (!strcasecmp(Params[2].Key, "INBOX")) {
1362                 CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
1363         }
1364
1365         /* Otherwise, do the subfolders.  Build a list of rooms to rename... */
1366         else {
1367                 irlparms.oldname = Params[2].Key;
1368                 irlparms.newname = Params[3].Key;
1369                 irlparms.irl = &irl;
1370                 CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
1371
1372                 /* ... and now rename them. */
1373                 while (irl != NULL) {
1374                         r = CtdlRenameRoom(irl->irl_oldroom,
1375                                            irl->irl_newroom,
1376                                            irl->irl_newfloor);
1377                         if (r != crr_ok) {
1378                                 /* FIXME handle error returns better */
1379                                 CtdlLogPrintf(CTDL_ERR, "CtdlRenameRoom() error %d\n", r);
1380                         }
1381                         irlp = irl;
1382                         irl = irl->next;
1383                         free(irlp);
1384                 }
1385         }
1386
1387         snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
1388                 Params[2].Key,
1389                 Params[3].Key,
1390                 CC->curr_user
1391         );
1392         CtdlAideMessage(aidemsg, "IMAP folder rename");
1393
1394         cprintf("%s OK RENAME completed\r\n", Params[0].Key);
1395 }
1396
1397
1398 /* 
1399  * Main command loop for IMAP sessions.
1400  */
1401 void imap_command_loop(void)
1402 {
1403         struct timeval tv1, tv2;
1404         suseconds_t total_time = 0;
1405         int untagged_ok = 1;
1406         citimap *Imap;
1407         const char *pchs, *pche;
1408
1409         gettimeofday(&tv1, NULL);
1410         CC->lastcmd = time(NULL);
1411         Imap = IMAP;
1412
1413         flush_output();
1414         if (Imap->Cmd.CmdBuf == NULL)
1415                 Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
1416         else
1417                 FlushStrBuf(Imap->Cmd.CmdBuf);
1418
1419         if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
1420                 CtdlLogPrintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
1421                 CC->kill_me = 1;
1422                 return;
1423         }
1424
1425         if (Imap->authstate == imap_as_expecting_password) {
1426                 CtdlLogPrintf(CTDL_INFO, "IMAP: <password>\n");
1427         }
1428         else if (Imap->authstate == imap_as_expecting_plainauth) {
1429                 CtdlLogPrintf(CTDL_INFO, "IMAP: <plain_auth>\n");
1430         }
1431         else if ((Imap->authstate == imap_as_expecting_multilineusername) || 
1432                  cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
1433                 CtdlLogPrintf(CTDL_INFO, "IMAP: LOGIN...\n");
1434         }
1435         else {
1436                 CtdlLogPrintf(CTDL_INFO, "IMAP: %s\n", ChrPtr(Imap->Cmd.CmdBuf));
1437         }
1438
1439         pchs = ChrPtr(Imap->Cmd.CmdBuf);
1440         pche = pchs + StrLength(Imap->Cmd.CmdBuf);
1441
1442         while ((pche > pchs) &&
1443                ((*pche == '\n') ||
1444                 (*pche == '\r')))
1445         {
1446                 pche --;
1447                 StrBufCutRight(Imap->Cmd.CmdBuf, 1);
1448         }
1449         StrBufTrim(Imap->Cmd.CmdBuf);
1450
1451         /* If we're in the middle of a multi-line command, handle that */
1452         switch (Imap->authstate){
1453         case imap_as_expecting_username:
1454                 imap_auth_login_user(imap_as_expecting_username);
1455                 return;
1456         case imap_as_expecting_multilineusername:
1457                 imap_auth_login_user(imap_as_expecting_multilineusername);
1458                 return;
1459         case imap_as_expecting_plainauth:
1460                 imap_auth_plain();
1461                 return;
1462         case imap_as_expecting_password:
1463                 imap_auth_login_pass(imap_as_expecting_password);
1464                 return;
1465         case imap_as_expecting_multilinepassword:
1466                 imap_auth_login_pass(imap_as_expecting_multilinepassword);
1467                 return;
1468         default:
1469                 break;
1470         }
1471
1472
1473         /* Ok, at this point we're in normal command mode.
1474          * If the command just submitted does not contain a literal, we
1475          * might think about delivering some untagged stuff...
1476          */
1477         if (*(ChrPtr(Imap->Cmd.CmdBuf) + StrLength(Imap->Cmd.CmdBuf) - 1)
1478             == '}') {
1479                 untagged_ok = 0;
1480         }
1481
1482         /* Grab the tag, command, and parameters. */
1483         imap_parameterize(&Imap->Cmd);
1484 #if 0 
1485 /* debug output the parsed vector */
1486         {
1487                 int i;
1488                 CtdlLogPrintf(CTDL_DEBUG, "----- %ld params \n",
1489                               Imap->Cmd.num_parms);
1490
1491         for (i=0; i < Imap->Cmd.num_parms; i++) {
1492                 if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
1493                         CtdlLogPrintf(CTDL_DEBUG, "*********** %ld != %ld : %s\n",
1494                                       Imap->Cmd.Params[i].len, 
1495                                       strlen(Imap->Cmd.Params[i].Key),
1496                                       Imap->Cmd.Params[i].Key);
1497                 else
1498                         CtdlLogPrintf(CTDL_DEBUG, "%ld : %s\n",
1499                                       Imap->Cmd.Params[i].len, 
1500                                       Imap->Cmd.Params[i].Key);
1501         }}
1502
1503 #endif
1504         /* RFC3501 says that we cannot output untagged data during these commands */
1505         if (Imap->Cmd.num_parms >= 2) {
1506                 if (  (!strcasecmp(Imap->Cmd.Params[1].Key, "FETCH"))
1507                    || (!strcasecmp(Imap->Cmd.Params[1].Key, "STORE"))
1508                    || (!strcasecmp(Imap->Cmd.Params[1].Key, "SEARCH"))
1509                 ) {
1510                         untagged_ok = 0;
1511                 }
1512         }
1513         
1514         if (untagged_ok) {
1515
1516                 /* we can put any additional untagged stuff right here in the future */
1517
1518                 /*
1519                  * Before processing the command that was just entered... if we happen
1520                  * to have a folder selected, we'd like to rescan that folder for new
1521                  * messages, and for deletions/changes of existing messages.  This
1522                  * could probably be optimized better with some deep thought...
1523                  */
1524                 if (Imap->selected) {
1525                         imap_rescan_msgids();
1526                 }
1527         }
1528
1529         /* Now for the command set. */
1530
1531         if (Imap->Cmd.num_parms < 2) {
1532                 cprintf("BAD syntax error\r\n");
1533         }
1534
1535         /* The commands below may be executed in any state */
1536
1537         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "NOOP"))
1538                  || (!strcasecmp(Imap->Cmd.Params[1].Key, "CHECK"))) {
1539                 cprintf("%s OK No operation\r\n",
1540                         Imap->Cmd.Params[0].Key);
1541         }
1542
1543         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "ID")) {
1544                 imap_id(Imap->Cmd.num_parms, Imap->Cmd.Params);
1545         }
1546
1547
1548         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "LOGOUT")) {
1549                 if (Imap->selected) {
1550                         imap_do_expunge();      /* yes, we auto-expunge at logout */
1551                 }
1552                 cprintf("* BYE %s logging out\r\n", config.c_fqdn);
1553                 cprintf("%s OK Citadel IMAP session ended.\r\n",
1554                         Imap->Cmd.Params[0].Key);
1555                 CC->kill_me = 1;
1556                 return;
1557         }
1558
1559         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "LOGIN")) {
1560                 imap_login(Imap->Cmd.num_parms, Imap->Cmd.Params);
1561         }
1562
1563         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "AUTHENTICATE")) {
1564                 imap_authenticate(Imap->Cmd.num_parms, Imap->Cmd.Params);
1565         }
1566
1567         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "CAPABILITY")) {
1568                 imap_capability(Imap->Cmd.num_parms, Imap->Cmd.Params);
1569         }
1570 #ifdef HAVE_OPENSSL
1571         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "STARTTLS")) {
1572                 imap_starttls(Imap->Cmd.num_parms, Imap->Cmd.Params);
1573         }
1574 #endif
1575         else if (!CC->logged_in) {
1576                 cprintf("%s BAD Not logged in.\r\n", Imap->Cmd.Params[0].Key);
1577         }
1578
1579         /* The commans below require a logged-in state */
1580
1581         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "SELECT")) {
1582                 imap_select(Imap->Cmd.num_parms, Imap->Cmd.Params);
1583         }
1584
1585         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "EXAMINE")) {
1586                 imap_select(Imap->Cmd.num_parms, Imap->Cmd.Params);
1587         }
1588
1589         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "LSUB")) {
1590                 imap_list(Imap->Cmd.num_parms, Imap->Cmd.Params);
1591         }
1592
1593         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "LIST")) {
1594                 imap_list(Imap->Cmd.num_parms, Imap->Cmd.Params);
1595         }
1596
1597         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "CREATE")) {
1598                 imap_create(Imap->Cmd.num_parms, Imap->Cmd.Params);
1599         }
1600
1601         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "DELETE")) {
1602                 imap_delete(Imap->Cmd.num_parms, Imap->Cmd.Params);
1603         }
1604
1605         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "RENAME")) {
1606                 imap_rename(Imap->Cmd.num_parms, Imap->Cmd.Params);
1607         }
1608
1609         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "STATUS")) {
1610                 imap_status(Imap->Cmd.num_parms, Imap->Cmd.Params);
1611         }
1612
1613         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "SUBSCRIBE")) {
1614                 imap_subscribe(Imap->Cmd.num_parms, Imap->Cmd.Params);
1615         }
1616
1617         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "UNSUBSCRIBE")) {
1618                 imap_unsubscribe(Imap->Cmd.num_parms, Imap->Cmd.Params);
1619         }
1620
1621         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "APPEND")) {
1622                 imap_append(Imap->Cmd.num_parms, Imap->Cmd.Params);
1623         }
1624
1625         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "NAMESPACE")) {
1626                 imap_namespace(Imap->Cmd.num_parms, Imap->Cmd.Params);
1627         }
1628
1629         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "SETACL")) {
1630                 imap_setacl(Imap->Cmd.num_parms, Imap->Cmd.Params);
1631         }
1632
1633         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "DELETEACL")) {
1634                 imap_deleteacl(Imap->Cmd.num_parms, Imap->Cmd.Params);
1635         }
1636
1637         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "GETACL")) {
1638                 imap_getacl(Imap->Cmd.num_parms, Imap->Cmd.Params);
1639         }
1640
1641         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "LISTRIGHTS")) {
1642                 imap_listrights(Imap->Cmd.num_parms, Imap->Cmd.Params);
1643         }
1644
1645         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "MYRIGHTS")) {
1646                 imap_myrights(Imap->Cmd.num_parms, Imap->Cmd.Params);
1647         }
1648
1649         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "GETMETADATA")) {
1650                 imap_getmetadata(Imap->Cmd.num_parms, Imap->Cmd.Params);
1651         }
1652
1653         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "SETMETADATA")) {
1654                 imap_setmetadata(Imap->Cmd.num_parms, Imap->Cmd.Params);
1655         }
1656
1657         else if (Imap->selected == 0) {
1658                 cprintf("%s BAD no folder selected\r\n", Imap->Cmd.Params[0].Key);
1659         }
1660
1661         /* The commands below require the SELECT state on a mailbox */
1662
1663         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "FETCH")) {
1664                 imap_fetch(Imap->Cmd.num_parms, Imap->Cmd.Params);
1665         }
1666
1667         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "UID"))
1668                  && (!strcasecmp(Imap->Cmd.Params[2].Key, "FETCH"))) {
1669                 imap_uidfetch(Imap->Cmd.num_parms, Imap->Cmd.Params);
1670         }
1671
1672         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "SEARCH")) {
1673                 imap_search(Imap->Cmd.num_parms, Imap->Cmd.Params);
1674         }
1675
1676         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "UID"))
1677                  && (!strcasecmp(Imap->Cmd.Params[2].Key, "SEARCH"))) {
1678                 imap_uidsearch(Imap->Cmd.num_parms, Imap->Cmd.Params);
1679         }
1680
1681         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "STORE")) {
1682                 imap_store(Imap->Cmd.num_parms, Imap->Cmd.Params);
1683         }
1684
1685         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "UID"))
1686                  && (!strcasecmp(Imap->Cmd.Params[2].Key, "STORE"))) {
1687                 imap_uidstore(Imap->Cmd.num_parms, Imap->Cmd.Params);
1688         }
1689
1690         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "COPY")) {
1691                 imap_copy(Imap->Cmd.num_parms, Imap->Cmd.Params);
1692         }
1693
1694         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "UID")) && (!strcasecmp(Imap->Cmd.Params[2].Key, "COPY"))) {
1695                 imap_uidcopy(Imap->Cmd.num_parms, Imap->Cmd.Params);
1696         }
1697
1698         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "EXPUNGE")) {
1699                 imap_expunge(Imap->Cmd.num_parms, Imap->Cmd.Params);
1700         }
1701
1702         else if ((!strcasecmp(Imap->Cmd.Params[1].Key, "UID")) && (!strcasecmp(Imap->Cmd.Params[2].Key, "EXPUNGE"))) {
1703                 imap_expunge(Imap->Cmd.num_parms, Imap->Cmd.Params);
1704         }
1705
1706         else if (!strcasecmp(Imap->Cmd.Params[1].Key, "CLOSE")) {
1707                 imap_close(Imap->Cmd.num_parms, Imap->Cmd.Params);
1708         }
1709
1710         /* End of commands.  If we get here, the command is either invalid
1711          * or unimplemented.
1712          */
1713
1714         else {
1715                 cprintf("%s BAD command unrecognized\r\n", Imap->Cmd.Params[0].Key);
1716         }
1717
1718         /* If the client transmitted a message we can free it now */
1719         imap_free_transmitted_message();
1720
1721         gettimeofday(&tv2, NULL);
1722         total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
1723         CtdlLogPrintf(CTDL_DEBUG, "IMAP command completed in %ld.%ld seconds\n",
1724                 (total_time / 1000000),
1725                 (total_time % 1000000)
1726         );
1727 }
1728
1729
1730 const char *CitadelServiceIMAP="IMAP";
1731 const char *CitadelServiceIMAPS="IMAPS";
1732
1733 /*
1734  * This function is called to register the IMAP extension with Citadel.
1735  */
1736 CTDL_MODULE_INIT(imap)
1737 {
1738         if (!threading)
1739         {
1740                 CtdlRegisterServiceHook(config.c_imap_port,
1741                                         NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
1742 #ifdef HAVE_OPENSSL
1743                 CtdlRegisterServiceHook(config.c_imaps_port,
1744                                         NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
1745 #endif
1746                 CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP);
1747         }
1748         
1749         /* return our Subversion id for the Log */
1750         return "imap";
1751 }