Back to index

citadel  8.12
serv_pop3.c
Go to the documentation of this file.
00001 /*
00002  * POP3 service for the Citadel system
00003  *
00004  * Copyright (c) 1998-2012 by the citadel.org team
00005  *
00006  *  This program is open source software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License version 3.
00008  *  
00009  *  
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  
00017  *  
00018  *  
00019  *
00020  * Current status of standards conformance:
00021  *
00022  * -> All required POP3 commands described in RFC1939 are implemented.
00023  * -> All optional POP3 commands described in RFC1939 are also implemented.
00024  * -> The deprecated "LAST" command is included in this implementation, because
00025  *    there exist mail clients which insist on using it (such as Bynari
00026  *    TradeMail, and certain versions of Eudora).
00027  * -> Capability detection via the method described in RFC2449 is implemented.
00028  * 
00029  */
00030 
00031 #include "sysdep.h"
00032 #include <stdlib.h>
00033 #include <unistd.h>
00034 #include <stdio.h>
00035 #include <fcntl.h>
00036 #include <signal.h>
00037 #include <pwd.h>
00038 #include <errno.h>
00039 #include <sys/types.h>
00040 
00041 #if TIME_WITH_SYS_TIME
00042 # include <sys/time.h>
00043 # include <time.h>
00044 #else
00045 # if HAVE_SYS_TIME_H
00046 #  include <sys/time.h>
00047 # else
00048 #  include <time.h>
00049 # endif
00050 #endif
00051 
00052 #include <sys/wait.h>
00053 #include <string.h>
00054 #include <limits.h>
00055 #include <ctype.h>
00056 #include <libcitadel.h>
00057 #include "citadel.h"
00058 #include "server.h"
00059 #include "citserver.h"
00060 #include "support.h"
00061 #include "config.h"
00062 #include "user_ops.h"
00063 #include "database.h"
00064 #include "msgbase.h"
00065 #include "internet_addressing.h"
00066 #include "serv_pop3.h"
00067 #include "md5.h"
00068 
00069 
00070 
00071 #include "ctdl_module.h"
00072 
00073 
00074 
00075 /*
00076  * This cleanup function blows away the temporary memory and files used by
00077  * the POP3 server.
00078  */
00079 void pop3_cleanup_function(void) {
00080 
00081        /* Don't do this stuff if this is not a POP3 session! */
00082        if (CC->h_command_function != pop3_command_loop) return;
00083 
00084        syslog(LOG_DEBUG, "Performing POP3 cleanup hook");
00085        if (POP3->msgs != NULL) free(POP3->msgs);
00086 
00087        free(POP3);
00088 }
00089 
00090 
00091 
00092 /*
00093  * Here's where our POP3 session begins its happy day.
00094  */
00095 void pop3_greeting(void) {
00096        strcpy(CC->cs_clientname, "POP3 session");
00097        CC->internal_pgm = 1;
00098        CC->session_specific_data = malloc(sizeof(struct citpop3));
00099        memset(POP3, 0, sizeof(struct citpop3));
00100 
00101        cprintf("+OK Citadel POP3 server ready.\r\n");
00102 }
00103 
00104 
00105 /*
00106  * POP3S is just like POP3, except it goes crypto right away.
00107  */
00108 void pop3s_greeting(void) {
00109        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
00110 
00111 /* kill session if no crypto */
00112 #ifdef HAVE_OPENSSL
00113        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;
00114 #else
00115        CC->kill_me = KILLME_NO_CRYPTO;
00116 #endif
00117 
00118        pop3_greeting();
00119 }
00120 
00121 
00122 
00123 /*
00124  * Specify user name (implements POP3 "USER" command)
00125  */
00126 void pop3_user(char *argbuf) {
00127        char username[SIZ];
00128 
00129        if (CC->logged_in) {
00130               cprintf("-ERR You are already logged in.\r\n");
00131               return;
00132        }
00133 
00134        strcpy(username, argbuf);
00135        striplt(username);
00136 
00137        /* syslog(LOG_DEBUG, "Trying <%s>", username); */
00138        if (CtdlLoginExistingUser(NULL, username) == login_ok) {
00139               cprintf("+OK Password required for %s\r\n", username);
00140        }
00141        else {
00142               cprintf("-ERR No such user.\r\n");
00143        }
00144 }
00145 
00146 
00147 
00148 /*
00149  * Back end for pop3_grab_mailbox()
00150  */
00151 void pop3_add_message(long msgnum, void *userdata) {
00152        struct MetaData smi;
00153 
00154        ++POP3->num_msgs;
00155        if (POP3->num_msgs < 2) POP3->msgs = malloc(sizeof(struct pop3msg));
00156        else POP3->msgs = realloc(POP3->msgs, 
00157               (POP3->num_msgs * sizeof(struct pop3msg)) ) ;
00158        POP3->msgs[POP3->num_msgs-1].msgnum = msgnum;
00159        POP3->msgs[POP3->num_msgs-1].deleted = 0;
00160 
00161        /* We need to know the length of this message when it is printed in
00162         * RFC822 format.  Perhaps we have cached this length in the message's
00163         * metadata record.  If so, great; if not, measure it and then cache
00164         * it for next time.
00165         */
00166        GetMetaData(&smi, msgnum);
00167        if (smi.meta_rfc822_length <= 0L) {
00168               CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00169               CtdlOutputMsg(msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO);
00170               smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
00171               FreeStrBuf(&CC->redirect_buffer); /* TODO: WHEW, all this for just knowing the length???? */
00172               PutMetaData(&smi);
00173        }
00174        POP3->msgs[POP3->num_msgs-1].rfc822_length = smi.meta_rfc822_length;
00175 }
00176 
00177 
00178 
00179 /*
00180  * Open the inbox and read its contents.
00181  * (This should be called only once, by pop3_pass(), and returns the number
00182  * of messages in the inbox, or -1 for error)
00183  */
00184 int pop3_grab_mailbox(void) {
00185         visit vbuf;
00186        int i;
00187 
00188        if (CtdlGetRoom(&CC->room, MAILROOM) != 0) return(-1);
00189 
00190        /* Load up the messages */
00191        CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL,
00192               pop3_add_message, NULL);
00193 
00194        /* Figure out which are old and which are new */
00195         CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
00196        POP3->lastseen = (-1);
00197        if (POP3->num_msgs) for (i=0; i<POP3->num_msgs; ++i) {
00198               if (is_msg_in_sequence_set(vbuf.v_seen,
00199                  (POP3->msgs[POP3->num_msgs-1].msgnum) )) {
00200                      POP3->lastseen = i;
00201               }
00202        }
00203 
00204        return(POP3->num_msgs);
00205 }
00206 
00207 void pop3_login(void)
00208 {
00209        int msgs;
00210        
00211        msgs = pop3_grab_mailbox();
00212        if (msgs >= 0) {
00213               cprintf("+OK %s is logged in (%d messages)\r\n",
00214                      CC->user.fullname, msgs);
00215               syslog(LOG_NOTICE, "POP3 authenticated %s", CC->user.fullname);
00216        }
00217        else {
00218               cprintf("-ERR Can't open your mailbox\r\n");
00219        }
00220        
00221 }
00222 
00223 
00224 /*
00225  * Authorize with password (implements POP3 "PASS" command)
00226  */
00227 void pop3_pass(char *argbuf) {
00228        char password[SIZ];
00229 
00230        safestrncpy(password, argbuf, sizeof password);
00231        striplt(password);
00232 
00233        /* syslog(LOG_DEBUG, "Trying <%s>", password); */
00234        if (CtdlTryPassword(password, strlen(password)) == pass_ok) {
00235               pop3_login();
00236        }
00237        else {
00238               cprintf("-ERR That is NOT the password.\r\n");
00239        }
00240 }
00241 
00242 
00243 
00244 /*
00245  * list available msgs
00246  */
00247 void pop3_list(char *argbuf) {
00248        int i;
00249        int which_one;
00250 
00251        which_one = atoi(argbuf);
00252 
00253        /* "list one" mode */
00254        if (which_one > 0) {
00255               if (which_one > POP3->num_msgs) {
00256                      cprintf("-ERR no such message, only %d are here\r\n",
00257                             POP3->num_msgs);
00258                      return;
00259               }
00260               else if (POP3->msgs[which_one-1].deleted) {
00261                      cprintf("-ERR Sorry, you deleted that message.\r\n");
00262                      return;
00263               }
00264               else {
00265                      cprintf("+OK %d %ld\r\n",
00266                             which_one,
00267                             (long)POP3->msgs[which_one-1].rfc822_length
00268                             );
00269                      return;
00270               }
00271        }
00272 
00273        /* "list all" (scan listing) mode */
00274        else {
00275               cprintf("+OK Here's your mail:\r\n");
00276               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
00277                      if (! POP3->msgs[i].deleted) {
00278                             cprintf("%d %ld\r\n",
00279                                    i+1,
00280                                    (long)POP3->msgs[i].rfc822_length);
00281                      }
00282               }
00283               cprintf(".\r\n");
00284        }
00285 }
00286 
00287 
00288 /*
00289  * STAT (tally up the total message count and byte count) command
00290  */
00291 void pop3_stat(char *argbuf) {
00292        int total_msgs = 0;
00293        size_t total_octets = 0;
00294        int i;
00295        
00296        if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
00297               if (! POP3->msgs[i].deleted) {
00298                      ++total_msgs;
00299                      total_octets += POP3->msgs[i].rfc822_length;
00300               }
00301        }
00302 
00303        cprintf("+OK %d %ld\r\n", total_msgs, (long)total_octets);
00304 }
00305 
00306 
00307 
00308 /*
00309  * RETR command (fetch a message)
00310  */
00311 void pop3_retr(char *argbuf) {
00312        int which_one;
00313 
00314        which_one = atoi(argbuf);
00315        if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
00316               cprintf("-ERR No such message.\r\n");
00317               return;
00318        }
00319 
00320        if (POP3->msgs[which_one - 1].deleted) {
00321               cprintf("-ERR Sorry, you deleted that message.\r\n");
00322               return;
00323        }
00324 
00325        cprintf("+OK Message %d:\r\n", which_one);
00326        CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum,
00327               MT_RFC822, HEADERS_ALL, 0, 1, NULL,
00328               (ESC_DOT|SUPPRESS_ENV_TO)
00329        );
00330        cprintf(".\r\n");
00331 }
00332 
00333 
00334 /*
00335  * TOP command (dumb way of fetching a partial message or headers-only)
00336  */
00337 void pop3_top(char *argbuf) {
00338        int which_one;
00339        int lines_requested = 0;
00340        int lines_dumped = 0;
00341        char buf[1024];
00342        StrBuf *msgtext;
00343        const char *ptr;
00344        int in_body = 0;
00345        int done = 0;
00346 
00347        sscanf(argbuf, "%d %d", &which_one, &lines_requested);
00348        if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
00349               cprintf("-ERR No such message.\r\n");
00350               return;
00351        }
00352 
00353        if (POP3->msgs[which_one - 1].deleted) {
00354               cprintf("-ERR Sorry, you deleted that message.\r\n");
00355               return;
00356        }
00357 
00358        CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
00359        CtdlOutputMsg(POP3->msgs[which_one - 1].msgnum, MT_RFC822, HEADERS_ALL, 0, 1, NULL, SUPPRESS_ENV_TO);
00360        msgtext = CC->redirect_buffer;
00361        CC->redirect_buffer = NULL;
00362 
00363        cprintf("+OK Message %d:\r\n", which_one);
00364        
00365        ptr = ChrPtr(msgtext);
00366        while (ptr = cmemreadline(ptr, buf, (sizeof buf - 2)),
00367              ( (*ptr != 0) && (done == 0))) {
00368               strcat(buf, "\r\n");
00369               if (in_body == 1) {
00370                      if (lines_dumped >= lines_requested) {
00371                             done = 1;
00372                      }
00373               }
00374               if ((in_body == 0) || (done == 0)) {
00375                      client_write(buf, strlen(buf));
00376               }
00377               if (in_body) {
00378                      ++lines_dumped;
00379               }
00380               if ((buf[0]==13)||(buf[0]==10)) in_body = 1;
00381        }
00382 
00383        if (buf[strlen(buf)-1] != 10) cprintf("\n");
00384        FreeStrBuf(&msgtext);
00385 
00386        cprintf(".\r\n");
00387 }
00388 
00389 
00390 /*
00391  * DELE (delete message from mailbox)
00392  */
00393 void pop3_dele(char *argbuf) {
00394        int which_one;
00395 
00396        which_one = atoi(argbuf);
00397        if ( (which_one < 1) || (which_one > POP3->num_msgs) ) {
00398               cprintf("-ERR No such message.\r\n");
00399               return;
00400        }
00401 
00402        if (POP3->msgs[which_one - 1].deleted) {
00403               cprintf("-ERR You already deleted that message.\r\n");
00404               return;
00405        }
00406 
00407        /* Flag the message as deleted.  Will expunge during QUIT command. */
00408        POP3->msgs[which_one - 1].deleted = 1;
00409        cprintf("+OK Message %d deleted.\r\n",
00410               which_one);
00411 }
00412 
00413 
00414 /* Perform "UPDATE state" stuff
00415  */
00416 void pop3_update(void) {
00417        int i;
00418         visit vbuf;
00419 
00420        long *deletemsgs = NULL;
00421        int num_deletemsgs = 0;
00422 
00423        /* Remove messages marked for deletion */
00424        if (POP3->num_msgs > 0) {
00425               deletemsgs = malloc(POP3->num_msgs * sizeof(long));
00426               for (i=0; i<POP3->num_msgs; ++i) {
00427                      if (POP3->msgs[i].deleted) {
00428                             deletemsgs[num_deletemsgs++] = POP3->msgs[i].msgnum;
00429                      }
00430               }
00431               if (num_deletemsgs > 0) {
00432                      CtdlDeleteMessages(MAILROOM, deletemsgs, num_deletemsgs, "");
00433               }
00434               free(deletemsgs);
00435        }
00436 
00437        /* Set last read pointer */
00438        if (POP3->num_msgs > 0) {
00439               CtdlGetUserLock(&CC->user, CC->curr_user);
00440 
00441               CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
00442               snprintf(vbuf.v_seen, sizeof vbuf.v_seen, "*:%ld",
00443                      POP3->msgs[POP3->num_msgs-1].msgnum);
00444               CtdlSetRelationship(&vbuf, &CC->user, &CC->room);
00445 
00446               CtdlPutUserLock(&CC->user);
00447        }
00448 
00449 }
00450 
00451 
00452 /* 
00453  * RSET (reset, i.e. undelete any deleted messages) command
00454  */
00455 void pop3_rset(char *argbuf) {
00456        int i;
00457 
00458        if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
00459               if (POP3->msgs[i].deleted) {
00460                      POP3->msgs[i].deleted = 0;
00461               }
00462        }
00463        cprintf("+OK Reset completed.\r\n");
00464 }
00465 
00466 
00467 
00468 /* 
00469  * LAST (Determine which message is the last unread message)
00470  */
00471 void pop3_last(char *argbuf) {
00472        cprintf("+OK %d\r\n", POP3->lastseen + 1);
00473 }
00474 
00475 
00476 /*
00477  * CAPA is a command which tells the client which POP3 extensions
00478  * are supported.
00479  */
00480 void pop3_capa(void) {
00481        cprintf("+OK Capability list follows\r\n"
00482               "TOP\r\n"
00483               "USER\r\n"
00484               "UIDL\r\n"
00485               "IMPLEMENTATION %s\r\n"
00486               ".\r\n"
00487               ,
00488               CITADEL
00489        );
00490 }
00491 
00492 
00493 
00494 /*
00495  * UIDL (Universal IDentifier Listing) is easy.  Our 'unique' message
00496  * identifiers are simply the Citadel message numbers in the database.
00497  */
00498 void pop3_uidl(char *argbuf) {
00499        int i;
00500        int which_one;
00501 
00502        which_one = atoi(argbuf);
00503 
00504        /* "list one" mode */
00505        if (which_one > 0) {
00506               if (which_one > POP3->num_msgs) {
00507                      cprintf("-ERR no such message, only %d are here\r\n",
00508                             POP3->num_msgs);
00509                      return;
00510               }
00511               else if (POP3->msgs[which_one-1].deleted) {
00512                      cprintf("-ERR Sorry, you deleted that message.\r\n");
00513                      return;
00514               }
00515               else {
00516                      cprintf("+OK %d %ld\r\n",
00517                             which_one,
00518                             POP3->msgs[which_one-1].msgnum
00519                             );
00520                      return;
00521               }
00522        }
00523 
00524        /* "list all" (scan listing) mode */
00525        else {
00526               cprintf("+OK Here's your mail:\r\n");
00527               if (POP3->num_msgs > 0) for (i=0; i<POP3->num_msgs; ++i) {
00528                      if (! POP3->msgs[i].deleted) {
00529                             cprintf("%d %ld\r\n",
00530                                    i+1,
00531                                    POP3->msgs[i].msgnum);
00532                      }
00533               }
00534               cprintf(".\r\n");
00535        }
00536 }
00537 
00538 
00539 /*
00540  * implements the STLS command (Citadel API version)
00541  */
00542 void pop3_stls(void)
00543 {
00544        char ok_response[SIZ];
00545        char nosup_response[SIZ];
00546        char error_response[SIZ];
00547 
00548        sprintf(ok_response,
00549               "+OK Begin TLS negotiation now\r\n");
00550        sprintf(nosup_response,
00551               "-ERR TLS not supported here\r\n");
00552        sprintf(error_response,
00553               "-ERR Internal error\r\n");
00554        CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
00555 }
00556 
00557 
00558 
00559 
00560 
00561 
00562 
00563 /* 
00564  * Main command loop for POP3 sessions.
00565  */
00566 void pop3_command_loop(void) {
00567        char cmdbuf[SIZ];
00568 
00569        time(&CC->lastcmd);
00570        memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
00571        if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
00572               syslog(LOG_ERR, "POP3 client disconnected: ending session.");
00573               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
00574               return;
00575        }
00576        if (!strncasecmp(cmdbuf, "PASS", 4)) {
00577               syslog(LOG_INFO, "POP3: PASS...");
00578        }
00579        else {
00580               syslog(LOG_INFO, "POP3: %s", cmdbuf);
00581        }
00582        while (strlen(cmdbuf) < 5) strcat(cmdbuf, " ");
00583 
00584        if (!strncasecmp(cmdbuf, "NOOP", 4)) {
00585               cprintf("+OK No operation.\r\n");
00586        }
00587 
00588        else if (!strncasecmp(cmdbuf, "CAPA", 4)) {
00589               pop3_capa();
00590        }
00591 
00592        else if (!strncasecmp(cmdbuf, "QUIT", 4)) {
00593               cprintf("+OK Goodbye...\r\n");
00594               pop3_update();
00595               CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
00596               return;
00597        }
00598 
00599        else if (!strncasecmp(cmdbuf, "USER", 4)) {
00600               pop3_user(&cmdbuf[5]);
00601        }
00602 
00603        else if (!strncasecmp(cmdbuf, "PASS", 4)) {
00604               pop3_pass(&cmdbuf[5]);
00605        }
00606 
00607 #ifdef HAVE_OPENSSL
00608        else if (!strncasecmp(cmdbuf, "STLS", 4)) {
00609               pop3_stls();
00610        }
00611 #endif
00612 
00613        else if (!CC->logged_in) {
00614               cprintf("-ERR Not logged in.\r\n");
00615        }
00616        
00617        else if (CC->nologin) {
00618               cprintf("-ERR System busy, try later.\r\n");
00619               CC->kill_me = KILLME_NOLOGIN;
00620        }
00621 
00622        else if (!strncasecmp(cmdbuf, "LIST", 4)) {
00623               pop3_list(&cmdbuf[5]);
00624        }
00625 
00626        else if (!strncasecmp(cmdbuf, "STAT", 4)) {
00627               pop3_stat(&cmdbuf[5]);
00628        }
00629 
00630        else if (!strncasecmp(cmdbuf, "RETR", 4)) {
00631               pop3_retr(&cmdbuf[5]);
00632        }
00633 
00634        else if (!strncasecmp(cmdbuf, "DELE", 4)) {
00635               pop3_dele(&cmdbuf[5]);
00636        }
00637 
00638        else if (!strncasecmp(cmdbuf, "RSET", 4)) {
00639               pop3_rset(&cmdbuf[5]);
00640        }
00641 
00642        else if (!strncasecmp(cmdbuf, "UIDL", 4)) {
00643               pop3_uidl(&cmdbuf[5]);
00644        }
00645 
00646        else if (!strncasecmp(cmdbuf, "TOP", 3)) {
00647               pop3_top(&cmdbuf[4]);
00648        }
00649 
00650        else if (!strncasecmp(cmdbuf, "LAST", 4)) {
00651               pop3_last(&cmdbuf[4]);
00652        }
00653 
00654        else {
00655               cprintf("-ERR I'm afraid I can't do that.\r\n");
00656        }
00657 
00658 }
00659 
00660 const char *CitadelServicePop3="POP3";
00661 const char *CitadelServicePop3S="POP3S";
00662 
00663 
00664 CTDL_MODULE_INIT(pop3)
00665 {
00666        if(!threading)
00667        {
00668               CtdlRegisterServiceHook(config.c_pop3_port,
00669                                    NULL,
00670                                    pop3_greeting,
00671                                    pop3_command_loop,
00672                                    NULL,
00673                                    CitadelServicePop3);
00674 #ifdef HAVE_OPENSSL
00675               CtdlRegisterServiceHook(config.c_pop3s_port,
00676                                    NULL,
00677                                    pop3s_greeting,
00678                                    pop3_command_loop,
00679                                    NULL,
00680                                    CitadelServicePop3S);
00681 #endif
00682               CtdlRegisterSessionHook(pop3_cleanup_function, EVT_STOP, PRIO_STOP + 30);
00683        }
00684        
00685        /* return our module name for the log */
00686        return "pop3";
00687 }