]> code.citadel.org Git - citadel.git/blob - citadel/serv_pop3.c
* serv_pop3.c: Fixed APOP. Now logs in properly. Also cleaned up some non-
[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 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[SIZ];
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[SIZ];
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("-ERR 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         do_login();
236         pop3_login();
237    }
238    else
239    {
240         cprintf("-ERR That is NOT the password.\r\n");
241    }
242 }
243
244
245 /*
246  * Authorize with password (implements POP3 "PASS" command)
247  */
248 void pop3_pass(char *argbuf) {
249         char password[SIZ];
250
251         strcpy(password, argbuf);
252         striplt(password);
253
254         lprintf(9, "Trying <%s>\n", password);
255         if (CtdlTryPassword(password) == pass_ok) {
256                 pop3_login();
257         }
258         else {
259                 cprintf("-ERR That is NOT the password.\r\n");
260         }
261 }
262
263
264
265 /*
266  * list available msgs
267  */
268 void pop3_list(char *argbuf) {
269         int i;
270         int which_one;
271
272         which_one = atoi(argbuf);
273
274         /* "list one" mode */
275         if (which_one > 0) {
276                 if (which_one > POP3->num_msgs) {
277                         cprintf("-ERR no such message, only %d are here\r\n",
278                                 POP3->num_msgs);
279                         return;
280                 }
281                 else if (POP3->msgs[which_one-1].deleted) {
282                         cprintf("-ERR Sorry, you deleted that message.\r\n");
283                         return;
284                 }
285                 else {
286                         cprintf("+OK %d %d\r\n",
287                                 which_one,
288                                 POP3->msgs[which_one-1].rfc822_length
289                                 );
290                         return;
291                 }
292         }
293
294         /* "list all" (scan listing) mode */
295         else {
296                 cprintf("+OK Here's your mail:\r\n");
297                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
298                         if (! POP3->msgs[i].deleted) {
299                                 cprintf("%d %d\r\n",
300                                         i+1,
301                                         POP3->msgs[i].rfc822_length);
302                         }
303                 }
304                 cprintf(".\r\n");
305         }
306 }
307
308
309 /*
310  * STAT (tally up the total message count and byte count) command
311  */
312 void pop3_stat(char *argbuf) {
313         int total_msgs = 0;
314         size_t total_octets = 0;
315         int i;
316         
317         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
318                 if (! POP3->msgs[i].deleted) {
319                         ++total_msgs;
320                         total_octets += POP3->msgs[i].rfc822_length;
321                 }
322         }
323
324         cprintf("+OK %d %d\r\n", total_msgs, total_octets);
325 }
326
327
328
329 /*
330  * RETR command (fetch a message)
331  */
332 void pop3_retr(char *argbuf) {
333         int which_one;
334         int ch = 0;
335         size_t bytes_remaining;
336
337         which_one = atoi(argbuf);
338         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
339                 cprintf("-ERR No such message.\r\n");
340                 return;
341         }
342
343         if (POP3->msgs[which_one - 1].deleted) {
344                 cprintf("-ERR Sorry, you deleted that message.\r\n");
345                 return;
346         }
347
348         cprintf("+OK Message %d:\r\n", which_one);
349         bytes_remaining = POP3->msgs[which_one -1].rfc822_length;
350         rewind(POP3->msgs[which_one - 1].temp);
351         while (bytes_remaining-- > 0) {
352                 ch = getc(POP3->msgs[which_one - 1].temp);
353                 cprintf("%c", ch);
354         }
355         if (ch != 10) {
356                 lprintf(5, "Problem: message ends with 0x%2x, not 0x0a\n", ch);
357         }
358         cprintf(".\r\n");
359 }
360
361
362 /*
363  * TOP command (dumb way of fetching a partial message or headers-only)
364  */
365 void pop3_top(char *argbuf) {
366         int which_one;
367         int lines_requested = 0;
368         int lines_dumped = 0;
369         char buf[1024];
370         char *ptr;
371         int in_body = 0;
372         int done = 0;
373
374         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
375         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
376                 cprintf("-ERR No such message.\r\n");
377                 return;
378         }
379
380         if (POP3->msgs[which_one - 1].deleted) {
381                 cprintf("-ERR Sorry, you deleted that message.\r\n");
382                 return;
383         }
384
385         cprintf("+OK Message %d:\r\n", which_one);
386         rewind(POP3->msgs[which_one - 1].temp);
387         while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp),
388               ( (ptr!=NULL) && (done == 0))) {
389                 if (in_body == 1)
390                         if (lines_dumped >= lines_requested) done = 1;
391                 if ((in_body == 0) || (done == 0))
392                         client_write(buf, strlen(buf));
393                 if (in_body) ++lines_dumped;
394                 if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
395         }
396         if (buf[strlen(buf)-1] != 10) cprintf("\n");
397         cprintf(".\r\n");
398 }
399
400
401 /*
402  * DELE (delete message from mailbox)
403  */
404 void pop3_dele(char *argbuf) {
405         int which_one;
406
407         which_one = atoi(argbuf);
408         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
409                 cprintf("-ERR No such message.\r\n");
410                 return;
411         }
412
413         if (POP3->msgs[which_one - 1].deleted) {
414                 cprintf("-ERR You already deleted that message.\r\n");
415                 return;
416         }
417
418         /* Flag the message as deleted.  Will expunge during QUIT command. */
419         POP3->msgs[which_one - 1].deleted = 1;
420         cprintf("+OK Message %d deleted.\r\n",
421                 which_one);
422 }
423
424
425 /* Perform "UPDATE state" stuff
426  */
427 void pop3_update(void) {
428         int i;
429         struct visit vbuf;
430
431         /* Remove messages marked for deletion */
432         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
433                 if (POP3->msgs[i].deleted) {
434                         CtdlDeleteMessages(MAILROOM,
435                                 POP3->msgs[i].msgnum, "");
436                 }
437         }
438
439         /* Set last read pointer */
440         if (POP3->num_msgs > 0) {
441                 lgetuser(&CC->usersupp, CC->curr_user);
442
443                 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
444                 vbuf.v_lastseen = POP3->msgs[POP3->num_msgs-1].msgnum;
445                 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
446
447                 lputuser(&CC->usersupp);
448         }
449
450 }
451
452
453 /* 
454  * RSET (reset, i.e. undelete any deleted messages) command
455  */
456 void pop3_rset(char *argbuf) {
457         int i;
458
459         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
460                 if (POP3->msgs[i].deleted) {
461                         POP3->msgs[i].deleted = 0;
462                 }
463         }
464         cprintf("+OK Reset completed.\r\n");
465 }
466
467
468
469 /* 
470  * LAST (Determine which message is the last unread message)
471  */
472 void pop3_last(char *argbuf) {
473         cprintf("+OK %d\r\n", POP3->lastseen + 1);
474 }
475
476
477
478 /*
479  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
480  * identifiers are simply the Citadel message numbers in the database.
481  */
482 void pop3_uidl(char *argbuf) {
483         int i;
484         int which_one;
485
486         which_one = atoi(argbuf);
487
488         /* "list one" mode */
489         if (which_one > 0) {
490                 if (which_one > POP3->num_msgs) {
491                         cprintf("-ERR no such message, only %d are here\r\n",
492                                 POP3->num_msgs);
493                         return;
494                 }
495                 else if (POP3->msgs[which_one-1].deleted) {
496                         cprintf("-ERR Sorry, you deleted that message.\r\n");
497                         return;
498                 }
499                 else {
500                         cprintf("+OK %d %ld\r\n",
501                                 which_one,
502                                 POP3->msgs[which_one-1].msgnum
503                                 );
504                         return;
505                 }
506         }
507
508         /* "list all" (scan listing) mode */
509         else {
510                 cprintf("+OK Here's your mail:\r\n");
511                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
512                         if (! POP3->msgs[i].deleted) {
513                                 cprintf("%d %ld\r\n",
514                                         i+1,
515                                         POP3->msgs[i].msgnum);
516                         }
517                 }
518                 cprintf(".\r\n");
519         }
520 }
521
522
523
524
525 /* 
526  * Main command loop for POP3 sessions.
527  */
528 void pop3_command_loop(void) {
529         char cmdbuf[SIZ];
530
531         time(&CC->lastcmd);
532         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
533         if (client_gets(cmdbuf) < 1) {
534                 lprintf(3, "POP3 socket is broken.  Ending session.\r\n");
535                 CC->kill_me = 1;
536                 return;
537         }
538         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
539         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
540
541         if (!strncasecmp(cmdbuf, "NOOP", 4)) {
542                 cprintf("+OK No operation.\r\n");
543         }
544
545         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
546                 cprintf("+OK Goodbye...\r\n");
547                 pop3_update();
548                 CC->kill_me = 1;
549                 return;
550         }
551
552         else if (!strncasecmp(cmdbuf, "USER", 4)) {
553                 pop3_user(&cmdbuf[5]);
554         }
555
556         else if (!strncasecmp(cmdbuf, "PASS", 4)) {
557                 pop3_pass(&cmdbuf[5]);
558         }
559
560         else if (!strncasecmp(cmdbuf, "APOP", 4))
561         {
562                 pop3_apop(&cmdbuf[5]);
563         }
564
565         else if (!CC->logged_in) {
566                 cprintf("-ERR Not logged in.\r\n");
567         }
568
569         else if (!strncasecmp(cmdbuf, "LIST", 4)) {
570                 pop3_list(&cmdbuf[5]);
571         }
572
573         else if (!strncasecmp(cmdbuf, "STAT", 4)) {
574                 pop3_stat(&cmdbuf[5]);
575         }
576
577         else if (!strncasecmp(cmdbuf, "RETR", 4)) {
578                 pop3_retr(&cmdbuf[5]);
579         }
580
581         else if (!strncasecmp(cmdbuf, "DELE", 4)) {
582                 pop3_dele(&cmdbuf[5]);
583         }
584
585         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
586                 pop3_rset(&cmdbuf[5]);
587         }
588
589         else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
590                 pop3_uidl(&cmdbuf[5]);
591         }
592
593         else if (!strncasecmp(cmdbuf, "TOP", 3)) {
594                 pop3_top(&cmdbuf[4]);
595         }
596
597         else if (!strncasecmp(cmdbuf, "LAST", 4)) {
598                 pop3_last(&cmdbuf[4]);
599         }
600
601         else {
602                 cprintf("-ERR I'm afraid I can't do that.\r\n");
603         }
604
605 }
606
607
608
609 char *Dynamic_Module_Init(void)
610 {
611         SYM_POP3 = CtdlGetDynamicSymbol();
612         CtdlRegisterServiceHook(config.c_pop3_port,
613                                 NULL,
614                                 pop3_greeting,
615                                 pop3_command_loop);
616         CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
617         return "$Id$";
618 }