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