b8273f89716aa1d7599fcb41f3883a1e40922add
[citadel.git] / citadel / serv_pop3.c
1 /*
2  * $Id$ 
3  *
4  * POP3 server for the Citadel/UX system
5  * Copyright (C) 1998-2000 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  * -> Nearly all of the optional commands in RFC1939 are also implemented.
13  *    The only one missing is APOP, because it implements a "shared secret"
14  *    method  of authentication which would require some major changes to the
15  *    Citadel server core.
16  *
17  *    This is no longer true- APOP is implemented.
18  *
19  * -> The deprecated "LAST" command is included in this implementation, because
20  *    there exist mail clients which insist on using it (such as Bynari
21  *    TradeMail, and certain versions of Eudora).
22  * 
23  */
24
25 #include "sysdep.h"
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <pwd.h>
32 #include <errno.h>
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <sys/wait.h>
36 #include <string.h>
37 #include <limits.h>
38 #include <ctype.h>
39 #include "citadel.h"
40 #include "server.h"
41 #include <time.h>
42 #include "sysdep_decls.h"
43 #include "citserver.h"
44 #include "support.h"
45 #include "config.h"
46 #include "dynloader.h"
47 #include "room_ops.h"
48 #include "user_ops.h"
49 #include "policy.h"
50 #include "database.h"
51 #include "msgbase.h"
52 #include "tools.h"
53 #include "internet_addressing.h"
54 #include "serv_pop3.h"
55 #include "md5.h"
56
57 long SYM_POP3;
58
59
60 /*
61  * This cleanup function blows away the temporary memory and files used by
62  * the POP3 server.
63  */
64 void pop3_cleanup_function(void) {
65         int i;
66
67         /* Don't do this stuff if this is not a POP3 session! */
68         if (CC->h_command_function != pop3_command_loop) return;
69
70         lprintf(9, "Performing POP3 cleanup hook\n");
71
72         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
73                 fclose(POP3->msgs[i].temp);
74         }
75         if (POP3->msgs != NULL) phree(POP3->msgs);
76
77         lprintf(9, "Finished POP3 cleanup hook\n");
78 }
79
80
81
82 /*
83  * Here's where our POP3 session begins its happy day.
84  */
85 void pop3_greeting(void) {
86         strcpy(CC->cs_clientname, "POP3 session");
87         CC->internal_pgm = 1;
88         CtdlAllocUserData(SYM_POP3, sizeof(struct citpop3));
89         POP3->msgs = NULL;
90         POP3->num_msgs = 0;
91
92         cprintf("+OK Welcome to the Citadel/UX POP3 server %s\r\n",
93                 CC->cs_nonce, config.c_fqdn);
94 }
95
96
97 /*
98  * Specify user name (implements POP3 "USER" command)
99  */
100 void pop3_user(char *argbuf) {
101         char username[256];
102
103         if (CC->logged_in) {
104                 cprintf("-ERR You are already logged in.\r\n");
105                 return;
106         }
107
108         strcpy(username, argbuf);
109         striplt(username);
110
111         lprintf(9, "Trying <%s>\n", username);
112         if (CtdlLoginExistingUser(username) == login_ok) {
113                 cprintf("+OK Password required for %s\r\n", username);
114         }
115         else {
116                 cprintf("-ERR No such user.\r\n");
117         }
118 }
119
120
121
122 /*
123  * Back end for pop3_grab_mailbox()
124  */
125 void pop3_add_message(long msgnum, void *userdata) {
126         FILE *fp;
127         lprintf(9, "in pop3_add_message()\n");
128
129         ++POP3->num_msgs;
130         if (POP3->num_msgs < 2) POP3->msgs = mallok(sizeof(struct pop3msg));
131         else POP3->msgs = reallok(POP3->msgs, 
132                 (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
133         POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
134         POP3->msgs[POP3->num_msgs-1].deleted = 0;
135         fp = tmpfile();
136         POP3->msgs[POP3->num_msgs-1].temp = fp;
137
138         CtdlRedirectOutput(fp, -1);
139         CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
140         CtdlRedirectOutput(NULL, -1);
141
142         POP3->msgs[POP3->num_msgs-1].rfc822_length = ftell(fp);
143 }
144
145
146
147 /*
148  * Open the inbox and read its contents.
149  * (This should be called only once, by pop3_pass(), and returns the number
150  * of messages in the inbox, or -1 for error)
151  */
152 int pop3_grab_mailbox(void) {
153         struct visit vbuf;
154         int i;
155
156         if (getroom(&CC->quickroom, MAILROOM) != 0) return(-1);
157
158         /* Load up the messages */
159         CtdlForEachMessage(MSGS_ALL, 0L, (-63), NULL, NULL,
160                 pop3_add_message, NULL);
161
162         /* Figure out which are old and which are new */
163         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
164         POP3->lastseen = (-1);
165         if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
166                 if ((POP3->msgs[POP3->num_msgs-1].msgnum) <= vbuf.v_lastseen) {
167                         POP3->lastseen = i;
168                 }
169         }
170
171         return(POP3->num_msgs);
172 }
173
174 void pop3_login(void)
175 {
176         int msgs;
177         
178         msgs = pop3_grab_mailbox();
179         if (msgs >= 0) {
180                 cprintf("+OK %s is logged in (%d messages)\r\n",
181                         CC->usersupp.fullname, msgs);
182                 lprintf(9, "POP3 password login successful\n");
183         }
184         else {
185                 cprintf("-ERR Can't open your mailbox\r\n");
186         }
187         
188 }
189
190 void pop3_apop(char *argbuf)
191 {
192    char username[256];
193    char userdigest[MD5_HEXSTRING_SIZE];
194    char realdigest[MD5_HEXSTRING_SIZE];
195    char *sptr;
196    
197    if (CC->logged_in)
198    {
199         cprintf("-ERR You are already logged in; not in the AUTHORIZATION phase.\r\n");
200         return;
201    }
202    
203    if ((sptr = strchr(argbuf, ' ')) == NULL)
204    {
205         cprintf("Invalid APOP line.\r\n");
206         return;
207    }
208    
209    *sptr++ = '\0';
210    
211    while ((*sptr) && isspace(*sptr))
212       sptr++;
213    
214    strncpy(username, argbuf, sizeof(username)-1);
215    username[sizeof(username)-1] = '\0';
216    
217    memset(userdigest, MD5_HEXSTRING_SIZE, 0);
218    strncpy(userdigest, sptr, MD5_HEXSTRING_SIZE-1);
219    
220    if (CtdlLoginExistingUser(username) != login_ok)
221    {
222         cprintf("-ERR No such user.\r\n");
223         return;
224    }
225    
226    if (getuser(&CC->usersupp, CC->curr_user))
227    {
228         cprintf("-ERR No such user.\r\n");
229         return;
230    }
231    
232    make_apop_string(CC->usersupp.password, CC->cs_nonce, realdigest);
233    if (!strncasecmp(realdigest, userdigest, MD5_HEXSTRING_SIZE-1))
234    {
235         pop3_login();
236    }
237    else
238    {
239         cprintf("-ERR That is NOT the password!  Go away!\r\n");
240    }
241 }
242
243
244 /*
245  * Authorize with password (implements POP3 "PASS" command)
246  */
247 void pop3_pass(char *argbuf) {
248         char password[256];
249
250         strcpy(password, argbuf);
251         striplt(password);
252
253         lprintf(9, "Trying <%s>\n", password);
254         if (CtdlTryPassword(password) == pass_ok) {
255                 pop3_login();
256         }
257         else {
258                 cprintf("-ERR That is NOT the password!  Go away!\r\n");
259         }
260 }
261
262
263
264 /*
265  * list available msgs
266  */
267 void pop3_list(char *argbuf) {
268         int i;
269         int which_one;
270
271         which_one = atoi(argbuf);
272
273         /* "list one" mode */
274         if (which_one > 0) {
275                 if (which_one > POP3->num_msgs) {
276                         cprintf("-ERR no such message, only %d are here\r\n",
277                                 POP3->num_msgs);
278                         return;
279                 }
280                 else if (POP3->msgs[which_one-1].deleted) {
281                         cprintf("-ERR Sorry, you deleted that message.\r\n");
282                         return;
283                 }
284                 else {
285                         cprintf("+OK %d %d\n",
286                                 which_one,
287                                 POP3->msgs[which_one-1].rfc822_length
288                                 );
289                         return;
290                 }
291         }
292
293         /* "list all" (scan listing) mode */
294         else {
295                 cprintf("+OK Here's your mail:\r\n");
296                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
297                         if (! POP3->msgs[i].deleted) {
298                                 cprintf("%d %d\r\n",
299                                         i+1,
300                                         POP3->msgs[i].rfc822_length);
301                         }
302                 }
303                 cprintf(".\r\n");
304         }
305 }
306
307
308 /*
309  * STAT (tally up the total message count and byte count) command
310  */
311 void pop3_stat(char *argbuf) {
312         int total_msgs = 0;
313         size_t total_octets = 0;
314         int i;
315         
316         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
317                 if (! POP3->msgs[i].deleted) {
318                         ++total_msgs;
319                         total_octets += POP3->msgs[i].rfc822_length;
320                 }
321         }
322
323         cprintf("+OK %d %d\n", total_msgs, total_octets);
324 }
325
326
327
328 /*
329  * RETR command (fetch a message)
330  */
331 void pop3_retr(char *argbuf) {
332         int which_one;
333         int ch = 0;
334         size_t bytes_remaining;
335
336         which_one = atoi(argbuf);
337         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
338                 cprintf("-ERR No such message.\r\n");
339                 return;
340         }
341
342         if (POP3->msgs[which_one - 1].deleted) {
343                 cprintf("-ERR Sorry, you deleted that message.\r\n");
344                 return;
345         }
346
347         cprintf("+OK Whoop, there it is:\r\n");
348         bytes_remaining = POP3->msgs[which_one -1].rfc822_length;
349         rewind(POP3->msgs[which_one - 1].temp);
350         while (bytes_remaining-- > 0) {
351                 ch = getc(POP3->msgs[which_one - 1].temp);
352                 cprintf("%c", ch);
353         }
354         if (ch != 10) {
355                 lprintf(5, "Problem: message ends with 0x%2x, not 0x0a\n", ch);
356         }
357         cprintf(".\r\n");
358 }
359
360
361 /*
362  * TOP command (dumb way of fetching a partial message or headers-only)
363  */
364 void pop3_top(char *argbuf) {
365         int which_one;
366         int lines_requested = 0;
367         int lines_dumped = 0;
368         char buf[1024];
369         char *ptr;
370         int in_body = 0;
371         int done = 0;
372
373         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
374         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
375                 cprintf("-ERR No such message.\r\n");
376                 return;
377         }
378
379         if (POP3->msgs[which_one - 1].deleted) {
380                 cprintf("-ERR Sorry, you deleted that message.\r\n");
381                 return;
382         }
383
384         cprintf("+OK Whoop, there it is:\r\n");
385         rewind(POP3->msgs[which_one - 1].temp);
386         while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp),
387               ( (ptr!=NULL) && (done == 0))) {
388                 if (in_body == 1)
389                         if (lines_dumped >= lines_requested) done = 1;
390                 if ((in_body == 0) || (done == 0))
391                         client_write(buf, strlen(buf));
392                 if (in_body) ++lines_dumped;
393                 if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
394         }
395         if (buf[strlen(buf)-1] != 10) cprintf("\n");
396         cprintf(".\r\n");
397 }
398
399
400 /*
401  * DELE (delete message from mailbox)
402  */
403 void pop3_dele(char *argbuf) {
404         int which_one;
405
406         which_one = atoi(argbuf);
407         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
408                 cprintf("-ERR No such message.\r\n");
409                 return;
410         }
411
412         if (POP3->msgs[which_one - 1].deleted) {
413                 cprintf("-ERR You already deleted that message.\r\n");
414                 return;
415         }
416
417         /* Flag the message as deleted.  Will expunge during QUIT command. */
418         POP3->msgs[which_one - 1].deleted = 1;
419         cprintf("+OK Message %d disappears in a cloud of orange smoke.\r\n",
420                 which_one);
421 }
422
423
424 /* Perform "UPDATE state" stuff
425  */
426 void pop3_update(void) {
427         int i;
428         struct visit vbuf;
429
430         /* Remove messages marked for deletion */
431         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
432                 if (POP3->msgs[i].deleted) {
433                         CtdlDeleteMessages(MAILROOM,
434                                 POP3->msgs[i].msgnum, "");
435                 }
436         }
437
438         /* Set last read pointer */
439         if (POP3->num_msgs > 0) {
440                 lgetuser(&CC->usersupp, CC->curr_user);
441
442                 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
443                 vbuf.v_lastseen = POP3->msgs[POP3->num_msgs-1].msgnum;
444                 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
445
446                 lputuser(&CC->usersupp);
447         }
448
449 }
450
451
452 /* 
453  * RSET (reset, i.e. undelete any deleted messages) command
454  */
455 void pop3_rset(char *argbuf) {
456         int i;
457
458         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
459                 if (POP3->msgs[i].deleted) {
460                         POP3->msgs[i].deleted = 0;
461                 }
462         }
463         cprintf("+OK all that has come to pass, has now gone away.\r\n");
464 }
465
466
467
468 /* 
469  * LAST (Determine which message is the last unread message)
470  */
471 void pop3_last(char *argbuf) {
472         cprintf("+OK %d\r\n", POP3->lastseen + 1);
473 }
474
475
476
477 /*
478  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
479  * identifiers are simply the Citadel message numbers in the database.
480  */
481 void pop3_uidl(char *argbuf) {
482         int i;
483         int which_one;
484
485         which_one = atoi(argbuf);
486
487         /* "list one" mode */
488         if (which_one > 0) {
489                 if (which_one > POP3->num_msgs) {
490                         cprintf("-ERR no such message, only %d are here\r\n",
491                                 POP3->num_msgs);
492                         return;
493                 }
494                 else if (POP3->msgs[which_one-1].deleted) {
495                         cprintf("-ERR Sorry, you deleted that message.\r\n");
496                         return;
497                 }
498                 else {
499                         cprintf("+OK %d %ld\n",
500                                 which_one,
501                                 POP3->msgs[which_one-1].msgnum
502                                 );
503                         return;
504                 }
505         }
506
507         /* "list all" (scan listing) mode */
508         else {
509                 cprintf("+OK Here's your mail:\r\n");
510                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
511                         if (! POP3->msgs[i].deleted) {
512                                 cprintf("%d %ld\r\n",
513                                         i+1,
514                                         POP3->msgs[i].msgnum);
515                         }
516                 }
517                 cprintf(".\r\n");
518         }
519 }
520
521
522
523
524 /* 
525  * Main command loop for POP3 sessions.
526  */
527 void pop3_command_loop(void) {
528         char cmdbuf[256];
529
530         time(&CC->lastcmd);
531         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
532         if (client_gets(cmdbuf) < 1) {
533                 lprintf(3, "POP3 socket is broken.  Ending session.\r\n");
534                 CC->kill_me = 1;
535                 return;
536         }
537         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
538         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
539
540         if (!strncasecmp(cmdbuf, "NOOP", 4)) {
541                 cprintf("+OK This command successfully did nothing.\r\n");
542         }
543
544         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
545                 cprintf("+OK Goodbye...\r\n");
546                 pop3_update();
547                 CC->kill_me = 1;
548                 return;
549         }
550
551         else if (!strncasecmp(cmdbuf, "USER", 4)) {
552                 pop3_user(&cmdbuf[5]);
553         }
554
555         else if (!strncasecmp(cmdbuf, "PASS", 4)) {
556                 pop3_pass(&cmdbuf[5]);
557         }
558
559         else if (!strncasecmp(cmdbuf, "APOP", 4))
560         {
561                 pop3_apop(&cmdbuf[5]);
562         }
563
564         else if (!CC->logged_in) {
565                 cprintf("-ERR Not logged in.\r\n");
566         }
567
568         else if (!strncasecmp(cmdbuf, "LIST", 4)) {
569                 pop3_list(&cmdbuf[5]);
570         }
571
572         else if (!strncasecmp(cmdbuf, "STAT", 4)) {
573                 pop3_stat(&cmdbuf[5]);
574         }
575
576         else if (!strncasecmp(cmdbuf, "RETR", 4)) {
577                 pop3_retr(&cmdbuf[5]);
578         }
579
580         else if (!strncasecmp(cmdbuf, "DELE", 4)) {
581                 pop3_dele(&cmdbuf[5]);
582         }
583
584         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
585                 pop3_rset(&cmdbuf[5]);
586         }
587
588         else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
589                 pop3_uidl(&cmdbuf[5]);
590         }
591
592         else if (!strncasecmp(cmdbuf, "TOP", 3)) {
593                 pop3_top(&cmdbuf[4]);
594         }
595
596         else if (!strncasecmp(cmdbuf, "LAST", 4)) {
597                 pop3_last(&cmdbuf[4]);
598         }
599
600         else {
601                 cprintf("500 I'm afraid I can't do that, Dave.\r\n");
602         }
603
604 }
605
606
607
608 char *Dynamic_Module_Init(void)
609 {
610         SYM_POP3 = CtdlGetDynamicSymbol();
611         CtdlRegisterServiceHook(config.c_pop3_port,
612                                 NULL,
613                                 pop3_greeting,
614                                 pop3_command_loop);
615         CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
616         return "$Id$";
617 }