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