Continue phase 2 of modules stuff.
[citadel.git] / citadel / modules / pop3 / serv_pop3.c
1 /*
2  * $Id$ 
3  *
4  * POP3 service for the Citadel system
5  * Copyright (C) 1998-2001 by Art Cancro and others.
6  * This code is released under the terms of the GNU General Public License.
7  *
8  * Current status of standards conformance:
9  *
10  * -> All required POP3 commands described in RFC1939 are implemented.
11  * -> All optional POP3 commands described in RFC1939 are also implemented.
12  * -> The deprecated "LAST" command is included in this implementation, because
13  *    there exist mail clients which insist on using it (such as Bynari
14  *    TradeMail, and certain versions of Eudora).
15  * -> Capability detection via the method described in RFC2449 is implemented.
16  * 
17  */
18
19 #include "sysdep.h"
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <errno.h>
27 #include <sys/types.h>
28
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
31 # include <time.h>
32 #else
33 # if HAVE_SYS_TIME_H
34 #  include <sys/time.h>
35 # else
36 #  include <time.h>
37 # endif
38 #endif
39
40 #include <sys/wait.h>
41 #include <string.h>
42 #include <limits.h>
43 #include <ctype.h>
44 #include "citadel.h"
45 #include "server.h"
46 #include "citserver.h"
47 #include "support.h"
48 #include "config.h"
49 #include "room_ops.h"
50 #include "user_ops.h"
51 #include "policy.h"
52 #include "database.h"
53 #include "msgbase.h"
54 #include "tools.h"
55 #include "internet_addressing.h"
56 #include "serv_pop3.h"
57 #include "md5.h"
58
59
60
61 #include "ctdl_module.h"
62
63
64
65 /*
66  * This cleanup function blows away the temporary memory and files used by
67  * the POP3 server.
68  */
69 void pop3_cleanup_function(void) {
70
71         /* Don't do this stuff if this is not a POP3 session! */
72         if (CC->h_command_function != pop3_command_loop) return;
73
74         lprintf(CTDL_DEBUG, "Performing POP3 cleanup hook\n");
75         if (POP3->msgs != NULL) free(POP3->msgs);
76
77         free(POP3);
78 }
79
80
81
82 /*
83  * Here's where our POP3 session begins its happy day.
84  */
85 void pop3_greeting(void) {
86         strcpy(CC->cs_clientname, "POP3 session");
87         CC->internal_pgm = 1;
88         POP3 = malloc(sizeof(struct citpop3));
89         memset(POP3, 0, sizeof(struct citpop3));
90
91         cprintf("+OK Citadel POP3 server %s\r\n",
92                 CC->cs_nonce);
93 }
94
95
96 /*
97  * POP3S is just like POP3, except it goes crypto right away.
98  */
99 void pop3s_greeting(void) {
100         CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
101         pop3_greeting();
102 }
103
104
105
106 /*
107  * Specify user name (implements POP3 "USER" command)
108  */
109 void pop3_user(char *argbuf) {
110         char username[SIZ];
111
112         if (CC->logged_in) {
113                 cprintf("-ERR You are already logged in.\r\n");
114                 return;
115         }
116
117         strcpy(username, argbuf);
118         striplt(username);
119
120         /* lprintf(CTDL_DEBUG, "Trying <%s>\n", username); */
121         if (CtdlLoginExistingUser(NULL, username) == login_ok) {
122                 cprintf("+OK Password required for %s\r\n", username);
123         }
124         else {
125                 cprintf("-ERR No such user.\r\n");
126         }
127 }
128
129
130
131 /*
132  * Back end for pop3_grab_mailbox()
133  */
134 void pop3_add_message(long msgnum, void *userdata) {
135         struct MetaData smi;
136
137         ++POP3->num_msgs;
138         if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg));
139         else POP3->msgs = realloc(POP3->msgs, 
140                 (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
141         POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
142         POP3->msgs[POP3->num_msgs-1].deleted = 0;
143
144         /* We need to know the length of this message when it is printed in
145          * RFC822 format.  Perhaps we have cached this length in the message's
146          * metadata record.  If so, great; if not, measure it and then cache
147          * it for next time.
148          */
149         GetMetaData(&smi, msgnum);
150         if (smi.meta_rfc822_length <= 0L) {
151                 CC->redirect_buffer = malloc(SIZ);
152                 CC->redirect_len = 0;
153                 CC->redirect_alloc = SIZ;
154                 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
155                 smi.meta_rfc822_length = CC->redirect_len;
156                 free(CC->redirect_buffer);
157                 CC->redirect_buffer = NULL;
158                 CC->redirect_len = 0;
159                 CC->redirect_alloc = 0;
160                 PutMetaData(&smi);
161         }
162         POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
163 }
164
165
166
167 /*
168  * Open the inbox and read its contents.
169  * (This should be called only once, by pop3_pass(), and returns the number
170  * of messages in the inbox, or -1 for error)
171  */
172 int pop3_grab_mailbox(void) {
173         struct visit vbuf;
174         int i;
175
176         if (getroom(&CC->room, MAILROOM) != 0) return(-1);
177
178         /* Load up the messages */
179         CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
180                 pop3_add_message, NULL);
181
182         /* Figure out which are old and which are new */
183         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
184         POP3->lastseen = (-1);
185         if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
186                 if (is_msg_in_sequence_set(vbuf.v_seen,
187                    (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
188                         POP3->lastseen = i;
189                 }
190         }
191
192         return(POP3->num_msgs);
193 }
194
195 void pop3_login(void)
196 {
197         int msgs;
198         
199         msgs = pop3_grab_mailbox();
200         if (msgs >= 0) {
201                 cprintf("+OK %s is logged in (%d messages)\r\n",
202                         CC->user.fullname, msgs);
203                 lprintf(CTDL_NOTICE, "POP3 authenticated %s\n", CC->user.fullname);
204         }
205         else {
206                 cprintf("-ERR Can't open your mailbox\r\n");
207         }
208         
209 }
210
211 void pop3_apop(char *argbuf)
212 {
213    char username[SIZ];
214    char userdigest[MD5_HEXSTRING_SIZE];
215    char realdigest[MD5_HEXSTRING_SIZE];
216    char *sptr;
217    
218    if (CC->logged_in)
219    {
220         cprintf("-ERR You are already logged in; not in the AUTHORIZATION phase.\r\n");
221         return;
222    }
223    
224    if ((sptr = strchr(argbuf, ' ')) == NULL)
225    {
226         cprintf("-ERR Invalid APOP line.\r\n");
227         return;
228    }
229    
230    *sptr++ = '\0';
231    
232    while ((*sptr) && isspace(*sptr))
233       sptr++;
234    
235    strncpy(username, argbuf, sizeof(username)-1);
236    username[sizeof(username)-1] = '\0';
237    
238    memset(userdigest, MD5_HEXSTRING_SIZE, 0);
239    strncpy(userdigest, sptr, MD5_HEXSTRING_SIZE-1);
240    
241    if (CtdlLoginExistingUser(NULL, username) != login_ok)
242    {
243         cprintf("-ERR No such user.\r\n");
244         return;
245    }
246    
247    if (getuser(&CC->user, CC->curr_user))
248    {
249         cprintf("-ERR No such user.\r\n");
250         return;
251    }
252    
253    make_apop_string(CC->user.password, CC->cs_nonce, realdigest, sizeof realdigest);
254    if (!strncasecmp(realdigest, userdigest, MD5_HEXSTRING_SIZE-1))
255    {
256         do_login();
257         pop3_login();
258    }
259    else
260    {
261         cprintf("-ERR That is NOT the password.\r\n");
262    }
263 }
264
265
266 /*
267  * Authorize with password (implements POP3 "PASS" command)
268  */
269 void pop3_pass(char *argbuf) {
270         char password[SIZ];
271
272         strcpy(password, argbuf);
273         striplt(password);
274
275         /* lprintf(CTDL_DEBUG, "Trying <%s>\n", password); */
276         if (CtdlTryPassword(password) == pass_ok) {
277                 pop3_login();
278         }
279         else {
280                 cprintf("-ERR That is NOT the password.\r\n");
281         }
282 }
283
284
285
286 /*
287  * list available msgs
288  */
289 void pop3_list(char *argbuf) {
290         int i;
291         int which_one;
292
293         which_one = atoi(argbuf);
294
295         /* "list one" mode */
296         if (which_one > 0) {
297                 if (which_one > POP3->num_msgs) {
298                         cprintf("-ERR no such message, only %d are here\r\n",
299                                 POP3->num_msgs);
300                         return;
301                 }
302                 else if (POP3->msgs[which_one-1].deleted) {
303                         cprintf("-ERR Sorry, you deleted that message.\r\n");
304                         return;
305                 }
306                 else {
307                         cprintf("+OK %d %ld\r\n",
308                                 which_one,
309                                 (long)POP3->msgs[which_one-1].rfc822_length
310                                 );
311                         return;
312                 }
313         }
314
315         /* "list all" (scan listing) mode */
316         else {
317                 cprintf("+OK Here's your mail:\r\n");
318                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
319                         if (! POP3->msgs[i].deleted) {
320                                 cprintf("%d %ld\r\n",
321                                         i+1,
322                                         (long)POP3->msgs[i].rfc822_length);
323                         }
324                 }
325                 cprintf(".\r\n");
326         }
327 }
328
329
330 /*
331  * STAT (tally up the total message count and byte count) command
332  */
333 void pop3_stat(char *argbuf) {
334         int total_msgs = 0;
335         size_t total_octets = 0;
336         int i;
337         
338         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
339                 if (! POP3->msgs[i].deleted) {
340                         ++total_msgs;
341                         total_octets += POP3->msgs[i].rfc822_length;
342                 }
343         }
344
345         cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
346 }
347
348
349
350 /*
351  * RETR command (fetch a message)
352  */
353 void pop3_retr(char *argbuf) {
354         int which_one;
355
356         which_one = atoi(argbuf);
357         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
358                 cprintf("-ERR No such message.\r\n");
359                 return;
360         }
361
362         if (POP3->msgs[which_one - 1].deleted) {
363                 cprintf("-ERR Sorry, you deleted that message.\r\n");
364                 return;
365         }
366
367         cprintf("+OK Message %d:\r\n", which_one);
368         CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL);
369         cprintf(".\r\n");
370 }
371
372
373 /*
374  * TOP command (dumb way of fetching a partial message or headers-only)
375  */
376 void pop3_top(char *argbuf) {
377         int which_one;
378         int lines_requested = 0;
379         int lines_dumped = 0;
380         char buf[1024];
381         char *msgtext;
382         char *ptr;
383         int in_body = 0;
384         int done = 0;
385
386         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
387         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
388                 cprintf("-ERR No such message.\r\n");
389                 return;
390         }
391
392         if (POP3->msgs[which_one - 1].deleted) {
393                 cprintf("-ERR Sorry, you deleted that message.\r\n");
394                 return;
395         }
396
397         CC->redirect_buffer = malloc(SIZ);
398         CC->redirect_len = 0;
399         CC->redirect_alloc = SIZ;
400         CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
401                         MT_RFC822, HEADERS_ALL, 0, 1, NULL);
402         msgtext = CC->redirect_buffer;
403         CC->redirect_buffer = NULL;
404         CC->redirect_len = 0;
405         CC->redirect_alloc = 0;
406
407         cprintf("+OK Message %d:\r\n", which_one);
408
409         ptr = msgtext;
410
411         while (ptr = memreadline(ptr, buf, (sizeof buf - 2)),
412               ( (*ptr != 0) && (done == 0))) {
413                 strcat(buf, "\r\n");
414                 if (in_body == 1) {
415                         if (lines_dumped >= lines_requested) {
416                                 done = 1;
417                         }
418                 }
419                 if ((in_body == 0) || (done == 0)) {
420                         client_write(buf, strlen(buf));
421                 }
422                 if (in_body) {
423                         ++lines_dumped;
424                 }
425                 if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
426         }
427
428         if (buf[strlen(buf)-1] != 10) cprintf("\n");
429         free(msgtext);
430
431         cprintf(".\r\n");
432 }
433
434
435 /*
436  * DELE (delete message from mailbox)
437  */
438 void pop3_dele(char *argbuf) {
439         int which_one;
440
441         which_one = atoi(argbuf);
442         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
443                 cprintf("-ERR No such message.\r\n");
444                 return;
445         }
446
447         if (POP3->msgs[which_one - 1].deleted) {
448                 cprintf("-ERR You already deleted that message.\r\n");
449                 return;
450         }
451
452         /* Flag the message as deleted.  Will expunge during QUIT command. */
453         POP3->msgs[which_one - 1].deleted = 1;
454         cprintf("+OK Message %d deleted.\r\n",
455                 which_one);
456 }
457
458
459 /* Perform "UPDATE state" stuff
460  */
461 void pop3_update(void) {
462         int i;
463         struct visit vbuf;
464
465         long *deletemsgs = NULL;
466         int num_deletemsgs = 0;
467
468         /* Remove messages marked for deletion */
469         if (POP3->num_msgs > 0) {
470                 deletemsgs = malloc(POP3->num_msgs * sizeof(long));
471                 for (i=0; i<POP3->num_msgs; ++i) {
472                         if (POP3->msgs[i].deleted) {
473                                 deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
474                         }
475                 }
476                 if (num_deletemsgs > 0) {
477                         CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
478                 }
479                 free(deletemsgs);
480         }
481
482         /* Set last read pointer */
483         if (POP3->num_msgs > 0) {
484                 lgetuser(&CC->user, CC->curr_user);
485
486                 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
487                 snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld",
488                         POP3->msgs[POP3->num_msgs-1].msgnum);
489                 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
490
491                 lputuser(&CC->user);
492         }
493
494 }
495
496
497 /* 
498  * RSET (reset, i.e. undelete any deleted messages) command
499  */
500 void pop3_rset(char *argbuf) {
501         int i;
502
503         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
504                 if (POP3->msgs[i].deleted) {
505                         POP3->msgs[i].deleted = 0;
506                 }
507         }
508         cprintf("+OK Reset completed.\r\n");
509 }
510
511
512
513 /* 
514  * LAST (Determine which message is the last unread message)
515  */
516 void pop3_last(char *argbuf) {
517         cprintf("+OK %d\r\n", POP3->lastseen + 1);
518 }
519
520
521 /*
522  * CAPA is a command which tells the client which POP3 extensions
523  * are supported.
524  */
525 void pop3_capa(void) {
526         cprintf("+OK Capability list follows\r\n"
527                 "TOP\r\n"
528                 "USER\r\n"
529                 "UIDL\r\n"
530                 "IMPLEMENTATION %s\r\n"
531                 ".\r\n"
532                 ,
533                 CITADEL
534         );
535 }
536
537
538
539 /*
540  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
541  * identifiers are simply the Citadel message numbers in the database.
542  */
543 void pop3_uidl(char *argbuf) {
544         int i;
545         int which_one;
546
547         which_one = atoi(argbuf);
548
549         /* "list one" mode */
550         if (which_one > 0) {
551                 if (which_one > POP3->num_msgs) {
552                         cprintf("-ERR no such message, only %d are here\r\n",
553                                 POP3->num_msgs);
554                         return;
555                 }
556                 else if (POP3->msgs[which_one-1].deleted) {
557                         cprintf("-ERR Sorry, you deleted that message.\r\n");
558                         return;
559                 }
560                 else {
561                         cprintf("+OK %d %ld\r\n",
562                                 which_one,
563                                 POP3->msgs[which_one-1].msgnum
564                                 );
565                         return;
566                 }
567         }
568
569         /* "list all" (scan listing) mode */
570         else {
571                 cprintf("+OK Here's your mail:\r\n");
572                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
573                         if (! POP3->msgs[i].deleted) {
574                                 cprintf("%d %ld\r\n",
575                                         i+1,
576                                         POP3->msgs[i].msgnum);
577                         }
578                 }
579                 cprintf(".\r\n");
580         }
581 }
582
583
584 /*
585  * implements the STLS command (Citadel API version)
586  */
587 void pop3_stls(void)
588 {
589         char ok_response[SIZ];
590         char nosup_response[SIZ];
591         char error_response[SIZ];
592
593         sprintf(ok_response,
594                 "+OK Begin TLS negotiation now\r\n");
595         sprintf(nosup_response,
596                 "-ERR TLS not supported here\r\n");
597         sprintf(error_response,
598                 "-ERR Internal error\r\n");
599         CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
600 }
601
602
603
604
605
606
607
608 /* 
609  * Main command loop for POP3 sessions.
610  */
611 void pop3_command_loop(void) {
612         char cmdbuf[SIZ];
613
614         time(&CC->lastcmd);
615         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
616         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
617                 lprintf(CTDL_ERR, "Client disconnected: ending session.\r\n");
618                 CC->kill_me = 1;
619                 return;
620         }
621         if (!strncasecmp(cmdbuf, "PASS", 4)) {
622                 lprintf(CTDL_INFO, "POP3: PASS...\r\n");
623         }
624         else {
625                 lprintf(CTDL_INFO, "POP3: %s\r\n", cmdbuf);
626         }
627         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
628
629         if (!strncasecmp(cmdbuf, "NOOP", 4)) {
630                 cprintf("+OK No operation.\r\n");
631         }
632
633         else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
634                 pop3_capa();
635         }
636
637         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
638                 cprintf("+OK Goodbye...\r\n");
639                 pop3_update();
640                 CC->kill_me = 1;
641                 return;
642         }
643
644         else if (!strncasecmp(cmdbuf, "USER", 4)) {
645                 pop3_user(&cmdbuf[5]);
646         }
647
648         else if (!strncasecmp(cmdbuf, "PASS", 4)) {
649                 pop3_pass(&cmdbuf[5]);
650         }
651
652         else if (!strncasecmp(cmdbuf, "APOP", 4))
653         {
654                 pop3_apop(&cmdbuf[5]);
655         }
656
657 #ifdef HAVE_OPENSSL
658         else if (!strncasecmp(cmdbuf, "STLS", 4)) {
659                 pop3_stls();
660         }
661 #endif
662
663         else if (!CC->logged_in) {
664                 cprintf("-ERR Not logged in.\r\n");
665         }
666
667         else if (!strncasecmp(cmdbuf, "LIST", 4)) {
668                 pop3_list(&cmdbuf[5]);
669         }
670
671         else if (!strncasecmp(cmdbuf, "STAT", 4)) {
672                 pop3_stat(&cmdbuf[5]);
673         }
674
675         else if (!strncasecmp(cmdbuf, "RETR", 4)) {
676                 pop3_retr(&cmdbuf[5]);
677         }
678
679         else if (!strncasecmp(cmdbuf, "DELE", 4)) {
680                 pop3_dele(&cmdbuf[5]);
681         }
682
683         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
684                 pop3_rset(&cmdbuf[5]);
685         }
686
687         else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
688                 pop3_uidl(&cmdbuf[5]);
689         }
690
691         else if (!strncasecmp(cmdbuf, "TOP", 3)) {
692                 pop3_top(&cmdbuf[4]);
693         }
694
695         else if (!strncasecmp(cmdbuf, "LAST", 4)) {
696                 pop3_last(&cmdbuf[4]);
697         }
698
699         else {
700                 cprintf("-ERR I'm afraid I can't do that.\r\n");
701         }
702
703 }
704
705
706
707 CTDL_MODULE_INIT(pop3)
708 {
709         CtdlRegisterServiceHook(config.c_pop3_port,
710                                 NULL,
711                                 pop3_greeting,
712                                 pop3_command_loop,
713                                 NULL);
714 #ifdef HAVE_OPENSSL
715         CtdlRegisterServiceHook(config.c_pop3s_port,
716                                 NULL,
717                                 pop3s_greeting,
718                                 pop3_command_loop,
719                                 NULL);
720 #endif
721         CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
722
723         /* return our Subversion id for the Log */
724         return "$Id$";
725 }