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