]> code.citadel.org Git - citadel.git/blob - citadel/serv_pop3.c
* Added [idle] to client wholist display for sessions idle >15 minutes
[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, void *userdata) {
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,
158                 pop3_add_message, NULL);
159
160         /* Figure out which are old and which are new */
161         CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
162         POP3->lastseen = (-1);
163         if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
164                 if ((POP3->msgs[POP3->num_msgs-1].msgnum) <= vbuf.v_lastseen) {
165                         POP3->lastseen = i;
166                 }
167         }
168
169         return(POP3->num_msgs);
170 }
171
172 /*
173  * Authorize with password (implements POP3 "PASS" command)
174  */
175 void pop3_pass(char *argbuf) {
176         char password[256];
177         int msgs;
178
179         strcpy(password, argbuf);
180         striplt(password);
181
182         lprintf(9, "Trying <%s>\n", password);
183         if (CtdlTryPassword(password) == pass_ok) {
184                 msgs = pop3_grab_mailbox();
185                 if (msgs >= 0) {
186                         cprintf("+OK %s is logged in (%d messages)\r\n",
187                                 CC->usersupp.fullname, msgs);
188                         lprintf(9, "POP3 password login successful\n");
189                 }
190                 else {
191                         cprintf("-ERR Can't open your mailbox\r\n");
192                 }
193         }
194         else {
195                 cprintf("-ERR That is NOT the password!  Go away!\r\n");
196         }
197 }
198
199
200
201 /*
202  * list available msgs
203  */
204 void pop3_list(char *argbuf) {
205         int i;
206         int which_one;
207
208         which_one = atoi(argbuf);
209
210         /* "list one" mode */
211         if (which_one > 0) {
212                 if (which_one > POP3->num_msgs) {
213                         cprintf("-ERR no such message, only %d are here\r\n",
214                                 POP3->num_msgs);
215                         return;
216                 }
217                 else if (POP3->msgs[which_one-1].deleted) {
218                         cprintf("-ERR Sorry, you deleted that message.\r\n");
219                         return;
220                 }
221                 else {
222                         cprintf("+OK %d %d\n",
223                                 which_one,
224                                 POP3->msgs[which_one-1].rfc822_length
225                                 );
226                         return;
227                 }
228         }
229
230         /* "list all" (scan listing) mode */
231         else {
232                 cprintf("+OK Here's your mail:\r\n");
233                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
234                         if (! POP3->msgs[i].deleted) {
235                                 cprintf("%d %d\r\n",
236                                         i+1,
237                                         POP3->msgs[i].rfc822_length);
238                         }
239                 }
240                 cprintf(".\r\n");
241         }
242 }
243
244
245 /*
246  * STAT (tally up the total message count and byte count) command
247  */
248 void pop3_stat(char *argbuf) {
249         int total_msgs = 0;
250         size_t total_octets = 0;
251         int i;
252         
253         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
254                 if (! POP3->msgs[i].deleted) {
255                         ++total_msgs;
256                         total_octets += POP3->msgs[i].rfc822_length;
257                 }
258         }
259
260         cprintf("+OK %d %d\n", total_msgs, total_octets);
261 }
262
263
264
265 /*
266  * RETR command (fetch a message)
267  */
268 void pop3_retr(char *argbuf) {
269         int which_one;
270         int ch = 0;
271         size_t bytes_remaining;
272
273         which_one = atoi(argbuf);
274         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
275                 cprintf("-ERR No such message.\r\n");
276                 return;
277         }
278
279         if (POP3->msgs[which_one - 1].deleted) {
280                 cprintf("-ERR Sorry, you deleted that message.\r\n");
281                 return;
282         }
283
284         cprintf("+OK Whoop, there it is:\r\n");
285         bytes_remaining = POP3->msgs[which_one -1].rfc822_length;
286         rewind(POP3->msgs[which_one - 1].temp);
287         while (bytes_remaining-- > 0) {
288                 ch = getc(POP3->msgs[which_one - 1].temp);
289                 cprintf("%c", ch);
290         }
291         if (ch != 10) {
292                 lprintf(5, "Problem: message ends with 0x%2x, not 0x0a\n", ch);
293         }
294         cprintf(".\r\n");
295 }
296
297
298 /*
299  * TOP command (dumb way of fetching a partial message or headers-only)
300  */
301 void pop3_top(char *argbuf) {
302         int which_one;
303         int lines_requested = 0;
304         int lines_dumped = 0;
305         char buf[1024];
306         char *ptr;
307         int in_body = 0;
308         int done = 0;
309
310         sscanf(argbuf, "%d %d", &which_one, &lines_requested);
311         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
312                 cprintf("-ERR No such message.\r\n");
313                 return;
314         }
315
316         if (POP3->msgs[which_one - 1].deleted) {
317                 cprintf("-ERR Sorry, you deleted that message.\r\n");
318                 return;
319         }
320
321         cprintf("+OK Whoop, there it is:\r\n");
322         rewind(POP3->msgs[which_one - 1].temp);
323         while (ptr = fgets(buf, sizeof buf, POP3->msgs[which_one - 1].temp),
324               ( (ptr!=NULL) && (done == 0))) {
325                 if (in_body == 1)
326                         if (lines_dumped >= lines_requested) done = 1;
327                 if ((in_body == 0) || (done == 0))
328                         client_write(buf, strlen(buf));
329                 if (in_body) ++lines_dumped;
330                 if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
331         }
332         if (buf[strlen(buf)-1] != 10) cprintf("\n");
333         cprintf(".\r\n");
334 }
335
336
337 /*
338  * DELE (delete message from mailbox)
339  */
340 void pop3_dele(char *argbuf) {
341         int which_one;
342
343         which_one = atoi(argbuf);
344         if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
345                 cprintf("-ERR No such message.\r\n");
346                 return;
347         }
348
349         if (POP3->msgs[which_one - 1].deleted) {
350                 cprintf("-ERR You already deleted that message.\r\n");
351                 return;
352         }
353
354         /* Flag the message as deleted.  Will expunge during QUIT command. */
355         POP3->msgs[which_one - 1].deleted = 1;
356         cprintf("+OK Message %d disappears in a cloud of orange smoke.\r\n",
357                 which_one);
358 }
359
360
361 /* Perform "UPDATE state" stuff
362  */
363 void pop3_update(void) {
364         int i;
365         struct visit vbuf;
366
367         /* Remove messages marked for deletion */
368         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
369                 if (POP3->msgs[i].deleted) {
370                         CtdlDeleteMessages(MAILROOM,
371                                 POP3->msgs[i].msgnum, "");
372                 }
373         }
374
375         /* Set last read pointer */
376         if (POP3->num_msgs > 0) {
377                 lgetuser(&CC->usersupp, CC->curr_user);
378
379                 CtdlGetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
380                 vbuf.v_lastseen = POP3->msgs[POP3->num_msgs-1].msgnum;
381                 CtdlSetRelationship(&vbuf, &CC->usersupp, &CC->quickroom);
382
383                 lputuser(&CC->usersupp);
384         }
385
386 }
387
388
389 /* 
390  * RSET (reset, i.e. undelete any deleted messages) command
391  */
392 void pop3_rset(char *argbuf) {
393         int i;
394
395         if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
396                 if (POP3->msgs[i].deleted) {
397                         POP3->msgs[i].deleted = 0;
398                 }
399         }
400         cprintf("+OK all that has come to pass, has now gone away.\r\n");
401 }
402
403
404
405 /* 
406  * LAST (Determine which message is the last unread message)
407  */
408 void pop3_last(char *argbuf) {
409         cprintf("+OK %d\r\n", POP3->lastseen + 1);
410 }
411
412
413
414 /*
415  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
416  * identifiers are simply the Citadel message numbers in the database.
417  */
418 void pop3_uidl(char *argbuf) {
419         int i;
420         int which_one;
421
422         which_one = atoi(argbuf);
423
424         /* "list one" mode */
425         if (which_one > 0) {
426                 if (which_one > POP3->num_msgs) {
427                         cprintf("-ERR no such message, only %d are here\r\n",
428                                 POP3->num_msgs);
429                         return;
430                 }
431                 else if (POP3->msgs[which_one-1].deleted) {
432                         cprintf("-ERR Sorry, you deleted that message.\r\n");
433                         return;
434                 }
435                 else {
436                         cprintf("+OK %d %ld\n",
437                                 which_one,
438                                 POP3->msgs[which_one-1].msgnum
439                                 );
440                         return;
441                 }
442         }
443
444         /* "list all" (scan listing) mode */
445         else {
446                 cprintf("+OK Here's your mail:\r\n");
447                 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
448                         if (! POP3->msgs[i].deleted) {
449                                 cprintf("%d %ld\r\n",
450                                         i+1,
451                                         POP3->msgs[i].msgnum);
452                         }
453                 }
454                 cprintf(".\r\n");
455         }
456 }
457
458
459
460
461 /* 
462  * Main command loop for POP3 sessions.
463  */
464 void pop3_command_loop(void) {
465         char cmdbuf[256];
466
467         time(&CC->lastcmd);
468         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
469         if (client_gets(cmdbuf) < 1) {
470                 lprintf(3, "POP3 socket is broken.  Ending session.\r\n");
471                 CC->kill_me = 1;
472                 return;
473         }
474         lprintf(5, "citserver[%3d]: %s\r\n", CC->cs_pid, cmdbuf);
475         while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
476
477         if (!strncasecmp(cmdbuf, "NOOP", 4)) {
478                 cprintf("+OK This command successfully did nothing.\r\n");
479         }
480
481         else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
482                 cprintf("+OK Goodbye...\r\n");
483                 pop3_update();
484                 CC->kill_me = 1;
485                 return;
486         }
487
488         else if (!strncasecmp(cmdbuf, "USER", 4)) {
489                 pop3_user(&cmdbuf[5]);
490         }
491
492         else if (!strncasecmp(cmdbuf, "PASS", 4)) {
493                 pop3_pass(&cmdbuf[5]);
494         }
495
496         else if (!CC->logged_in) {
497                 cprintf("-ERR Not logged in.\r\n");
498         }
499
500         else if (!strncasecmp(cmdbuf, "LIST", 4)) {
501                 pop3_list(&cmdbuf[5]);
502         }
503
504         else if (!strncasecmp(cmdbuf, "STAT", 4)) {
505                 pop3_stat(&cmdbuf[5]);
506         }
507
508         else if (!strncasecmp(cmdbuf, "RETR", 4)) {
509                 pop3_retr(&cmdbuf[5]);
510         }
511
512         else if (!strncasecmp(cmdbuf, "DELE", 4)) {
513                 pop3_dele(&cmdbuf[5]);
514         }
515
516         else if (!strncasecmp(cmdbuf, "RSET", 4)) {
517                 pop3_rset(&cmdbuf[5]);
518         }
519
520         else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
521                 pop3_uidl(&cmdbuf[5]);
522         }
523
524         else if (!strncasecmp(cmdbuf, "TOP", 3)) {
525                 pop3_top(&cmdbuf[4]);
526         }
527
528         else if (!strncasecmp(cmdbuf, "LAST", 4)) {
529                 pop3_last(&cmdbuf[4]);
530         }
531
532         else {
533                 cprintf("500 I'm afraid I can't do that, Dave.\r\n");
534         }
535
536 }
537
538
539
540 char *Dynamic_Module_Init(void)
541 {
542         SYM_POP3 = CtdlGetDynamicSymbol();
543         CtdlRegisterServiceHook(config.c_pop3_port,
544                                 NULL,
545                                 pop3_greeting,
546                                 pop3_command_loop);
547         CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP);
548         return "$Id$";
549 }