citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

Loading...
Searching...
No Matches
serv_pop3.c
Go to the documentation of this file.
1// POP3 service for the Citadel system
2//
3// Copyright (c) 1998-2022 by the citadel.org team
4//
5// This program is open source software; you can redistribute it and/or modify
6// it under the terms of the GNU General Public License version 3.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// Current status of standards conformance:
14//
15// -> All required POP3 commands described in RFC1939 are implemented.
16// -> All optional POP3 commands described in RFC1939 are also implemented.
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// -> Capability detection via the method described in RFC2449 is implemented.
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 <time.h>
32#include <sys/wait.h>
33#include <string.h>
34#include <limits.h>
35#include <ctype.h>
36#include <libcitadel.h>
37#include "../../citadel.h"
38#include "../../server.h"
39#include "../../citserver.h"
40#include "../../support.h"
41#include "../../config.h"
42#include "../../user_ops.h"
43#include "../../database.h"
44#include "../../msgbase.h"
45#include "../../internet_addressing.h"
46#include "serv_pop3.h"
47#include "../../ctdl_module.h"
48
49
50// This cleanup function blows away the temporary memory and files used by
51// the POP3 server.
53 /* Don't do this stuff if this is not a POP3 session! */
54 if (CC->h_command_function != pop3_command_loop) return;
55
56 struct citpop3 *pop3 = ((struct citpop3 *)CC->session_specific_data);
57 syslog(LOG_DEBUG, "pop3: performing cleanup hook");
58 if (pop3->msgs != NULL) {
59 free(pop3->msgs);
60 }
61
62 free(pop3);
63}
64
65
66// Here's where our POP3 session begins its happy day.
67void pop3_greeting(void) {
68 strcpy(CC->cs_clientname, "POP3 session");
69 CC->internal_pgm = 1;
70 CC->session_specific_data = malloc(sizeof(struct citpop3));
71 memset(POP3, 0, sizeof(struct citpop3));
72
73 cprintf("+OK Citadel POP3 server ready.\r\n");
74}
75
76
77// POP3S is just like POP3, except it goes crypto right away.
78void pop3s_greeting(void) {
79 CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
80
81/* kill session if no crypto */
82#ifdef HAVE_OPENSSL
83 if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
84#else
85 CC->kill_me = KILLME_NO_CRYPTO;
86#endif
87
89}
90
91
92// Specify user name (implements POP3 "USER" command)
93void pop3_user(char *argbuf) {
94 char username[SIZ];
95
96 if (CC->logged_in) {
97 cprintf("-ERR You are already logged in.\r\n");
98 return;
99 }
100
101 strcpy(username, argbuf);
102 striplt(username);
103
104 if (CtdlLoginExistingUser(username) == login_ok) {
105 cprintf("+OK Password required for %s\r\n", username);
106 }
107 else {
108 cprintf("-ERR No such user.\r\n");
109 }
110}
111
112
113// Back end for pop3_grab_mailbox()
114void pop3_add_message(long msgnum, void *userdata) {
115 struct MetaData smi;
116
117 ++POP3->num_msgs;
118 if (POP3->num_msgs < 2) {
119 POP3->msgs = malloc(sizeof(struct pop3msg));
120 }
121 else {
122 POP3->msgs = realloc(POP3->msgs, (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
123 }
124 POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
125 POP3->msgs[POP3->num_msgs-1].deleted = 0;
126
127 // We need to know the length of this message when it is printed in
128 // RFC822 format. Perhaps we have cached this length in the message's
129 // metadata record. If so, great; if not, measure it and then cache
130 // it for next time.
131 GetMetaData(&smi, msgnum);
132 if (smi.meta_rfc822_length <= 0L) {
133 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
134 CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO, NULL, NULL, NULL);
135 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
136 FreeStrBuf(&CC->redirect_buffer);
138 }
139 POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
140}
141
142
143// Open the inbox and read its contents.
144// (This should be called only once, by pop3_pass(), and returns the number
145// of messages in the inbox, or -1 for error)
147 visit vbuf;
148 int i;
149
150 if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
151
152 /* Load up the messages */
153 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, pop3_add_message, NULL);
154
155 /* Figure out which are old and which are new */
156 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
157 POP3->lastseen = (-1);
158 if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
159 if (is_msg_in_sequence_set(vbuf.v_seen, (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
160 POP3->lastseen = i;
161 }
162 }
163
164 return(POP3->num_msgs);
165}
166
167
168void pop3_login(void) {
169 int msgs;
170
171 msgs = pop3_grab_mailbox();
172 if (msgs >= 0) {
173 cprintf("+OK %s is logged in (%d messages)\r\n",
174 CC->user.fullname, msgs);
175 syslog(LOG_DEBUG, "pop3: authenticated %s", CC->user.fullname);
176 }
177 else {
178 cprintf("-ERR Can't open your mailbox\r\n");
179 }
180
181}
182
183
184// Authorize with password (implements POP3 "PASS" command)
185void pop3_pass(char *argbuf) {
186 char password[SIZ];
187
188 safestrncpy(password, argbuf, sizeof password);
189 striplt(password);
190
191 if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
192 pop3_login();
193 }
194 else {
195 cprintf("-ERR That is NOT the password.\r\n");
196 }
197}
198
199
200// list available msgs
201void pop3_list(char *argbuf) {
202 int i;
203 int which_one;
204
205 which_one = atoi(argbuf);
206
207 // "list one" mode
208 if (which_one > 0) {
209 if (which_one > POP3->num_msgs) {
210 cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
211 return;
212 }
213 else if (POP3->msgs[which_one-1].deleted) {
214 cprintf("-ERR Sorry, you deleted that message.\r\n");
215 return;
216 }
217 else {
218 cprintf("+OK %d %ld\r\n", which_one, (long)POP3->msgs[which_one-1].rfc822_length);
219 return;
220 }
221 }
222
223 // "list all" (scan listing) mode
224 else {
225 cprintf("+OK Here's your mail:\r\n");
226 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
227 if (! POP3->msgs[i].deleted) {
228 cprintf("%d %ld\r\n", i+1, (long)POP3->msgs[i].rfc822_length);
229 }
230 }
231 cprintf(".\r\n");
232 }
233}
234
235
236// STAT (tally up the total message count and byte count) command
237void pop3_stat(char *argbuf) {
238 int total_msgs = 0;
239 size_t total_octets = 0;
240 int i;
241
242 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
243 if (! POP3->msgs[i].deleted) {
244 ++total_msgs;
245 total_octets += POP3->msgs[i].rfc822_length;
246 }
247 }
248
249 cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
250}
251
252
253// RETR command (fetch a message)
254void pop3_retr(char *argbuf) {
255 int which_one;
256
257 which_one = atoi(argbuf);
258 if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
259 cprintf("-ERR No such message.\r\n");
260 return;
261 }
262
263 if (POP3->msgs[which_one - 1].deleted) {
264 cprintf("-ERR Sorry, you deleted that message.\r\n");
265 return;
266 }
267
268 cprintf("+OK Message %d:\r\n", which_one);
269 CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822,
270 HEADERS_ALL, 0, 1, NULL,
271 (ESC_DOT|SUPPRESS_ENV_TO), NULL, NULL, NULL
272 );
273 cprintf(".\r\n");
274}
275
276
277// TOP command (dumb way of fetching a partial message or headers-only)
278void pop3_top(char *argbuf) {
279 int which_one;
280 int lines_requested = 0;
281 int lines_dumped = 0;
282 char buf[1024];
283 StrBuf *msgtext;
284 const char *ptr;
285 int in_body = 0;
286 int done = 0;
287
288 sscanf(argbuf, "%d %d", &which_one, &lines_requested);
289 if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
290 cprintf("-ERR No such message.\r\n");
291 return;
292 }
293
294 if (POP3->msgs[which_one - 1].deleted) {
295 cprintf("-ERR Sorry, you deleted that message.\r\n");
296 return;
297 }
298
299 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
300
301 CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
302 MT_RFC822,
304 0, 1, NULL,
306 NULL, NULL, NULL);
307
308 msgtext = CC->redirect_buffer;
309 CC->redirect_buffer = NULL;
310
311 cprintf("+OK Message %d:\r\n", which_one);
312
313 ptr = ChrPtr(msgtext);
314 while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
315 ( (*ptr != 0) && (done == 0))) {
316 strcat(buf, "\r\n");
317 if (in_body == 1) {
318 if (lines_dumped >= lines_requested) {
319 done = 1;
320 }
321 }
322 if ((in_body == 0) || (done == 0)) {
323 client_write(buf, strlen(buf));
324 }
325 if (in_body) {
326 ++lines_dumped;
327 }
328 if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
329 }
330
331 if (buf[strlen(buf)-1] != 10) cprintf("\n");
332 FreeStrBuf(&msgtext);
333
334 cprintf(".\r\n");
335}
336
337
338// DELE (delete message from mailbox)
339void 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 deleted.\r\n",
356 which_one);
357}
358
359
360// Perform "UPDATE state" stuff
361void pop3_update(void) {
362 int i;
363 visit vbuf;
364
365 long *deletemsgs = NULL;
366 int num_deletemsgs = 0;
367
368 // Remove messages marked for deletion
369 if (POP3->num_msgs > 0) {
370 deletemsgs = malloc(POP3->num_msgs * sizeof(long));
371 for (i=0; i<POP3->num_msgs; ++i) {
372 if (POP3->msgs[i].deleted) {
373 deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
374 }
375 }
376 if (num_deletemsgs > 0) {
377 CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
378 }
379 free(deletemsgs);
380 }
381
382 // Set last read pointer
383 if (POP3->num_msgs > 0) {
385 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
386 snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld", POP3->msgs[POP3->num_msgs-1].msgnum);
387 CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
389 }
390
391}
392
393
394// RSET (reset, i.e. undelete any deleted messages) command
395void pop3_rset(char *argbuf) {
396 int i;
397
398 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
399 if (POP3->msgs[i].deleted) {
400 POP3->msgs[i].deleted = 0;
401 }
402 }
403 cprintf("+OK Reset completed.\r\n");
404}
405
406
407// LAST (Determine which message is the last unread message)
408void pop3_last(char *argbuf) {
409 cprintf("+OK %d\r\n", POP3->lastseen + 1);
410}
411
412
413// CAPA is a command which tells the client which POP3 extensions are supported.
414void pop3_capa(void) {
415 cprintf("+OK Capability list follows\r\n"
416 "TOP\r\n"
417 "USER\r\n"
418 "UIDL\r\n"
419 "IMPLEMENTATION %s\r\n"
420 ".\r\n"
421 ,
422 CITADEL
423 );
424}
425
426
427// UIDL (Universal IDentifier Listing) is easy. Our 'unique' message
428// identifiers are simply the Citadel message numbers in the database.
429void pop3_uidl(char *argbuf) {
430 int i;
431 int which_one;
432
433 which_one = atoi(argbuf);
434
435 // "list one" mode
436 if (which_one > 0) {
437 if (which_one > POP3->num_msgs) {
438 cprintf("-ERR no such message, only %d are here\r\n", POP3->num_msgs);
439 return;
440 }
441 else if (POP3->msgs[which_one-1].deleted) {
442 cprintf("-ERR Sorry, you deleted that message.\r\n");
443 return;
444 }
445 else {
446 cprintf("+OK %d %ld\r\n", which_one, POP3->msgs[which_one-1].msgnum);
447 return;
448 }
449 }
450
451 // "list all" (scan listing) mode
452 else {
453 cprintf("+OK Here's your mail:\r\n");
454 if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
455 if (! POP3->msgs[i].deleted) {
456 cprintf("%d %ld\r\n", i+1, POP3->msgs[i].msgnum);
457 }
458 }
459 cprintf(".\r\n");
460 }
461}
462
463
464// implements the STLS command (Citadel API version)
465void pop3_stls(void) {
466 char ok_response[SIZ];
467 char nosup_response[SIZ];
468 char error_response[SIZ];
469
470 sprintf(ok_response, "+OK Begin TLS negotiation now\r\n");
471 sprintf(nosup_response, "-ERR TLS not supported here\r\n");
472 sprintf(error_response, "-ERR Internal error\r\n");
473 CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
474}
475
476
477// Main command loop for POP3 sessions.
479 char cmdbuf[SIZ];
480
481 time(&CC->lastcmd);
482 memset(cmdbuf, 0, sizeof cmdbuf); // Clear it, just in case
483 if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
484 syslog(LOG_INFO, "pop3: client disconnected; ending session.");
486 return;
487 }
488 if (!strncasecmp(cmdbuf, "PASS", 4)) {
489 syslog(LOG_DEBUG, "pop3: PASS...");
490 }
491 else {
492 syslog(LOG_DEBUG, "pop3: %s", cmdbuf);
493 }
494 while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
495
496 if (!strncasecmp(cmdbuf, "NOOP", 4)) {
497 cprintf("+OK No operation.\r\n");
498 }
499
500 else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
501 pop3_capa();
502 }
503
504 else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
505 cprintf("+OK Goodbye...\r\n");
506 pop3_update();
507 CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
508 return;
509 }
510
511 else if (!strncasecmp(cmdbuf, "USER", 4)) {
512 pop3_user(&cmdbuf[5]);
513 }
514
515 else if (!strncasecmp(cmdbuf, "PASS", 4)) {
516 pop3_pass(&cmdbuf[5]);
517 }
518
519#ifdef HAVE_OPENSSL
520 else if (!strncasecmp(cmdbuf, "STLS", 4)) {
521 pop3_stls();
522 }
523#endif
524
525 else if (!CC->logged_in) {
526 cprintf("-ERR Not logged in.\r\n");
527 }
528
529 else if (CC->nologin) {
530 cprintf("-ERR System busy, try later.\r\n");
531 CC->kill_me = KILLME_NOLOGIN;
532 }
533
534 else if (!strncasecmp(cmdbuf, "LIST", 4)) {
535 pop3_list(&cmdbuf[5]);
536 }
537
538 else if (!strncasecmp(cmdbuf, "STAT", 4)) {
539 pop3_stat(&cmdbuf[5]);
540 }
541
542 else if (!strncasecmp(cmdbuf, "RETR", 4)) {
543 pop3_retr(&cmdbuf[5]);
544 }
545
546 else if (!strncasecmp(cmdbuf, "DELE", 4)) {
547 pop3_dele(&cmdbuf[5]);
548 }
549
550 else if (!strncasecmp(cmdbuf, "RSET", 4)) {
551 pop3_rset(&cmdbuf[5]);
552 }
553
554 else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
555 pop3_uidl(&cmdbuf[5]);
556 }
557
558 else if (!strncasecmp(cmdbuf, "TOP", 3)) {
559 pop3_top(&cmdbuf[4]);
560 }
561
562 else if (!strncasecmp(cmdbuf, "LAST", 4)) {
563 pop3_last(&cmdbuf[4]);
564 }
565
566 else {
567 cprintf("-ERR I'm afraid I can't do that.\r\n");
568 }
569
570}
571
572const char *CitadelServicePop3="POP3";
573const char *CitadelServicePop3S="POP3S";
574
575
576// Initialization function, called from modules_init.c
578 if (!threading) {
580 NULL,
583 NULL,
585#ifdef HAVE_OPENSSL
587 NULL,
590 NULL,
592#endif
594 }
595
596 /* return our module name for the log */
597 return "pop3";
598}
#define CITADEL
Definition: citadel.h:37
int CtdlGetConfigInt(char *key)
Definition: config.c:390
#define CC
Definition: context.h:140
void CtdlGetRelationship(visit *vbuf, struct ctdluser *rel_user, struct ctdlroom *rel_room)
Definition: user_ops.c:277
void CtdlModuleStartCryptoMsgs(char *ok_response, char *nosup_response, char *error_response)
int CtdlLockGetCurrentUser(void)
Definition: user_ops.c:89
int CtdlGetRoom(struct ctdlroom *qrbuf, const char *room_name)
Definition: room_ops.c:309
void CtdlRegisterServiceHook(int tcp_port, char *sockpath, void(*h_greeting_function)(void), void(*h_command_function)(void), void(*h_async_function)(void), const char *ServiceName)
void CtdlPutCurrentUserLock(void)
Definition: user_ops.c:115
void CtdlRegisterSessionHook(void(*fcn_ptr)(void), int EventType, int Priority)
int threading
Definition: modules_init.c:24
@ pass_ok
Definition: ctdl_module.h:293
int CtdlTryPassword(const char *password, long len)
Definition: user_ops.c:772
@ login_ok
Definition: ctdl_module.h:305
void CtdlSetRelationship(visit *newvisit, struct ctdluser *rel_user, struct ctdlroom *rel_room)
Definition: user_ops.c:265
int CtdlLoginExistingUser(const char *username)
Definition: user_ops.c:461
#define PRIO_STOP
Definition: ctdl_module.h:90
int CtdlOutputMsg(long msg_num, int mode, int headers_only, int do_proto, int crlf, char *section, int flags, char **Author, char **Address, char **MessageID)
Definition: msgbase.c:1470
void GetMetaData(struct MetaData *smibuf, long msgnum)
Definition: msgbase.c:3372
int CtdlForEachMessage(int mode, long ref, char *search_string, char *content_type, struct CtdlMessage *compare, ForEachMsgCallback CallBack, void *userdata)
Definition: msgbase.c:621
int CtdlDeleteMessages(const char *room_name, long *dmsgnums, int num_dmsgnums, char *content_type)
Definition: msgbase.c:3229
void PutMetaData(struct MetaData *smibuf)
Definition: msgbase.c:3400
#define SUPPRESS_ENV_TO
Definition: msgbase.h:163
#define ESC_DOT
Definition: msgbase.h:162
#define HEADERS_ALL
Definition: msgbase.h:39
@ MSGS_ALL
Definition: msgbase.h:6
void * malloc(unsigned)
void free(void *)
struct MetaData smi
Definition: serv_migrate.c:504
visit vbuf
Definition: serv_migrate.c:503
int total_msgs
Definition: serv_migrate.c:57
void pop3_add_message(long msgnum, void *userdata)
Definition: serv_pop3.c:114
void pop3_command_loop(void)
Definition: serv_pop3.c:478
void pop3_update(void)
Definition: serv_pop3.c:361
void pop3_top(char *argbuf)
Definition: serv_pop3.c:278
void pop3_capa(void)
Definition: serv_pop3.c:414
void pop3_login(void)
Definition: serv_pop3.c:168
void pop3_rset(char *argbuf)
Definition: serv_pop3.c:395
char * ctdl_module_init_pop3(void)
Definition: serv_pop3.c:577
void pop3s_greeting(void)
Definition: serv_pop3.c:78
void pop3_greeting(void)
Definition: serv_pop3.c:67
void pop3_stat(char *argbuf)
Definition: serv_pop3.c:237
void pop3_dele(char *argbuf)
Definition: serv_pop3.c:339
const char * CitadelServicePop3
Definition: serv_pop3.c:572
void pop3_list(char *argbuf)
Definition: serv_pop3.c:201
void pop3_stls(void)
Definition: serv_pop3.c:465
void pop3_last(char *argbuf)
Definition: serv_pop3.c:408
void pop3_retr(char *argbuf)
Definition: serv_pop3.c:254
void pop3_pass(char *argbuf)
Definition: serv_pop3.c:185
void pop3_uidl(char *argbuf)
Definition: serv_pop3.c:429
int pop3_grab_mailbox(void)
Definition: serv_pop3.c:146
const char * CitadelServicePop3S
Definition: serv_pop3.c:573
void pop3_cleanup_function(void)
Definition: serv_pop3.c:52
void pop3_user(char *argbuf)
Definition: serv_pop3.c:93
#define POP3
Definition: serv_pop3.h:30
#define EVT_STOP
Definition: server.h:212
@ MT_RFC822
Definition: server.h:167
@ KILLME_NOLOGIN
Definition: server.h:98
@ KILLME_CLIENT_DISCONNECTED
Definition: server.h:89
@ KILLME_CLIENT_LOGGED_OUT
Definition: server.h:87
@ KILLME_NO_CRYPTO
Definition: server.h:99
long meta_rfc822_length
Definition: server.h:271
char v_seen[4096]
Definition: server.h:251
struct pop3msg * msgs
Definition: serv_pop3.h:20
#define MAILROOM
Definition: sysconfig.h:61
#define SIZ
Definition: sysconfig.h:33
void cprintf(const char *format,...)
Definition: sysdep.c:369
int client_getln(char *buf, int bufsize)
Definition: sysdep.c:528
int client_write(const char *buf, int nbytes)
Definition: sysdep.c:296