]> code.citadel.org Git - citadel.git/blob - citadel/serv_imap.c
* Began implementing IMAP APPEND
[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  * FIXME: Handle wildcards, please.
503  */
504 void imap_lsub(int num_parms, char *parms[]) {
505         char pattern[SIZ];
506         if (num_parms < 4) {
507                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
508                 return;
509         }
510         sprintf(pattern, "%s%s", parms[2], parms[3]);
511
512         if (strlen(parms[3])==0) {
513                 cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n");
514         }
515
516         else {
517                 imap_list_floors("LSUB", pattern);
518                 ForEachRoom(imap_lsub_listroom, pattern);
519         }
520
521         cprintf("%s OK LSUB completed\r\n", parms[0]);
522 }
523
524
525
526 /*
527  * Back end for imap_list()
528  */
529 void imap_list_listroom(struct quickroom *qrbuf, void *data) {
530         char buf[SIZ];
531         int ra;
532         char *pattern;
533
534         pattern = (char *)data;
535
536         /* Only list rooms to which the user has access!! */
537         ra = CtdlRoomAccess(qrbuf, &CC->usersupp);
538         if ( (ra & UA_KNOWN) 
539           || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
540                 imap_mailboxname(buf, sizeof buf, qrbuf);
541                 if (imap_mailbox_matches_pattern(pattern, buf)) {
542                         cprintf("* LIST (\\NoInferiors) \"|\" ");
543                         imap_strout(buf);
544                         cprintf("\r\n");
545                 }
546         }
547 }
548
549
550 /*
551  * Implements the LIST command
552  *
553  * FIXME: Handle wildcards, please.
554  */
555 void imap_list(int num_parms, char *parms[]) {
556         char pattern[SIZ];
557         if (num_parms < 4) {
558                 cprintf("%s BAD arguments invalid\r\n", parms[0]);
559                 return;
560         }
561         sprintf(pattern, "%s%s", parms[2], parms[3]);
562
563         if (strlen(parms[3])==0) {
564                 cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n");
565         }
566
567         else {
568                 imap_list_floors("LIST", pattern);
569                 ForEachRoom(imap_list_listroom, pattern);
570         }
571
572         cprintf("%s OK LIST completed\r\n", parms[0]);
573 }
574
575
576
577 /*
578  * Implements the CREATE command
579  *
580  */
581 void imap_create(int num_parms, char *parms[]) {
582         int ret;
583         char roomname[ROOMNAMELEN];
584         int floornum;
585         int flags;
586         int newroomtype;
587
588         ret = imap_roomname(roomname, sizeof roomname, parms[2]);
589         if (ret < 0) {
590                 cprintf("%s NO Invalid mailbox name or location\r\n",
591                         parms[0]);
592                 return;
593         }
594         floornum = ( ret & 0x00ff );    /* lower 8 bits = floor number */
595         flags =    ( ret & 0xff00 );    /* upper 8 bits = flags        */
596
597         if (flags & IR_MAILBOX) {
598                 newroomtype = 4;        /* private mailbox */
599         }
600         else {
601                 newroomtype = 0;        /* public folder */
602         }
603
604         lprintf(7, "Create new room <%s> on floor <%d> with type <%d>\n",
605                 roomname, floornum, newroomtype);
606
607         ret = create_room(roomname, newroomtype, "", floornum, 1);
608         if (ret == 0) {
609                 cprintf("%s NO Mailbox already exists, or create failed\r\n",
610                         parms[0]);
611         }
612         else {
613                 cprintf("%s OK CREATE completed\r\n", parms[0]);
614         }
615 }
616
617
618 /*
619  * Locate a room by its IMAP folder name, and check access to it
620  */
621 int imap_grabroom(char *returned_roomname, char *foldername) {
622         int ret;
623         char augmented_roomname[ROOMNAMELEN];
624         char roomname[ROOMNAMELEN];
625         int c;
626         struct quickroom QRscratch;
627         int ra;
628         int ok = 0;
629
630         ret = imap_roomname(roomname, sizeof roomname, foldername);
631         if (ret < 0) {
632                 return(1);
633         }
634
635         /* First try a regular match */
636         c = getroom(&QRscratch, roomname);
637
638         /* Then try a mailbox name match */
639         if (c != 0) {
640                 MailboxName(augmented_roomname, &CC->usersupp, roomname);
641                 c = getroom(&QRscratch, augmented_roomname);
642                 if (c == 0)
643                         strcpy(roomname, augmented_roomname);
644         }
645
646         /* If the room exists, check security/access */
647         if (c == 0) {
648                 /* See if there is an existing user/room relationship */
649                 ra = CtdlRoomAccess(&QRscratch, &CC->usersupp);
650
651                 /* normal clients have to pass through security */
652                 if (ra & UA_KNOWN) {
653                         ok = 1;
654                 }
655         }
656
657         /* Fail here if no such room */
658         if (!ok) {
659                 strcpy(returned_roomname, "");
660                 return(2);
661         }
662         else {
663                 strcpy(returned_roomname, QRscratch.QRname);
664                 return(0);
665         }
666 }
667
668
669 /*
670  * Implements the STATUS command (sort of)
671  *
672  */
673 void imap_status(int num_parms, char *parms[]) {
674         int ret;
675         char roomname[ROOMNAMELEN];
676         char buf[SIZ];
677         char savedroom[ROOMNAMELEN];
678         int msgs, new;
679
680         ret = imap_grabroom(roomname, parms[2]);
681         if (ret != 0) {
682                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
683                         parms[0]);
684                 return;
685         }
686
687         /*
688          * usergoto() formally takes us to the desired room, happily returning
689          * the number of messages and number of new messages.  (If another
690          * folder is selected, save its name so we can return there!!!!!)
691          */
692         if (IMAP->selected) {
693                 strcpy(savedroom, CC->quickroom.QRname);
694         }
695         usergoto(roomname, 0, &msgs, &new);
696
697         /*
698          * Tell the client what it wants to know.  In fact, tell it *more* than
699          * it wants to know.  We happily IGnore the supplied status data item
700          * names and simply spew all possible data items.  It's far easier to
701          * code and probably saves us some processing time too.
702          *
703          * FIXME we need to implement RECENT and UNSEEN eventually...
704          */
705
706         imap_mailboxname(buf, sizeof buf, &CC->quickroom);
707         cprintf("* STATUS ");
708         imap_strout(buf);
709         cprintf(" (MESSAGES %d RECENT 0 UIDNEXT %ld "
710                 "UIDVALIDITY 0 UNSEEN 0)\r\n",
711                 msgs,
712                 CitControl.MMhighest + 1
713         );
714
715         /*
716          * If another folder is selected, go back to that room so we can resume
717          * our happy day without violent explosions.
718          */
719         if (IMAP->selected) {
720                 usergoto(savedroom, 0, &msgs, &new);
721         }
722
723         /*
724          * Oooh, look, we're done!
725          */
726         cprintf("%s OK STATUS completed\r\n", parms[0]);
727 }
728
729
730
731 /*
732  * Implements the SUBSCRIBE command
733  *
734  */
735 void imap_subscribe(int num_parms, char *parms[]) {
736         int ret;
737         char roomname[ROOMNAMELEN];
738         char savedroom[ROOMNAMELEN];
739         int msgs, new;
740
741         ret = imap_grabroom(roomname, parms[2]);
742         if (ret != 0) {
743                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
744                         parms[0]);
745                 return;
746         }
747
748         /*
749          * usergoto() formally takes us to the desired room, which has the side
750          * effect of marking the room as not-zapped ... exactly the effect
751          * we're looking for.
752          */
753         if (IMAP->selected) {
754                 strcpy(savedroom, CC->quickroom.QRname);
755         }
756         usergoto(roomname, 0, &msgs, &new);
757
758         /*
759          * If another folder is selected, go back to that room so we can resume
760          * our happy day without violent explosions.
761          */
762         if (IMAP->selected) {
763                 usergoto(savedroom, 0, &msgs, &new);
764         }
765
766         cprintf("%s OK SUBSCRIBE completed\r\n", parms[0]);
767 }
768
769
770 /*
771  * Implements the UNSUBSCRIBE command
772  *
773  */
774 void imap_unsubscribe(int num_parms, char *parms[]) {
775         int ret;
776         char roomname[ROOMNAMELEN];
777         char savedroom[ROOMNAMELEN];
778         int msgs, new;
779
780         ret = imap_grabroom(roomname, parms[2]);
781         if (ret != 0) {
782                 cprintf("%s NO Invalid mailbox name or location, or access denied\r\n",
783                         parms[0]);
784                 return;
785         }
786
787         /*
788          * usergoto() formally takes us to the desired room.
789          */
790         if (IMAP->selected) {
791                 strcpy(savedroom, CC->quickroom.QRname);
792         }
793         usergoto(roomname, 0, &msgs, &new);
794
795         /* 
796          * Now make the API call to zap the room
797          */
798         if (CtdlForgetThisRoom() == 0) {
799                 cprintf("%s OK UNSUBSCRIBE completed\r\n", parms[0]);
800         }
801         else {
802                 cprintf("%s NO You may not unsubscribe from this folder.\r\n",
803                         parms[0]);
804         }
805
806         /*
807          * If another folder is selected, go back to that room so we can resume
808          * our happy day without violent explosions.
809          */
810         if (IMAP->selected) {
811                 usergoto(savedroom, 0, &msgs, &new);
812         }
813 }
814
815
816
817 /*
818  * Implements the DELETE command
819  *
820  */
821 void imap_delete(int num_parms, char *parms[]) {
822         int ret;
823         char roomname[ROOMNAMELEN];
824         char savedroom[ROOMNAMELEN];
825         int msgs, new;
826
827         ret = imap_grabroom(roomname, parms[2]);
828         if (ret != 0) {
829                 cprintf("%s NO Invalid mailbox name, or access denied\r\n",
830                         parms[0]);
831                 return;
832         }
833
834         /*
835          * usergoto() formally takes us to the desired room, happily returning
836          * the number of messages and number of new messages.  (If another
837          * folder is selected, save its name so we can return there!!!!!)
838          */
839         if (IMAP->selected) {
840                 strcpy(savedroom, CC->quickroom.QRname);
841         }
842         usergoto(roomname, 0, &msgs, &new);
843
844         /*
845          * Now delete the room.
846          */
847         if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->quickroom)) {
848                 cprintf("%s OK DELETE completed\r\n", parms[0]);
849                 delete_room(&CC->quickroom);
850         }
851         else {
852                 cprintf("%s NO Can't delete this folder.\r\n", parms[0]);
853         }
854
855         /*
856          * If another folder is selected, go back to that room so we can resume
857          * our happy day without violent explosions.
858          */
859         if (IMAP->selected) {
860                 usergoto(savedroom, 0, &msgs, &new);
861         }
862 }
863
864
865
866 /*
867  * Implements the RENAME command
868  *
869  */
870 void imap_rename(int num_parms, char *parms[]) {
871         cprintf("%s NO The RENAME command is not yet implemented (FIXME)\r\n",
872                 parms[0]);
873 }
874
875
876
877 /* 
878  * Main command loop for IMAP sessions.
879  */
880 void imap_command_loop(void) {
881         char cmdbuf[SIZ];
882         char *parms[SIZ];
883         int num_parms;
884
885         time(&CC->lastcmd);
886         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
887         if (client_gets(cmdbuf) < 1) {
888                 lprintf(3, "IMAP socket is broken.  Ending session.\r\n");
889                 CC->kill_me = 1;
890                 return;
891         }
892
893         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
894         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
895
896
897         /* strip off l/t whitespace and CRLF */
898         if (cmdbuf[strlen(cmdbuf)-1]=='\n') cmdbuf[strlen(cmdbuf)-1]=0;
899         if (cmdbuf[strlen(cmdbuf)-1]=='\r') cmdbuf[strlen(cmdbuf)-1]=0;
900         striplt(cmdbuf);
901
902         /* If we're in the middle of a multi-line command, handle that */
903         if (IMAP->authstate == imap_as_expecting_username) {
904                 imap_auth_login_user(cmdbuf);
905                 return;
906         }
907         if (IMAP->authstate == imap_as_expecting_password) {
908                 imap_auth_login_pass(cmdbuf);
909                 return;
910         }
911
912
913         /* Ok, at this point we're in normal command mode.  The first thing
914          * we do is print any incoming pages (yeah! we really do!)
915          */
916         imap_print_express_messages();
917
918         /*
919          * Before processing the command that was just entered... if we happen
920          * to have a folder selected, we'd like to rescan that folder for new
921          * messages, and for deletions/changes of existing messages.  This
922          * could probably be optimized somehow, but IMAP sucks...
923          */
924         if (IMAP->selected) {
925                 imap_rescan_msgids();
926         }
927
928         /* Now for the command set. */
929
930         /* Grab the tag, command, and parameters.  Check syntax. */
931         num_parms = imap_parameterize(parms, cmdbuf);
932         if (num_parms < 2) {
933                 cprintf("BAD syntax error\r\n");
934         }
935
936         /* The commands below may be executed in any state */
937
938         else if ( (!strcasecmp(parms[1], "NOOP"))
939            || (!strcasecmp(parms[1], "CHECK")) ) {
940                 cprintf("%s OK This command successfully did nothing.\r\n",
941                         parms[0]);
942         }
943
944         else if (!strcasecmp(parms[1], "LOGOUT")) {
945                 cprintf("* BYE %s logging out\r\n", config.c_fqdn);
946                 cprintf("%s OK thank you for using Citadel IMAP\r\n", parms[0]);
947                 CC->kill_me = 1;
948                 return;
949         }
950
951         else if (!strcasecmp(parms[1], "LOGIN")) {
952                 imap_login(num_parms, parms);
953         }
954
955         else if (!strcasecmp(parms[1], "AUTHENTICATE")) {
956                 imap_authenticate(num_parms, parms);
957         }
958
959         else if (!strcasecmp(parms[1], "CAPABILITY")) {
960                 imap_capability(num_parms, parms);
961         }
962
963         else if (!CC->logged_in) {
964                 cprintf("%s BAD Not logged in.\r\n", parms[0]);
965         }
966
967         /* The commans below require a logged-in state */
968
969         else if (!strcasecmp(parms[1], "SELECT")) {
970                 imap_select(num_parms, parms);
971         }
972
973         else if (!strcasecmp(parms[1], "EXAMINE")) {
974                 imap_select(num_parms, parms);
975         }
976
977         else if (!strcasecmp(parms[1], "LSUB")) {
978                 imap_lsub(num_parms, parms);
979         }
980
981         else if (!strcasecmp(parms[1], "LIST")) {
982                 imap_list(num_parms, parms);
983         }
984
985         else if (!strcasecmp(parms[1], "CREATE")) {
986                 imap_create(num_parms, parms);
987         }
988
989         else if (!strcasecmp(parms[1], "DELETE")) {
990                 imap_delete(num_parms, parms);
991         }
992
993         else if (!strcasecmp(parms[1], "RENAME")) {
994                 imap_rename(num_parms, parms);
995         }
996
997         else if (!strcasecmp(parms[1], "STATUS")) {
998                 imap_status(num_parms, parms);
999         }
1000
1001         else if (!strcasecmp(parms[1], "SUBSCRIBE")) {
1002                 imap_subscribe(num_parms, parms);
1003         }
1004
1005         else if (!strcasecmp(parms[1], "UNSUBSCRIBE")) {
1006                 imap_unsubscribe(num_parms, parms);
1007         }
1008
1009         else if (!strcasecmp(parms[1], "APPEND")) {
1010                 imap_append(num_parms, parms);
1011         }
1012
1013         else if (IMAP->selected == 0) {
1014                 cprintf("%s BAD no folder selected\r\n", parms[0]);
1015         }
1016
1017         /* The commands below require the SELECT state on a mailbox */
1018
1019         else if (!strcasecmp(parms[1], "FETCH")) {
1020                 imap_fetch(num_parms, parms);
1021         }
1022
1023         else if ( (!strcasecmp(parms[1], "UID"))
1024                 && (!strcasecmp(parms[2], "FETCH")) ) {
1025                 imap_uidfetch(num_parms, parms);
1026         }
1027
1028         else if (!strcasecmp(parms[1], "SEARCH")) {
1029                 imap_search(num_parms, parms);
1030         }
1031
1032         else if ( (!strcasecmp(parms[1], "UID"))
1033                 && (!strcasecmp(parms[2], "SEARCH")) ) {
1034                 imap_uidsearch(num_parms, parms);
1035         }
1036
1037         else if (!strcasecmp(parms[1], "STORE")) {
1038                 imap_store(num_parms, parms);
1039         }
1040
1041         else if ( (!strcasecmp(parms[1], "UID"))
1042                 && (!strcasecmp(parms[2], "STORE")) ) {
1043                 imap_uidstore(num_parms, parms);
1044         }
1045
1046         else if (!strcasecmp(parms[1], "COPY")) {
1047                 imap_copy(num_parms, parms);
1048         }
1049
1050         else if ( (!strcasecmp(parms[1], "UID"))
1051                 && (!strcasecmp(parms[2], "COPY")) ) {
1052                 imap_uidcopy(num_parms, parms);
1053         }
1054
1055         else if (!strcasecmp(parms[1], "EXPUNGE")) {
1056                 imap_expunge(num_parms, parms);
1057         }
1058
1059         else if (!strcasecmp(parms[1], "CLOSE")) {
1060                 imap_close(num_parms, parms);
1061         }
1062
1063         /* End of commands.  If we get here, the command is either invalid
1064          * or unimplemented.
1065          */
1066
1067         else {
1068                 cprintf("%s BAD command unrecognized\r\n", parms[0]);
1069         }
1070
1071         /* If the client transmitted a message we can free it now */
1072         imap_free_transmitted_message();
1073 }
1074
1075
1076
1077 /*
1078  * This function is called by dynloader.c to register the IMAP module
1079  * with the Citadel server.
1080  */
1081 char *Dynamic_Module_Init(void)
1082 {
1083         SYM_IMAP = CtdlGetDynamicSymbol();
1084         CtdlRegisterServiceHook(config.c_imap_port,
1085                                 NULL,
1086                                 imap_greeting,
1087                                 imap_command_loop);
1088         CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP);
1089         return "$Id$";
1090 }