"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/pop3/serv_pop3.c" (5 Jun 2021, 14668 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "serv_pop3.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

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