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