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