* Replaced all "Citadel/UX" references with "Citadel"
[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         int i;
74
75         /* Don't do this stuff if this is not a POP3 session! */
76         if (CC->h_command_function != pop3_command_loop) return;
77
78         lprintf(CTDL_DEBUG, "Performing POP3 cleanup hook\n");
79
80         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
81                 if (POP3->msgs[i].temp != NULL) {
82                         fclose(POP3->msgs[i].temp);
83                         POP3->msgs[i].temp = NULL;
84                 }
85         }
86         if (POP3->msgs != NULL) free(POP3->msgs);
87
88         lprintf(CTDL_DEBUG, "Finished POP3 cleanup hook\n");
89 }
90
91
92
93 /*
94  * Here's where our POP3 session begins its happy day.
95  */
96 void pop3_greeting(void) {
97         strcpy(CC->cs_clientname, "POP3 session");
98         CC->internal_pgm = 1;
99         CtdlAllocUserData(SYM_POP3, sizeof(struct citpop3));
100         POP3->msgs = NULL;
101         POP3->num_msgs = 0;
102
103         cprintf("+OK Citadel POP3 server %s\r\n",
104                 CC->cs_nonce);
105 }
106
107
108 /*
109  * Specify user name (implements POP3 "USER" command)
110  */
111 void pop3_user(char *argbuf) {
112         char username[SIZ];
113
114         if (CC->logged_in) {
115                 cprintf("-ERR You are already logged in.\r\n");
116                 return;
117         }
118
119         strcpy(username, argbuf);
120         striplt(username);
121
122         lprintf(CTDL_DEBUG, "Trying <%s>\n", username);
123         if (CtdlLoginExistingUser(username) == login_ok) {
124                 cprintf("+OK Password required for %s\r\n", username);
125         }
126         else {
127                 cprintf("-ERR No such user.\r\n");
128         }
129 }
130
131
132
133 /*
134  * Back end for pop3_grab_mailbox()
135  */
136 void pop3_add_message(long msgnum, void *userdata) {
137         FILE *fp;
138         lprintf(CTDL_DEBUG, "in pop3_add_message()\n");
139
140         ++POP3->num_msgs;
141         if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg));
142         else POP3->msgs = realloc(POP3->msgs, 
143                 (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
144         POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
145         POP3->msgs[POP3->num_msgs-1].deleted = 0;
146         fp = tmpfile();
147         POP3->msgs[POP3->num_msgs-1].temp = fp;
148
149         CtdlRedirectOutput(fp, -1);
150         CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1);
151         CtdlRedirectOutput(NULL, -1);
152
153         POP3->msgs[POP3->num_msgs-1].rfc822_length = ftell(fp);
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         int ch = 0;
347         size_t bytes_remaining;
348
349         which_one = atoi(argbuf);
350         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
351                 cprintf("-ERR No such message.\r\n");
352                 return;
353         }
354
355         if (POP3->msgs[which_one - 1].deleted) {
356                 cprintf("-ERR Sorry, you deleted that message.\r\n");
357                 return;
358         }
359
360         cprintf("+OK Message %d:\r\n", which_one);
361         bytes_remaining = POP3->msgs[which_one -1].rfc822_length;
362         rewind(POP3->msgs[which_one - 1].temp);
363         while (bytes_remaining-- > 0) {
364                 ch = getc(POP3->msgs[which_one - 1].temp);
365                 cprintf("%c", ch);
366         }
367         if (ch != 10) {
368                 lprintf(CTDL_WARNING, "Problem: message ends with 0x%2x, not 0x0a\n", ch);
369         }
370         cprintf(".\r\n");
371 }
372
373
374 /*
375  * TOP command (dumb way of fetching a partial message or headers-only)
376  */
377 void pop3_top(char *argbuf) {
378         int which_one;
379         int lines_requested = 0;
380         int lines_dumped = 0;
381         char buf[1024];
382         char *ptr;
383         int in_body = 0;
384         int done = 0;
385
386         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
387         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
388                 cprintf("-ERR No such message.\r\n");
389                 return;
390         }
391
392         if (POP3->msgs[which_one - 1].deleted) {
393                 cprintf("-ERR Sorry, you deleted that message.\r\n");
394                 return;
395         }
396
397         cprintf("+OK Message %d:\r\n", which_one);
398         rewind(POP3->msgs[which_one - 1].temp);
399         while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp),
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         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_gets(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 }