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