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