62a3ba95b186cc3ed0c3052416938a039e0843cc
[citadel.git] / citadel / serv_imap.c
1 /*
2  * $Id$ 
3  *
4  * IMAP server for the Citadel/UX system
5  * Copyright (C) 2000 by Art Cancro and others.
6  * This code is released under the terms of the GNU General Public License.
7  *
8  * *** THIS IS UNDER DEVELOPMENT.  IT DOES NOT WORK.  DO NOT USE IT. ***
9  * 
10  */
11
12 #include "sysdep.h"
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <sys/time.h>
22 #include <sys/wait.h>
23 #include <ctype.h>
24 #include <string.h>
25 #include <limits.h>
26 #include "citadel.h"
27 #include "server.h"
28 #include <time.h>
29 #include "sysdep_decls.h"
30 #include "citserver.h"
31 #include "support.h"
32 #include "config.h"
33 #include "dynloader.h"
34 #include "room_ops.h"
35 #include "user_ops.h"
36 #include "policy.h"
37 #include "database.h"
38 #include "msgbase.h"
39 #include "tools.h"
40 #include "internet_addressing.h"
41 #include "serv_imap.h"
42 #include "imap_tools.h"
43 #include "imap_fetch.h"
44 #include "imap_search.h"
45
46
47 long SYM_IMAP;
48
49
50 /*
51  * If there is a message ID map in memory, free it
52  */
53 void imap_free_msgids(void) {
54         if (IMAP->msgids != NULL) {
55                 phree(IMAP->msgids);
56                 IMAP->msgids = NULL;
57                 IMAP->num_msgs = 0;
58         }
59         if (IMAP->flags != NULL) {
60                 phree(IMAP->flags);
61                 IMAP->flags = NULL;
62         }
63 }
64
65
66 /*
67  * Back end for imap_load_msgids()
68  *
69  * FIXME: this should be optimized by figuring out a way to allocate memory
70  * once rather than doing a reallok() for each message.
71  */
72 void imap_add_single_msgid(long msgnum, void *userdata) {
73         
74         IMAP->num_msgs = IMAP->num_msgs + 1;
75         if (IMAP->msgids == NULL) {
76                 IMAP->msgids = mallok(IMAP->num_msgs * sizeof(long));
77         }
78         else {
79                 IMAP->msgids = reallok(IMAP->msgids,
80                         IMAP->num_msgs * sizeof(long));
81         }
82         if (IMAP->flags == NULL) {
83                 IMAP->flags = mallok(IMAP->num_msgs * sizeof(long));
84         }
85         else {
86                 IMAP->flags = reallok(IMAP->flags,
87                         IMAP->num_msgs * sizeof(long));
88         }
89         IMAP->msgids[IMAP->num_msgs - 1] = msgnum;
90         IMAP->flags[IMAP->num_msgs - 1] = 0;
91 }
92
93
94
95 /*
96  * Set up a message ID map for the current room (folder)
97  */
98 void imap_load_msgids(void) {
99          
100         if (IMAP->selected == 0) {
101                 lprintf(5, "imap_load_msgids() can't run; no room selected\n");
102                 return;
103         }
104
105         imap_free_msgids();     /* If there was already a map, free it */
106
107         CtdlForEachMessage(MSGS_ALL, 0L, (-63), NULL, NULL,
108                 imap_add_single_msgid, NULL);
109
110         lprintf(9, "imap_load_msgids() mapped %d messages\n", IMAP->num_msgs);
111 }
112
113
114
115
116 /*
117  * This cleanup function blows away the temporary memory and files used by
118  * the IMAP server.
119  */
120 void imap_cleanup_function(void) {
121
122         /* Don't do this stuff if this is not a IMAP session! */
123         if (CC->h_command_function != imap_command_loop) return;
124
125         lprintf(9, "Performing IMAP cleanup hook\n");
126         imap_free_msgids();
127         lprintf(9, "Finished IMAP cleanup hook\n");
128 }
129
130
131
132 /*
133  * Here's where our IMAP session begins its happy day.
134  */
135 void imap_greeting(void) {
136
137         strcpy(CC->cs_clientname, "IMAP session");
138         CtdlAllocUserData(SYM_IMAP, sizeof(struct citimap));
139         IMAP->authstate = imap_as_normal;
140
141         cprintf("* OK %s Citadel/UX IMAP4rev1 server ready\r\n",
142                 config.c_fqdn);
143 }
144
145
146 /*
147  * implements the LOGIN command (ordinary username/password login)
148  */
149 void imap_login(int num_parms, char *parms[]) {
150         if (CtdlLoginExistingUser(parms[2]) == login_ok) {
151                 if (CtdlTryPassword(parms[3]) == pass_ok) {
152                         cprintf("%s OK login successful\r\n", parms[0]);
153                         return;
154                 }
155         }
156
157         cprintf("%s BAD Login incorrect\r\n", parms[0]);
158 }
159
160
161 /*
162  * Implements the AYTHENTICATE command
163  */
164 void imap_authenticate(int num_parms, char *parms[]) {
165         char buf[256];
166
167         if (num_parms != 3) {
168                 cprintf("%s BAD incorrect number of parameters\r\n", parms[0]);
169                 return;
170         }
171
172         if (!strcasecmp(parms[2], "LOGIN")) {
173                 encode_base64(buf, "Username:");
174                 cprintf("+ %s\r\n", buf);
175                 IMAP->authstate = imap_as_expecting_username;
176                 strcpy(IMAP->authseq, parms[0]);
177                 return;
178         }
179
180         else {
181                 cprintf("%s NO AUTHENTICATE %s failed\r\n",
182                         parms[0], parms[1]);
183         }
184 }
185
186 void imap_auth_login_user(char *cmd) {
187         char buf[256];
188
189         decode_base64(buf, cmd);
190         CtdlLoginExistingUser(buf);
191         encode_base64(buf, "Password:");
192         cprintf("+ %s\r\n", buf);
193         IMAP->authstate = imap_as_expecting_password;
194         return;
195 }
196
197 void imap_auth_login_pass(char *cmd) {
198         char buf[256];
199
200         decode_base64(buf, cmd);
201         if (CtdlTryPassword(buf) == pass_ok) {
202                 cprintf("%s OK authentication succeeded\r\n", IMAP->authseq);
203         }
204         else {
205                 cprintf("%s NO authentication failed\r\n", IMAP->authseq);
206         }
207         IMAP->authstate = imap_as_normal;
208         return;
209 }
210
211
212
213 /*
214  * implements the CAPABILITY command
215  */
216 void imap_capability(int num_parms, char *parms[]) {
217         cprintf("* CAPABILITY IMAP4 IMAP4REV1 AUTH=LOGIN\r\n");
218         cprintf("%s OK CAPABILITY completed\r\n", parms[0]);
219 }
220
221
222
223
224
225 /*
226  * implements the SELECT command
227  */
228 void imap_select(int num_parms, char *parms[]) {
229         char towhere[256];
230         char augmented_roomname[256];
231         int c = 0;
232         int ok = 0;
233         int ra = 0;
234         struct quickroom QRscratch;
235         int msgs, new;
236
237         strcpy(towhere, parms[2]);
238
239         /* IMAP uses the reserved name "INBOX" for the user's default incoming
240          * mail folder.  Convert this to whatever Citadel is using for the
241          * default mail room name (usually "Mail>").
242          */
243         if (!strcasecmp(towhere, "INBOX")) {
244                 strcpy(towhere, MAILROOM);
245         }
246
247         /* First try a regular match */
248         c = getroom(&QRscratch, towhere);
249
250         /* Then try a mailbox name match */
251         if (c != 0) {
252                 MailboxName(augmented_roomname, &CC->usersupp, towhere);
253                 c = getroom(&QRscratch, augmented_roomname);
254                 if (c == 0)
255                         strcpy(towhere, augmented_roomname);
256         }
257
258         /* If the room exists, check security/access */
259         if (c == 0) {
260                 /* See if there is an existing user/room relationship */
261                 ra = CtdlRoomAccess(&QRscratch, &CC->usersupp);
262
263                 /* normal clients have to pass through security */
264                 if (ra & UA_KNOWN) {
265                         ok = 1;
266                 }
267         }
268
269         /* Fail here if no such room */
270         if (!ok) {
271                 cprintf("%s NO ... no such room, or access denied\r\n",
272                         parms[0]);
273                 IMAP->selected = 0;
274                 return;
275         }
276
277         /*
278          * usergoto() formally takes us to the desired room, happily returning
279          * the number of messages and number of new messages.
280          */
281         usergoto(QRscratch.QRname, 0, &msgs, &new);
282         IMAP->selected = 1;
283
284         if (!strcasecmp(parms[1], "EXAMINE")) {
285                 IMAP->readonly = 1;
286         }
287         else {
288                 IMAP->readonly = 0;
289         }
290
291         imap_load_msgids();
292
293         /* FIXME ... much more info needs to be supplied here */
294         cprintf("* %d EXISTS\r\n", msgs);
295         cprintf("* %d RECENT\r\n", new);
296         cprintf("* OK [UIDVALIDITY 0] UIDs valid\r\n");
297         cprintf("%s OK [%s] %s completed\r\n",
298                 parms[0],
299                 (IMAP->readonly ? "READ-ONLY" : "READ-WRITE"),
300                 parms[1]);
301 }
302
303
304
305 /*
306  * implements the CLOSE command
307  */
308 void imap_close(int num_parms, char *parms[]) {
309         IMAP->selected = 0;
310         IMAP->readonly = 0;
311         imap_free_msgids();
312         cprintf("%s OK CLOSE completed\r\n", parms[0]);
313 }
314
315
316
317
318
319 /*
320  * Back end for imap_lsub()
321  *
322  * IMAP "subscribed folder" is equivocated to Citadel "known rooms."  This
323  * may or may not be the desired behavior in the future.
324  */
325 void imap_lsub_listroom(struct quickroom *qrbuf, void *data) {
326         char buf[256];
327         int ra;
328
329         /* Only list rooms to which the user has access!! */
330         ra = CtdlRoomAccess(qrbuf, &CC->usersupp);
331         if (ra & UA_KNOWN) {
332                 imap_mailboxname(buf, sizeof buf, qrbuf);
333                 cprintf("* LSUB () \"|\" \"%s\"\r\n", buf);
334         }
335 }
336
337
338 /*
339  * Implements the LSUB command
340  *
341  * FIXME: Handle wildcards, please.
342  */
343 void imap_lsub(int num_parms, char *parms[]) {
344         ForEachRoom(imap_lsub_listroom, NULL);
345         cprintf("%s OK LSUB completed\r\n", parms[0]);
346 }
347
348
349
350 /*
351  * Back end for imap_list()
352  */
353 void imap_list_listroom(struct quickroom *qrbuf, void *data) {
354         char buf[256];
355         int ra;
356
357         /* Only list rooms to which the user has access!! */
358         ra = CtdlRoomAccess(qrbuf, &CC->usersupp);
359         if ( (ra & UA_KNOWN) 
360           || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) {
361                 imap_mailboxname(buf, sizeof buf, qrbuf);
362                 cprintf("* LIST () \"|\" \"%s\"\r\n", buf);
363         }
364 }
365
366
367 /*
368  * Implements the LIST command
369  *
370  * FIXME: Handle wildcards, please.
371  */
372 void imap_list(int num_parms, char *parms[]) {
373         ForEachRoom(imap_list_listroom, NULL);
374         cprintf("%s OK LIST completed\r\n", parms[0]);
375 }
376
377
378
379 /* 
380  * Main command loop for IMAP sessions.
381  */
382 void imap_command_loop(void) {
383         char cmdbuf[256];
384         char *parms[256];
385         int num_parms;
386
387         time(&CC->lastcmd);
388         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
389         if (client_gets(cmdbuf) < 1) {
390                 lprintf(3, "IMAP socket is broken.  Ending session.\r\n");
391                 CC->kill_me = 1;
392                 return;
393         }
394
395         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
396         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
397
398
399         /* strip off l/t whitespace and CRLF */
400         if (cmdbuf[strlen(cmdbuf)-1]=='\n') cmdbuf[strlen(cmdbuf)-1]=0;
401         if (cmdbuf[strlen(cmdbuf)-1]=='\r') cmdbuf[strlen(cmdbuf)-1]=0;
402         striplt(cmdbuf);
403
404         /* If we're in the middle of a multi-line command, handle that */
405         if (IMAP->authstate == imap_as_expecting_username) {
406                 imap_auth_login_user(cmdbuf);
407                 return;
408         }
409         if (IMAP->authstate == imap_as_expecting_password) {
410                 imap_auth_login_pass(cmdbuf);
411                 return;
412         }
413
414
415         /* Ok, at this point we're in normal command mode */
416
417         /* grab the tag */
418         num_parms = imap_parameterize(parms, cmdbuf);
419
420         /* commands which may be executed in any state */
421
422         if ( (!strcasecmp(parms[1], "NOOP"))
423            || (!strcasecmp(parms[1], "CHECK")) ) {
424                 cprintf("%s OK This command successfully did nothing.\r\n",
425                         parms[0]);
426         }
427
428         else if (!strcasecmp(parms[1], "LOGOUT")) {
429                 cprintf("* BYE %s logging out\r\n", config.c_fqdn);
430                 cprintf("%s OK thank you for using Citadel IMAP\r\n", parms[0]);
431                 CC->kill_me = 1;
432                 return;
433         }
434
435         else if (!strcasecmp(parms[1], "LOGIN")) {
436                 imap_login(num_parms, parms);
437         }
438
439         else if (!strcasecmp(parms[1], "AUTHENTICATE")) {
440                 imap_authenticate(num_parms, parms);
441         }
442
443         else if (!strcasecmp(parms[1], "CAPABILITY")) {
444                 imap_capability(num_parms, parms);
445         }
446
447         else if (!CC->logged_in) {
448                 cprintf("%s BAD Not logged in.\r\n", parms[0]);
449         }
450
451         /* commands requiring the client to be logged in */
452
453         else if (!strcasecmp(parms[1], "SELECT")) {
454                 imap_select(num_parms, parms);
455         }
456
457         else if (!strcasecmp(parms[1], "EXAMINE")) {
458                 imap_select(num_parms, parms);
459         }
460
461         else if (!strcasecmp(parms[1], "LSUB")) {
462                 imap_lsub(num_parms, parms);
463         }
464
465         else if (!strcasecmp(parms[1], "LIST")) {
466                 imap_list(num_parms, parms);
467         }
468
469         else if (IMAP->selected == 0) {
470                 cprintf("%s BAD no folder selected\r\n", parms[0]);
471         }
472
473         /* commands requiring the SELECT state */
474
475         else if (!strcasecmp(parms[1], "FETCH")) {
476                 imap_fetch(num_parms, parms);
477         }
478
479         else if ( (!strcasecmp(parms[1], "UID"))
480                 && (!strcasecmp(parms[2], "FETCH")) ) {
481                 imap_uidfetch(num_parms, parms);
482         }
483
484         else if (!strcasecmp(parms[1], "SEARCH")) {
485                 imap_search(num_parms, parms);
486         }
487
488         else if ( (!strcasecmp(parms[1], "UID"))
489                 && (!strcasecmp(parms[2], "SEARCH")) ) {
490                 imap_uidsearch(num_parms, parms);
491         }
492
493         else if (!strcasecmp(parms[1], "CLOSE")) {
494                 imap_close(num_parms, parms);
495         }
496
497         /* end of commands */
498
499         else {
500                 cprintf("%s BAD command unrecognized\r\n", parms[0]);
501         }
502
503 }
504
505
506
507 char *Dynamic_Module_Init(void)
508 {
509         SYM_IMAP = CtdlGetDynamicSymbol();
510         CtdlRegisterServiceHook(config.c_imap_port,
511                                 NULL,
512                                 imap_greeting,
513                                 imap_command_loop);
514         CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP);
515         return "$Id$";
516 }