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