]> code.citadel.org Git - citadel.git/blob - citadel/serv_imap.c
* IMAP LIST/LSUB: made it case insensitive. Also minor IMAP code cleanup.
[citadel.git] / citadel / serv_imap.c
1 /*
2  * $Id$ 
3  *
4  * IMAP server for the Citadel/UX system
5  * Copyright (C) 2000-2001 by Art Cancro and others.
6  * This code is released under the terms of the GNU General Public License.
7  *
8  * WARNING: this is an incomplete implementation, still in progress.  Parts of
9  * it work, but it's not really usable yet from a user perspective.
10  *
11  * WARNING: Mark Crispin is an idiot.  IMAP is the most brain-damaged protocol
12  * you will ever have the profound lack of pleasure to encounter.
13  * 
14  */
15
16 #include "sysdep.h"
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <stdio.h>
20 #include <fcntl.h>
21 #include <signal.h>
22 #include <pwd.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/time.h>
26 #include <sys/wait.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <limits.h>
30 #include "citadel.h"
31 #include "server.h"
32 #include <time.h>
33 #include "sysdep_decls.h"
34 #include "citserver.h"
35 #include "support.h"
36 #include "config.h"
37 #include "dynloader.h"
38 #include "room_ops.h"
39 #include "user_ops.h"
40 #include "policy.h"
41 #include "database.h"
42 #include "msgbase.h"
43 #include "tools.h"
44 #include "internet_addressing.h"
45 #include "serv_imap.h"
46 #include "imap_tools.h"
47 #include "imap_fetch.h"
48 #include "imap_search.h"
49 #include "imap_store.h"
50 #include "imap_misc.h"
51
52
53 long SYM_IMAP;
54
55
56 /*
57  * If there is a message ID map in memory, free it
58  */
59 void imap_free_msgids(void) {
60         if (IMAP->msgids != NULL) {
61                 phree(IMAP->msgids);
62                 IMAP->msgids = NULL;
63                 IMAP->num_msgs = 0;
64         }
65         if (IMAP->flags != NULL) {
66                 phree(IMAP->flags);
67                 IMAP->flags = NULL;
68         }
69 }
70
71
72 /*
73  * If there is a transmitted message in memory, free it
74  */
75 void imap_free_transmitted_message(void) {
76         if (IMAP->transmitted_message != NULL) {
77                 phree(IMAP->transmitted_message);
78                 IMAP->transmitted_message = NULL;
79                 IMAP->transmitted_length = 0;
80         }
81 }
82
83
84 /*
85  * Back end for imap_load_msgids()
86  *
87  * FIXME: this should be optimized by figuring out a way to allocate memory
88  * once rather than doing a reallok() for each message.
89  */
90 void imap_add_single_msgid(long msgnum, void *userdata) {
91         
92         IMAP->num_msgs = IMAP->num_msgs + 1;
93         if (IMAP->msgids == NULL) {
94                 IMAP->msgids = mallok(IMAP->num_msgs * sizeof(long));
95         }
96         else {
97                 IMAP->msgids = reallok(IMAP->msgids,
98                         IMAP->num_msgs * sizeof(long));
99         }
100         if (IMAP->flags == NULL) {
101                 IMAP->flags = mallok(IMAP->num_msgs * sizeof(long));
102         }
103         else {
104                 IMAP->flags = reallok(IMAP->flags,
105                         IMAP->num_msgs * sizeof(long));
106         }
107         IMAP->msgids[IMAP->num_msgs - 1] = msgnum;
108         IMAP->flags[IMAP->num_msgs - 1] = 0;
109 }
110
111
112
113 /*
114  * Set up a message ID map for the current room (folder)
115  */
116 void imap_load_msgids(void) {
117          
118         if (IMAP->selected == 0) {
119                 lprintf(5, "imap_load_msgids() can't run; no room selected\n");
120                 return;
121         }
122
123         imap_free_msgids();     /* If there was already a map, free it */
124
125         CtdlForEachMessage(MSGS_ALL, 0L, (-63), NULL, NULL,
126                 imap_add_single_msgid, NULL);
127
128         lprintf(9, "imap_load_msgids() mapped %d messages\n", IMAP->num_msgs);
129 }
130
131
132 /*
133  * Re-scan the selected room (folder) and see if it's been changed at all
134  */
135 void imap_rescan_msgids(void) {
136
137         int original_num_msgs = 0;
138         long original_highest = 0L;
139         int i;
140         int count;
141
142         if (IMAP->selected == 0) {
143                 lprintf(5, "imap_load_msgids() can't run; no room selected\n");
144                 return;
145         }
146
147
148         /*
149          * Check to see if any of the messages we know about have been expunged
150          */
151         if (IMAP->num_msgs > 0)
152          for (i=0; i<IMAP->num_msgs; ++i) {
153
154                 count = CtdlForEachMessage(MSGS_EQ, IMAP->msgids[i],
155                         (-63), NULL, NULL, NULL, NULL);
156
157                 if (count == 0) {
158                         cprintf("* %d EXPUNGE\r\n", i+1);
159
160                         /* Here's some nice stupid nonsense.  When a message
161                          * is expunged, we have to slide all the existing
162                          * messages up in the message array.
163                          */
164                         --IMAP->num_msgs;
165                         memcpy(&IMAP->msgids[i], &IMAP->msgids[i+1],
166                                 (sizeof(long)*(IMAP->num_msgs-i)) );
167                         memcpy(&IMAP->flags[i], &IMAP->flags[i+1],
168                                 (sizeof(long)*(IMAP->num_msgs-i)) );
169
170                         --i;
171                 }
172
173         }
174
175         /*
176          * Remember how many messages were here before we re-scanned.
177          */
178         original_num_msgs = IMAP->num_msgs;
179         if (IMAP->num_msgs > 0) {
180                 original_highest = IMAP->msgids[IMAP->num_msgs - 1];
181         }
182         else {
183                 original_highest = 0L;
184         }
185
186         /*
187          * Now peruse the room for *new* messages only.
188          */
189         CtdlForEachMessage(MSGS_GT, original_highest, (-63), NULL, NULL,
190                 imap_add_single_msgid, NULL);
191
192         /*
193          * If new messages have arrived, tell the client about them.
194          */
195         if (IMAP->num_msgs > original_num_msgs) {
196                 cprintf("* %d EXISTS\r\n", IMAP->num_msgs);
197         }
198
199 }
200
201
202
203
204
205
206
207 /*
208  * This cleanup function blows away the temporary memory and files used by
209  * the IMAP server.
210  */
211 void imap_cleanup_function(void) {
212
213         /* Don't do this stuff if this is not a IMAP session! */
214         if (CC->h_command_function != imap_command_loop) return;
215
216         lprintf(9, "Performing IMAP cleanup hook\n");
217         imap_free_msgids();
218         imap_free_transmitted_message();
219         lprintf(9, "Finished IMAP cleanup hook\n");
220 }
221
222
223
224 /*
225  * Here's where our IMAP session begins its happy day.
226  */
227 void imap_greeting(void) {
228
229         strcpy(CC->cs_clientname, "IMAP session");
230         CtdlAllocUserData(SYM_IMAP, sizeof(struct citimap));
231         IMAP->authstate = imap_as_normal;
232
233         cprintf("* OK %s Citadel/UX IMAP4rev1 server ready\r\n",
234                 config.c_fqdn);
235 }
236
237
238 /*
239  * implements the LOGIN command (ordinary username/password login)
240  */
241 void imap_login(int num_parms, char *parms[]) {
242         if (CtdlLoginExistingUser(parms[2]) == login_ok) {
243                 if (CtdlTryPassword(parms[3]) == pass_ok) {
244                         cprintf("%s OK login successful\r\n", parms[0]);
245                         return;
246                 }
247         }
248
249         cprintf("%s BAD Login incorrect\r\n", parms[0]);
250 }
251
252
253 /*
254  * Implements the AUTHENTICATE command
255  */
256 void imap_authenticate(int num_parms, char *parms[]) {
257         char buf[SIZ];
258
259         if (num_parms != 3) {
260                 cprintf("%s BAD incorrect number of parameters\r\n", parms[0]);
261                 return;
262         }
263
264         if (!strcasecmp(parms[2], "LOGIN")) {
265                 encode_base64(buf, "Username:");
266                 cprintf("+ %s\r\n", buf);
267                 IMAP->authstate = imap_as_expecting_username;
268                 strcpy(IMAP->authseq, parms[0]);
269                 return;
270         }
271
272         else {
273                 cprintf("%s NO AUTHENTICATE %s failed\r\n",
274                         parms[0], parms[1]);
275         }
276 }
277
278 void imap_auth_login_user(char *cmd) {
279         char buf[SIZ];
280
281         decode_base64(buf, cmd);
282         CtdlLoginExistingUser(buf);
283         encode_base64(buf, "Password:");
284         cprintf("+ %s\r\n", buf);
285         IMAP->authstate = imap_as_expecting_password;
286         return;
287 }
288
289 void imap_auth_login_pass(char *cmd) {
290         char buf[SIZ];
291
292         decode_base64(buf, cmd);
293         if (CtdlTryPassword(buf) == pass_ok) {
294                 cprintf("%s OK authentication succeeded\r\n", IMAP->authseq);
295         }
296         else {
297                 cprintf("%s NO authentication failed\r\n", IMAP->authseq);
298         }
299         IMAP->authstate = imap_as_normal;
300         return;
301 }
302
303
304
305 /*
306  * implements the CAPABILITY command
307  */
308 void imap_capability(int num_parms, char *parms[]) {
309         cprintf("* CAPABILITY IMAP4 IMAP4REV1 AUTH=LOGIN\r\n");
310         cprintf("%s OK CAPABILITY completed\r\n", parms[0]);
311 }
312
313
314
315
316
317 /*
318  * implements the SELECT command
319  */
320 void imap_select(int num_parms, char *parms[]) {
321         char towhere[SIZ];
322         char augmented_roomname[ROOMNAMELEN];
323         int c = 0;
324         int ok = 0;
325         int ra = 0;
326         struct quickroom QRscratch;
327         int msgs, new;
328         int floornum;
329         int roomflags;
330         int i;
331
332         /* Convert the supplied folder name to a roomname */
333         i = imap_roomname(towhere, sizeof towhere, parms[2]);
334         if (i < 0) {
335                 cprintf("%s NO Invalid mailbox name.\r\n", parms[0]);
336                 IMAP->selected = 0;
337                 return;
338         }
339         floornum = (i & 0x00ff);
340         roomflags = (i & 0xff00);
341
342         /* First try a regular match */
343         c = getroom(&QRscratch, towhere);
344
345         /* Then try a mailbox name match */
346         if (c != 0) {
347                 MailboxName(augmented_roomname, &CC->usersupp, towhere);
348                 c = getroom(&QRscratch, augmented_roomname);
349                 if (c == 0)
350                         strcpy(towhere, augmented_roomname);
351         }
352
353         /* If the room exists, check security/access */
354         if (c == 0) {
355                 /* See if there is an existing user/room relationship */
356                 ra = CtdlRoomAccess(&QRscratch, &CC->usersupp);
357
358                 /* normal clients have to pass through security */
359                 if (ra & UA_KNOWN) {
360                         ok = 1;
361                 }
362         }
363
364         /* Fail here if no such room */
365         if (!ok) {
366                 cprintf("%s NO ... no such room, or access denied\r\n",
367                         parms[0]);
368                 IMAP->selected = 0;
369                 return;
370         }
371
372         /*
373          * usergoto() formally takes us to the desired room, happily returning
374          * the number of messages and number of new messages.
375          */
376         usergoto(QRscratch.QRname, 0, &msgs, &new);
377         IMAP->selected = 1;
378
379         if (!strcasecmp(parms[1], "EXAMINE")) {
380                 IMAP->readonly = 1;
381         }
382         else {
383                 IMAP->readonly = 0;
384         }
385
386         imap_load_msgids();
387
388         /* FIXME ... much more info needs to be supplied here */
389         cprintf("* %d EXISTS\r\n", msgs);
390         cprintf("* %d RECENT\r\n", new);
391         cprintf("* FLAGS (\\Deleted)\r\n");
392         cprintf("* OK [PERMANENTFLAGS (\\Deleted)] permanent flags\r\n");
393         cprintf("* OK [UIDVALIDITY 0] UIDs valid\r\n");
394         cprintf("%s OK [%s] %s completed\r\n",
395                 parms[0],
396                 (IMAP->readonly ? "READ-ONLY" : "READ-WRITE"),
397                 parms[1]);
398 }
399
400
401
402 /*
403  * does the real work for expunge
404  */
405 int imap_do_expunge(void) {
406         int i;
407         int num_expunged = 0;
408
409         if (IMAP->num_msgs > 0) for (i=0; i<IMAP->num_msgs; ++i) {
410                 if (IMAP->flags[i] & IMAP_DELETED) {
411                         CtdlDeleteMessages(CC->quickroom.QRname,
412                                         IMAP->msgids[i], "");
413                         ++num_expunged;
414                 }
415         }
416
417         if (num_expunged > 0) {
418                 imap_rescan_msgids();
419         }
420
421         return(num_expunged);
422 }
423
424
425 /*
426  * implements the EXPUNGE command syntax
427  */
428 void imap_expunge(int num_parms, char *parms[]) {
429         int num_expunged = 0;
430         imap_do_expunge();
431         cprintf("%s OK expunged %d messages.\r\n", parms[0], num_expunged);
432 }
433
434
435 /*
436  * implements the CLOSE command
437  */
438 void imap_close(int num_parms, char *parms[]) {
439
440         /* Yes, we always expunge on close. */
441         imap_do_expunge();
442
443         IMAP->selected = 0;
444         IMAP->readonly = 0;
445         imap_free_msgids();
446         cprintf("%s OK CLOSE completed\r\n", parms[0]);
447 }
448
449
450
451
452 /*
453  * Used by LIST and LSUB to show the floors in the listing
454  */
455 void imap_list_floors(char *cmd, char *pattern) {
456         int i;
457         struct floor *fl;
458
459         for (i=0; i<MAXFLOORS; ++i) {
460                 fl = cgetfloor(i);
461                 if (fl->f_flags & F_INUSE) {
462                         if (imap_mailbox_matches_pattern(pattern, fl->f_name)) {
463                                 cprintf("* %s (\\NoSelect) \"|\" ", cmd);
464                                 imap_strout(fl->f_name);
465                                 cprintf("\r\n");
466                         }
467                 }
468         }
469 }
470
471
472
473 /*
474  * Back end for imap_lsub()
475  *
476  * IMAP "subscribed folder" is equivocated to Citadel "known rooms."  This
477  * may or may not be the desired behavior in the future.
478  */
479 void imap_lsub_listroom(struct quickroom *qrbuf, void *data) {
480         char buf[SIZ];
481         int ra;
482         char *pattern;
483
484         pattern = (char *)data;
485
486         /* Only list rooms to which the user has access!! */
487         ra = CtdlRoomAccess(qrbuf, &CC->usersupp);
488         if (ra & UA_KNOWN) {
489                 imap_mailboxname(buf, sizeof buf, qrbuf);
490                 if (imap_mailbox_matches_pattern(pattern, buf)) {
491                         cprintf("* LSUB (\\NoInferiors) \"|\" ");
492                         imap_strout(buf);
493                         cprintf("\r\n");
494                 }
495         }
496 }
497
498
499 /*
500  * Implements the LSUB command
501  */
502 void imap_lsub(int num_parms, char *parms[]) {
503         char pattern[SIZ];
504         if (num_parms < 4) {
505                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
506                 return;
507         }
508         sprintf(pattern, "%s%s", parms[2], parms[3]);
509
510         if (strlen(parms[3])==0) {
511                 cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n");
512         }
513
514         else {
515                 imap_list_floors("LSUB", pattern);
516                 ForEachRoom(imap_lsub_listroom, pattern);
517         }
518
519         cprintf("%s OK LSUB completed\r\n", parms[0]);
520 }
521
522
523
524 /*
525  * Back end for imap_list()
526  */
527 void imap_list_listroom(struct quickroom *qrbuf, void *data) {
528         char buf[SIZ];
529         int ra;
530         char *pattern;
531
532         pattern = (char *)data;
533
534         /* Only list rooms to which the user has access!! */
535         ra = CtdlRoomAccess(qrbuf, &CC->usersupp);
536         if ( (ra & UA_KNOWN) 
537           || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
538                 imap_mailboxname(buf, sizeof buf, qrbuf);
539                 if (imap_mailbox_matches_pattern(pattern, buf)) {
540                         cprintf("* LIST (\\NoInferiors) \"|\" ");
541                         imap_strout(buf);
542                         cprintf("\r\n");
543                 }
544         }
545 }
546
547
548 /*
549  * Implements the LIST command
550  */
551 void imap_list(int num_parms, char *parms[]) {
552         char pattern[SIZ];
553         if (num_parms < 4) {
554                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
555                 return;
556         }
557         sprintf(pattern, "%s%s", parms[2], parms[3]);
558
559         if (strlen(parms[3])==0) {
560                 cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n");
561         }
562
563         else {
564                 imap_list_floors("LIST", pattern);
565                 ForEachRoom(imap_list_listroom, pattern);
566         }
567
568         cprintf("%s OK LIST completed\r\n", parms[0]);
569 }
570
571
572
573 /*
574  * Implements the CREATE command
575  *
576  */
577 void imap_create(int num_parms, char *parms[]) {
578         int ret;
579         char roomname[ROOMNAMELEN];
580         int floornum;
581         int flags;
582         int newroomtype;
583
584         ret = imap_roomname(roomname, sizeof roomname, parms[2]);
585         if (ret < 0) {
586                 cprintf("%s NO Invalid mailbox name or location\r\n",
587                         parms[0]);
588                 return;
589         }
590         floornum = ( ret & 0x00ff );    /* lower 8 bits = floor number */
591         flags =    ( ret & 0xff00 );    /* upper 8 bits = flags        */
592
593         if (flags & IR_MAILBOX) {
594                 newroomtype = 4;        /* private mailbox */
595         }
596         else {
597                 newroomtype = 0;        /* public folder */
598         }
599
600         lprintf(7, "Create new room <%s> on floor <%d> with type <%d>\n",
601                 roomname, floornum, newroomtype);
602
603         ret = create_room(roomname, newroomtype, "", floornum, 1);
604         if (ret == 0) {
605                 cprintf("%s NO Mailbox already exists, or create failed\r\n",
606                         parms[0]);
607         }
608         else {
609                 cprintf("%s OK CREATE completed\r\n", parms[0]);
610         }
611 }
612
613
614 /*
615  * Locate a room by its IMAP folder name, and check access to it
616  */
617 int imap_grabroom(char *returned_roomname, char *foldername) {
618         int ret;
619         char augmented_roomname[ROOMNAMELEN];
620         char roomname[ROOMNAMELEN];
621         int c;
622         struct quickroom QRscratch;
623         int ra;
624         int ok = 0;
625
626         ret = imap_roomname(roomname, sizeof roomname, foldername);
627         if (ret < 0) {
628                 return(1);
629         }
630
631         /* First try a regular match */
632         c = getroom(&QRscratch, roomname);
633
634         /* Then try a mailbox name match */
635         if (c != 0) {
636                 MailboxName(augmented_roomname, &CC->usersupp, roomname);
637                 c = getroom(&QRscratch, augmented_roomname);
638                 if (c == 0)
639                         strcpy(roomname, augmented_roomname);
640         }
641
642         /* If the room exists, check security/access */
643         if (c == 0) {
644                 /* See if there is an existing user/room relationship */
645                 ra = CtdlRoomAccess(&QRscratch, &CC->usersupp);
646
647                 /* normal clients have to pass through security */
648                 if (ra & UA_KNOWN) {
649                         ok = 1;
650                 }
651         }
652
653         /* Fail here if no such room */
654         if (!ok) {
655                 strcpy(returned_roomname, "");
656                 return(2);
657         }
658         else {
659                 strcpy(returned_roomname, QRscratch.QRname);
660                 return(0);
661         }
662 }
663
664
665 /*
666  * Implements the STATUS command (sort of)
667  *
668  */
669 void imap_status(int num_parms, char *parms[]) {
670         int ret;
671         char roomname[ROOMNAMELEN];
672         char buf[SIZ];
673         char savedroom[ROOMNAMELEN];
674         int msgs, new;
675
676         ret = imap_grabroom(roomname, parms[2]);
677         if (ret != 0) {
678                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
679                         parms[0]);
680                 return;
681         }
682
683         /*
684          * usergoto() formally takes us to the desired room, happily returning
685          * the number of messages and number of new messages.  (If another
686          * folder is selected, save its name so we can return there!!!!!)
687          */
688         if (IMAP->selected) {
689                 strcpy(savedroom, CC->quickroom.QRname);
690         }
691         usergoto(roomname, 0, &msgs, &new);
692
693         /*
694          * Tell the client what it wants to know.  In fact, tell it *more* than
695          * it wants to know.  We happily IGnore the supplied status data item
696          * names and simply spew all possible data items.  It's far easier to
697          * code and probably saves us some processing time too.
698          */
699         imap_mailboxname(buf, sizeof buf, &CC->quickroom);
700         cprintf("* STATUS ");
701         imap_strout(buf);
702         cprintf(" (MESSAGES %d ", msgs);
703         cprintf("RECENT 0 ");   /* FIXME we need to implement this */
704         cprintf("UIDNEXT %ld ", CitControl.MMhighest + 1);
705         cprintf("UNSEEN %d)\r\n", new);
706
707         /*
708          * If another folder is selected, go back to that room so we can resume
709          * our happy day without violent explosions.
710          */
711         if (IMAP->selected) {
712                 usergoto(savedroom, 0, &msgs, &new);
713         }
714
715         /*
716          * Oooh, look, we're done!
717          */
718         cprintf("%s OK STATUS completed\r\n", parms[0]);
719 }
720
721
722
723 /*
724  * Implements the SUBSCRIBE command
725  *
726  */
727 void imap_subscribe(int num_parms, char *parms[]) {
728         int ret;
729         char roomname[ROOMNAMELEN];
730         char savedroom[ROOMNAMELEN];
731         int msgs, new;
732
733         ret = imap_grabroom(roomname, parms[2]);
734         if (ret != 0) {
735                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
736                         parms[0]);
737                 return;
738         }
739
740         /*
741          * usergoto() formally takes us to the desired room, which has the side
742          * effect of marking the room as not-zapped ... exactly the effect
743          * we're looking for.
744          */
745         if (IMAP->selected) {
746                 strcpy(savedroom, CC->quickroom.QRname);
747         }
748         usergoto(roomname, 0, &msgs, &new);
749
750         /*
751          * If another folder is selected, go back to that room so we can resume
752          * our happy day without violent explosions.
753          */
754         if (IMAP->selected) {
755                 usergoto(savedroom, 0, &msgs, &new);
756         }
757
758         cprintf("%s OK SUBSCRIBE completed\r\n", parms[0]);
759 }
760
761
762 /*
763  * Implements the UNSUBSCRIBE command
764  *
765  */
766 void imap_unsubscribe(int num_parms, char *parms[]) {
767         int ret;
768         char roomname[ROOMNAMELEN];
769         char savedroom[ROOMNAMELEN];
770         int msgs, new;
771
772         ret = imap_grabroom(roomname, parms[2]);
773         if (ret != 0) {
774                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
775                         parms[0]);
776                 return;
777         }
778
779         /*
780          * usergoto() formally takes us to the desired room.
781          */
782         if (IMAP->selected) {
783                 strcpy(savedroom, CC->quickroom.QRname);
784         }
785         usergoto(roomname, 0, &msgs, &new);
786
787         /* 
788          * Now make the API call to zap the room
789          */
790         if (CtdlForgetThisRoom() == 0) {
791                 cprintf("%s OK UNSUBSCRIBE completed\r\n", parms[0]);
792         }
793         else {
794                 cprintf("%s NO You may not unsubscribe from this folder.\r\n",
795                         parms[0]);
796         }
797
798         /*
799          * If another folder is selected, go back to that room so we can resume
800          * our happy day without violent explosions.
801          */
802         if (IMAP->selected) {
803                 usergoto(savedroom, 0, &msgs, &new);
804         }
805 }
806
807
808
809 /*
810  * Implements the DELETE command
811  *
812  */
813 void imap_delete(int num_parms, char *parms[]) {
814         int ret;
815         char roomname[ROOMNAMELEN];
816         char savedroom[ROOMNAMELEN];
817         int msgs, new;
818
819         ret = imap_grabroom(roomname, parms[2]);
820         if (ret != 0) {
821                 cprintf("%s NO Invalid mailbox name, or access denied\r\n",
822                         parms[0]);
823                 return;
824         }
825
826         /*
827          * usergoto() formally takes us to the desired room, happily returning
828          * the number of messages and number of new messages.  (If another
829          * folder is selected, save its name so we can return there!!!!!)
830          */
831         if (IMAP->selected) {
832                 strcpy(savedroom, CC->quickroom.QRname);
833         }
834         usergoto(roomname, 0, &msgs, &new);
835
836         /*
837          * Now delete the room.
838          */
839         if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->quickroom)) {
840                 cprintf("%s OK DELETE completed\r\n", parms[0]);
841                 delete_room(&CC->quickroom);
842         }
843         else {
844                 cprintf("%s NO Can't delete this folder.\r\n", parms[0]);
845         }
846
847         /*
848          * If another folder is selected, go back to that room so we can resume
849          * our happy day without violent explosions.
850          */
851         if (IMAP->selected) {
852                 usergoto(savedroom, 0, &msgs, &new);
853         }
854 }
855
856
857
858 /*
859  * Implements the RENAME command
860  *
861  */
862 void imap_rename(int num_parms, char *parms[]) {
863         cprintf("%s NO The RENAME command is not yet implemented (FIXME)\r\n",
864                 parms[0]);
865 }
866
867
868
869 /* 
870  * Main command loop for IMAP sessions.
871  */
872 void imap_command_loop(void) {
873         char cmdbuf[SIZ];
874         char *parms[SIZ];
875         int num_parms;
876
877         time(&CC->lastcmd);
878         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
879         if (client_gets(cmdbuf) < 1) {
880                 lprintf(3, "IMAP socket is broken.  Ending session.\r\n");
881                 CC->kill_me = 1;
882                 return;
883         }
884
885         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
886         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
887
888
889         /* strip off l/t whitespace and CRLF */
890         if (cmdbuf[strlen(cmdbuf)-1]=='\n') cmdbuf[strlen(cmdbuf)-1]=0;
891         if (cmdbuf[strlen(cmdbuf)-1]=='\r') cmdbuf[strlen(cmdbuf)-1]=0;
892         striplt(cmdbuf);
893
894         /* If we're in the middle of a multi-line command, handle that */
895         if (IMAP->authstate == imap_as_expecting_username) {
896                 imap_auth_login_user(cmdbuf);
897                 return;
898         }
899         if (IMAP->authstate == imap_as_expecting_password) {
900                 imap_auth_login_pass(cmdbuf);
901                 return;
902         }
903
904
905         /* Ok, at this point we're in normal command mode.  The first thing
906          * we do is print any incoming pages (yeah! we really do!)
907          */
908         imap_print_express_messages();
909
910         /*
911          * Before processing the command that was just entered... if we happen
912          * to have a folder selected, we'd like to rescan that folder for new
913          * messages, and for deletions/changes of existing messages.  This
914          * could probably be optimized somehow, but IMAP sucks...
915          */
916         if (IMAP->selected) {
917                 imap_rescan_msgids();
918         }
919
920         /* Now for the command set. */
921
922         /* Grab the tag, command, and parameters.  Check syntax. */
923         num_parms = imap_parameterize(parms, cmdbuf);
924         if (num_parms < 2) {
925                 cprintf("BAD syntax error\r\n");
926         }
927
928         /* The commands below may be executed in any state */
929
930         else if ( (!strcasecmp(parms[1], "NOOP"))
931            || (!strcasecmp(parms[1], "CHECK")) ) {
932                 cprintf("%s OK This command successfully did nothing.\r\n",
933                         parms[0]);
934         }
935
936         else if (!strcasecmp(parms[1], "LOGOUT")) {
937                 cprintf("* BYE %s logging out\r\n", config.c_fqdn);
938                 cprintf("%s OK thank you for using Citadel IMAP\r\n", parms[0]);
939                 CC->kill_me = 1;
940                 return;
941         }
942
943         else if (!strcasecmp(parms[1], "LOGIN")) {
944                 imap_login(num_parms, parms);
945         }
946
947         else if (!strcasecmp(parms[1], "AUTHENTICATE")) {
948                 imap_authenticate(num_parms, parms);
949         }
950
951         else if (!strcasecmp(parms[1], "CAPABILITY")) {
952                 imap_capability(num_parms, parms);
953         }
954
955         else if (!CC->logged_in) {
956                 cprintf("%s BAD Not logged in.\r\n", parms[0]);
957         }
958
959         /* The commans below require a logged-in state */
960
961         else if (!strcasecmp(parms[1], "SELECT")) {
962                 imap_select(num_parms, parms);
963         }
964
965         else if (!strcasecmp(parms[1], "EXAMINE")) {
966                 imap_select(num_parms, parms);
967         }
968
969         else if (!strcasecmp(parms[1], "LSUB")) {
970                 imap_lsub(num_parms, parms);
971         }
972
973         else if (!strcasecmp(parms[1], "LIST")) {
974                 imap_list(num_parms, parms);
975         }
976
977         else if (!strcasecmp(parms[1], "CREATE")) {
978                 imap_create(num_parms, parms);
979         }
980
981         else if (!strcasecmp(parms[1], "DELETE")) {
982                 imap_delete(num_parms, parms);
983         }
984
985         else if (!strcasecmp(parms[1], "RENAME")) {
986                 imap_rename(num_parms, parms);
987         }
988
989         else if (!strcasecmp(parms[1], "STATUS")) {
990                 imap_status(num_parms, parms);
991         }
992
993         else if (!strcasecmp(parms[1], "SUBSCRIBE")) {
994                 imap_subscribe(num_parms, parms);
995         }
996
997         else if (!strcasecmp(parms[1], "UNSUBSCRIBE")) {
998                 imap_unsubscribe(num_parms, parms);
999         }
1000
1001         else if (!strcasecmp(parms[1], "APPEND")) {
1002                 imap_append(num_parms, parms);
1003         }
1004
1005         else if (IMAP->selected == 0) {
1006                 cprintf("%s BAD no folder selected\r\n", parms[0]);
1007         }
1008
1009         /* The commands below require the SELECT state on a mailbox */
1010
1011         else if (!strcasecmp(parms[1], "FETCH")) {
1012                 imap_fetch(num_parms, parms);
1013         }
1014
1015         else if ( (!strcasecmp(parms[1], "UID"))
1016                 && (!strcasecmp(parms[2], "FETCH")) ) {
1017                 imap_uidfetch(num_parms, parms);
1018         }
1019
1020         else if (!strcasecmp(parms[1], "SEARCH")) {
1021                 imap_search(num_parms, parms);
1022         }
1023
1024         else if ( (!strcasecmp(parms[1], "UID"))
1025                 && (!strcasecmp(parms[2], "SEARCH")) ) {
1026                 imap_uidsearch(num_parms, parms);
1027         }
1028
1029         else if (!strcasecmp(parms[1], "STORE")) {
1030                 imap_store(num_parms, parms);
1031         }
1032
1033         else if ( (!strcasecmp(parms[1], "UID"))
1034                 && (!strcasecmp(parms[2], "STORE")) ) {
1035                 imap_uidstore(num_parms, parms);
1036         }
1037
1038         else if (!strcasecmp(parms[1], "COPY")) {
1039                 imap_copy(num_parms, parms);
1040         }
1041
1042         else if ( (!strcasecmp(parms[1], "UID"))
1043                 && (!strcasecmp(parms[2], "COPY")) ) {
1044                 imap_uidcopy(num_parms, parms);
1045         }
1046
1047         else if (!strcasecmp(parms[1], "EXPUNGE")) {
1048                 imap_expunge(num_parms, parms);
1049         }
1050
1051         else if (!strcasecmp(parms[1], "CLOSE")) {
1052                 imap_close(num_parms, parms);
1053         }
1054
1055         /* End of commands.  If we get here, the command is either invalid
1056          * or unimplemented.
1057          */
1058
1059         else {
1060                 cprintf("%s BAD command unrecognized\r\n", parms[0]);
1061         }
1062
1063         /* If the client transmitted a message we can free it now */
1064         imap_free_transmitted_message();
1065 }
1066
1067
1068
1069 /*
1070  * This function is called by dynloader.c to register the IMAP module
1071  * with the Citadel server.
1072  */
1073 char *Dynamic_Module_Init(void)
1074 {
1075         SYM_IMAP = CtdlGetDynamicSymbol();
1076         CtdlRegisterServiceHook(config.c_imap_port,
1077                                 NULL,
1078                                 imap_greeting,
1079                                 imap_command_loop);
1080         CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP);
1081         return "$Id$";
1082 }