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