fix all the <time.h> vs. <sys/time.h> issues, hopefully
[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
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # if HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #include <sys/wait.h>
47 #include <string.h>
48 #include <limits.h>
49 #include <ctype.h>
50 #include "citadel.h"
51 #include "server.h"
52 #include "sysdep_decls.h"
53 #include "citserver.h"
54 #include "support.h"
55 #include "config.h"
56 #include "dynloader.h"
57 #include "room_ops.h"
58 #include "user_ops.h"
59 #include "policy.h"
60 #include "database.h"
61 #include "msgbase.h"
62 #include "tools.h"
63 #include "internet_addressing.h"
64 #include "serv_pop3.h"
65 #include "md5.h"
66
67 long SYM_POP3;
68
69
70 /*
71  * This cleanup function blows away the temporary memory and files used by
72  * the POP3 server.
73  */
74 void pop3_cleanup_function(void) {
75         int i;
76
77         /* Don't do this stuff if this is not a POP3 session! */
78         if (CC->h_command_function != pop3_command_loop) return;
79
80         lprintf(9, "Performing POP3 cleanup hook\n");
81
82         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
83                 fclose(POP3->msgs[i].temp);
84         }
85         if (POP3->msgs != NULL) phree(POP3->msgs);
86
87         lprintf(9, "Finished POP3 cleanup hook\n");
88 }
89
90
91
92 /*
93  * Here's where our POP3 session begins its happy day.
94  */
95 void pop3_greeting(void) {
96         strcpy(CC->cs_clientname, "POP3 session");
97         CC->internal_pgm = 1;
98         CtdlAllocUserData(SYM_POP3, sizeof(struct citpop3));
99         POP3->msgs = NULL;
100         POP3->num_msgs = 0;
101
102         cprintf("+OK Citadel/UX POP3 server %s\r\n",
103                 CC->cs_nonce, config.c_fqdn);
104 }
105
106
107 /*
108  * Specify user name (implements POP3 "USER" command)
109  */
110 void pop3_user(char *argbuf) {
111         char username[SIZ];
112
113         if (CC->logged_in) {
114                 cprintf("-ERR You are already logged in.\r\n");
115                 return;
116         }
117
118         strcpy(username, argbuf);
119         striplt(username);
120
121         lprintf(9, "Trying <%s>\n", username);
122         if (CtdlLoginExistingUser(username) == login_ok) {
123                 cprintf("+OK Password required for %s\r\n", username);
124         }
125         else {
126                 cprintf("-ERR No such user.\r\n");
127         }
128 }
129
130
131
132 /*
133  * Back end for pop3_grab_mailbox()
134  */
135 void pop3_add_message(long msgnum, void *userdata) {
136         FILE *fp;
137         lprintf(9, "in pop3_add_message()\n");
138
139         ++POP3->num_msgs;
140         if (POP3->num_msgs < 2) POP3->msgs = mallok(sizeof(struct pop3msg));
141         else POP3->msgs = reallok(POP3->msgs, 
142                 (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
143         POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
144         POP3->msgs[POP3->num_msgs-1].deleted = 0;
145         fp = tmpfile();
146         POP3->msgs[POP3->num_msgs-1].temp = fp;
147
148         CtdlRedirectOutput(fp, -1);
149         CtdlOutputMsg(msgnum, MT_RFC822, 0, 0, 1);
150         CtdlRedirectOutput(NULL, -1);
151
152         POP3->msgs[POP3->num_msgs-1].rfc822_length = ftell(fp);
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->quickroom, MAILROOM) != 0) return(-1);
167
168         /* Load up the messages */
169         CtdlForEachMessage(MSGS_ALL, 0L, (-63), NULL, NULL,
170                 pop3_add_message, NULL);
171
172         /* Figure out which are old and which are new */
173         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
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->usersupp.fullname, msgs);
193                 lprintf(9, "POP3 password login successful\n");
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->usersupp, CC->curr_user))
238    {
239         cprintf("-ERR No such user.\r\n");
240         return;
241    }
242    
243    make_apop_string(CC->usersupp.password, CC->cs_nonce, 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(9, "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 %d\r\n",
298                                 which_one,
299                                 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 %d\r\n",
311                                         i+1,
312                                         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 %d\r\n", total_msgs, 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         int ch = 0;
346         size_t bytes_remaining;
347
348         which_one = atoi(argbuf);
349         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
350                 cprintf("-ERR No such message.\r\n");
351                 return;
352         }
353
354         if (POP3->msgs[which_one - 1].deleted) {
355                 cprintf("-ERR Sorry, you deleted that message.\r\n");
356                 return;
357         }
358
359         cprintf("+OK Message %d:\r\n", which_one);
360         bytes_remaining = POP3->msgs[which_one -1].rfc822_length;
361         rewind(POP3->msgs[which_one - 1].temp);
362         while (bytes_remaining-- > 0) {
363                 ch = getc(POP3->msgs[which_one - 1].temp);
364                 cprintf("%c", ch);
365         }
366         if (ch != 10) {
367                 lprintf(5, "Problem: message ends with 0x%2x, not 0x0a\n", ch);
368         }
369         cprintf(".\r\n");
370 }
371
372
373 /*
374  * TOP command (dumb way of fetching a partial message or headers-only)
375  */
376 void pop3_top(char *argbuf) {
377         int which_one;
378         int lines_requested = 0;
379         int lines_dumped = 0;
380         char buf[1024];
381         char *ptr;
382         int in_body = 0;
383         int done = 0;
384
385         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
386         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
387                 cprintf("-ERR No such message.\r\n");
388                 return;
389         }
390
391         if (POP3->msgs[which_one - 1].deleted) {
392                 cprintf("-ERR Sorry, you deleted that message.\r\n");
393                 return;
394         }
395
396         cprintf("+OK Message %d:\r\n", which_one);
397         rewind(POP3->msgs[which_one - 1].temp);
398         while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp),
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         cprintf(".\r\n");
409 }
410
411
412 /*
413  * DELE (delete message from mailbox)
414  */
415 void pop3_dele(char *argbuf) {
416         int which_one;
417
418         which_one = atoi(argbuf);
419         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
420                 cprintf("-ERR No such message.\r\n");
421                 return;
422         }
423
424         if (POP3->msgs[which_one - 1].deleted) {
425                 cprintf("-ERR You already deleted that message.\r\n");
426                 return;
427         }
428
429         /* Flag the message as deleted.  Will expunge during QUIT command. */
430         POP3->msgs[which_one - 1].deleted = 1;
431         cprintf("+OK Message %d deleted.\r\n",
432                 which_one);
433 }
434
435
436 /* Perform "UPDATE state" stuff
437  */
438 void pop3_update(void) {
439         int i;
440         struct visit vbuf;
441
442         /* Remove messages marked for deletion */
443         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
444                 if (POP3->msgs[i].deleted) {
445                         CtdlDeleteMessages(MAILROOM,
446                                 POP3->msgs[i].msgnum, "");
447                 }
448         }
449
450         /* Set last read pointer */
451         if (POP3->num_msgs > 0) {
452                 lgetuser(&CC->usersupp, CC->curr_user);
453
454                 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
455                 sprintf(vbuf.v_seen, "*:%ld",
456                         POP3->msgs[POP3->num_msgs-1].msgnum);
457                 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
458
459                 lputuser(&CC->usersupp);
460         }
461
462 }
463
464
465 /* 
466  * RSET (reset, i.e. undelete any deleted messages) command
467  */
468 void pop3_rset(char *argbuf) {
469         int i;
470
471         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
472                 if (POP3->msgs[i].deleted) {
473                         POP3->msgs[i].deleted = 0;
474                 }
475         }
476         cprintf("+OK Reset completed.\r\n");
477 }
478
479
480
481 /* 
482  * LAST (Determine which message is the last unread message)
483  */
484 void pop3_last(char *argbuf) {
485         cprintf("+OK %d\r\n", POP3->lastseen + 1);
486 }
487
488
489
490 /*
491  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
492  * identifiers are simply the Citadel message numbers in the database.
493  */
494 void pop3_uidl(char *argbuf) {
495         int i;
496         int which_one;
497
498         which_one = atoi(argbuf);
499
500         /* "list one" mode */
501         if (which_one > 0) {
502                 if (which_one > POP3->num_msgs) {
503                         cprintf("-ERR no such message, only %d are here\r\n",
504                                 POP3->num_msgs);
505                         return;
506                 }
507                 else if (POP3->msgs[which_one-1].deleted) {
508                         cprintf("-ERR Sorry, you deleted that message.\r\n");
509                         return;
510                 }
511                 else {
512                         cprintf("+OK %d %ld\r\n",
513                                 which_one,
514                                 POP3->msgs[which_one-1].msgnum
515                                 );
516                         return;
517                 }
518         }
519
520         /* "list all" (scan listing) mode */
521         else {
522                 cprintf("+OK Here's your mail:\r\n");
523                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
524                         if (! POP3->msgs[i].deleted) {
525                                 cprintf("%d %ld\r\n",
526                                         i+1,
527                                         POP3->msgs[i].msgnum);
528                         }
529                 }
530                 cprintf(".\r\n");
531         }
532 }
533
534
535
536
537 /* 
538  * Main command loop for POP3 sessions.
539  */
540 void pop3_command_loop(void) {
541         char cmdbuf[SIZ];
542
543         time(&CC->lastcmd);
544         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
545         if (client_gets(cmdbuf) < 1) {
546                 lprintf(3, "POP3 socket is broken.  Ending session.\r\n");
547                 CC->kill_me = 1;
548                 return;
549         }
550         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
551         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
552
553         if (!strncasecmp(cmdbuf, "NOOP", 4)) {
554                 cprintf("+OK No operation.\r\n");
555         }
556
557         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
558                 cprintf("+OK Goodbye...\r\n");
559                 pop3_update();
560                 CC->kill_me = 1;
561                 return;
562         }
563
564         else if (!strncasecmp(cmdbuf, "USER", 4)) {
565                 pop3_user(&cmdbuf[5]);
566         }
567
568         else if (!strncasecmp(cmdbuf, "PASS", 4)) {
569                 pop3_pass(&cmdbuf[5]);
570         }
571
572         else if (!strncasecmp(cmdbuf, "APOP", 4))
573         {
574                 pop3_apop(&cmdbuf[5]);
575         }
576
577         else if (!CC->logged_in) {
578                 cprintf("-ERR Not logged in.\r\n");
579         }
580
581         else if (!strncasecmp(cmdbuf, "LIST", 4)) {
582                 pop3_list(&cmdbuf[5]);
583         }
584
585         else if (!strncasecmp(cmdbuf, "STAT", 4)) {
586                 pop3_stat(&cmdbuf[5]);
587         }
588
589         else if (!strncasecmp(cmdbuf, "RETR", 4)) {
590                 pop3_retr(&cmdbuf[5]);
591         }
592
593         else if (!strncasecmp(cmdbuf, "DELE", 4)) {
594                 pop3_dele(&cmdbuf[5]);
595         }
596
597         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
598                 pop3_rset(&cmdbuf[5]);
599         }
600
601         else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
602                 pop3_uidl(&cmdbuf[5]);
603         }
604
605         else if (!strncasecmp(cmdbuf, "TOP", 3)) {
606                 pop3_top(&cmdbuf[4]);
607         }
608
609         else if (!strncasecmp(cmdbuf, "LAST", 4)) {
610                 pop3_last(&cmdbuf[4]);
611         }
612
613         else {
614                 cprintf("-ERR I'm afraid I can't do that.\r\n");
615         }
616
617 }
618
619
620
621 char *Dynamic_Module_Init(void)
622 {
623         SYM_POP3 = CtdlGetDynamicSymbol();
624         CtdlRegisterServiceHook(config.c_pop3_port,
625                                 NULL,
626                                 pop3_greeting,
627                                 pop3_command_loop);
628         CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
629         return "$Id$";
630 }