"Fossies" - the Fresh Open Source Software Archive

Member "citadel/modules/smtp/serv_smtp.c" (5 Jun 2021, 28744 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_smtp.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  * This module is an SMTP and ESMTP server for the Citadel system.
    3  * It is compliant with all of the following:
    4  *
    5  * RFC  821 - Simple Mail Transfer Protocol
    6  * RFC  876 - Survey of SMTP Implementations
    7  * RFC 1047 - Duplicate messages and SMTP
    8  * RFC 1652 - 8 bit MIME
    9  * RFC 1869 - Extended Simple Mail Transfer Protocol
   10  * RFC 1870 - SMTP Service Extension for Message Size Declaration
   11  * RFC 2033 - Local Mail Transfer Protocol
   12  * RFC 2197 - SMTP Service Extension for Command Pipelining
   13  * RFC 2476 - Message Submission
   14  * RFC 2487 - SMTP Service Extension for Secure SMTP over TLS
   15  * RFC 2554 - SMTP Service Extension for Authentication
   16  * RFC 2821 - Simple Mail Transfer Protocol
   17  * RFC 2822 - Internet Message Format
   18  * RFC 2920 - SMTP Service Extension for Command Pipelining
   19  *  
   20  * The VRFY and EXPN commands have been removed from this implementation
   21  * because nobody uses these commands anymore, except for spammers.
   22  *
   23  * Copyright (c) 1998-2021 by the citadel.org team
   24  *
   25  * This program is open source software; you can redistribute it and/or modify
   26  * it under the terms of the GNU General Public License version 3.
   27  *  
   28  * This program is distributed in the hope that it will be useful,
   29  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   30  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   31  * GNU General Public License for more details.
   32  */
   33 
   34 #include "sysdep.h"
   35 #include <stdlib.h>
   36 #include <unistd.h>
   37 #include <stdio.h>
   38 #include <termios.h>
   39 #include <fcntl.h>
   40 #include <signal.h>
   41 #include <pwd.h>
   42 #include <errno.h>
   43 #include <sys/types.h>
   44 #include <syslog.h>
   45 #include <time.h>
   46 #include <sys/wait.h>
   47 #include <ctype.h>
   48 #include <string.h>
   49 #include <limits.h>
   50 #include <sys/socket.h>
   51 #include <netinet/in.h>
   52 #include <arpa/inet.h>
   53 #include <libcitadel.h>
   54 #include "citadel.h"
   55 #include "server.h"
   56 #include "citserver.h"
   57 #include "support.h"
   58 #include "config.h"
   59 #include "control.h"
   60 #include "user_ops.h"
   61 #include "room_ops.h"
   62 #include "database.h"
   63 #include "msgbase.h"
   64 #include "internet_addressing.h"
   65 #include "genstamp.h"
   66 #include "domain.h"
   67 #include "clientsocket.h"
   68 #include "locate_host.h"
   69 #include "citadel_dirs.h"
   70 #include "ctdl_module.h"
   71 
   72 #include "smtp_util.h"
   73 
   74 enum {              /* Command states for login authentication */
   75     smtp_command,
   76     smtp_user,
   77     smtp_password,
   78     smtp_plain
   79 };
   80 
   81 enum SMTP_FLAGS {
   82     HELO,
   83     EHLO,
   84     LHLO
   85 };
   86 
   87 
   88 /*
   89  * Here's where our SMTP session begins its happy day.
   90  */
   91 void smtp_greeting(int is_msa) {
   92     char message_to_spammer[1024];
   93 
   94     strcpy(CC->cs_clientname, "SMTP session");
   95     CC->internal_pgm = 1;
   96     CC->cs_flags |= CS_STEALTH;
   97     CC->session_specific_data = malloc(sizeof(struct citsmtp));
   98     memset(SMTP, 0, sizeof(struct citsmtp));
   99     SMTP->is_msa = is_msa;
  100     SMTP->Cmd = NewStrBufPlain(NULL, SIZ);
  101     SMTP->helo_node = NewStrBuf();
  102     SMTP->from = NewStrBufPlain(NULL, SIZ);
  103     SMTP->recipients = NewStrBufPlain(NULL, SIZ);
  104     SMTP->OneRcpt = NewStrBufPlain(NULL, SIZ);
  105     SMTP->preferred_sender_email = NULL;
  106     SMTP->preferred_sender_name = NULL;
  107 
  108     /* If this config option is set, reject connections from problem
  109      * addresses immediately instead of after they execute a RCPT
  110      */
  111     if ( (CtdlGetConfigInt("c_rbl_at_greeting")) && (SMTP->is_msa == 0) ) {
  112         if (rbl_check(CC->cs_addr, message_to_spammer)) {
  113             if (server_shutting_down)
  114                 cprintf("421 %s\r\n", message_to_spammer);
  115             else
  116                 cprintf("550 %s\r\n", message_to_spammer);
  117             CC->kill_me = KILLME_SPAMMER;
  118             /* no need to free_recipients(valid), it's not allocated yet */
  119             return;
  120         }
  121     }
  122 
  123     /* Otherwise we're either clean or we check later. */
  124 
  125     if (CC->nologin==1) {
  126         cprintf("451 Too many connections are already open; please try again later.\r\n");
  127         CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
  128         /* no need to free_recipients(valid), it's not allocated yet */
  129         return;
  130     }
  131 
  132     /* Note: the FQDN *must* appear as the first thing after the 220 code.
  133      * Some clients (including citmail.c) depend on it being there.
  134      */
  135     cprintf("220 %s ESMTP Citadel server ready.\r\n", CtdlGetConfigStr("c_fqdn"));
  136 }
  137 
  138 
  139 /*
  140  * SMTPS is just like SMTP, except it goes crypto right away.
  141  */
  142 void smtps_greeting(void) {
  143     CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
  144 #ifdef HAVE_OPENSSL
  145     if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;      /* kill session if no crypto */
  146 #endif
  147     smtp_greeting(0);
  148 }
  149 
  150 
  151 /*
  152  * SMTP MSA port requires authentication.
  153  */
  154 void smtp_msa_greeting(void) {
  155     smtp_greeting(1);
  156 }
  157 
  158 
  159 /*
  160  * LMTP is like SMTP but with some extra bonus footage added.
  161  */
  162 void lmtp_greeting(void) {
  163 
  164     smtp_greeting(0);
  165     SMTP->is_lmtp = 1;
  166 }
  167 
  168 
  169 /* 
  170  * Generic SMTP MTA greeting
  171  */
  172 void smtp_mta_greeting(void) {
  173     smtp_greeting(0);
  174 }
  175 
  176 
  177 /*
  178  * We also have an unfiltered LMTP socket that bypasses spam filters.
  179  */
  180 void lmtp_unfiltered_greeting(void) {
  181     smtp_greeting(0);
  182     SMTP->is_lmtp = 1;
  183     SMTP->is_unfiltered = 1;
  184 }
  185 
  186 
  187 /*
  188  * Login greeting common to all auth methods
  189  */
  190 void smtp_auth_greeting(void) {
  191     cprintf("235 Hello, %s\r\n", CC->user.fullname);
  192     syslog(LOG_INFO, "serv_smtp: SMTP authenticated %s", CC->user.fullname);
  193     CC->internal_pgm = 0;
  194     CC->cs_flags &= ~CS_STEALTH;
  195 }
  196 
  197 
  198 /*
  199  * Implement HELO and EHLO commands.
  200  *
  201  * which_command:  0=HELO, 1=EHLO, 2=LHLO
  202  */
  203 void smtp_hello(int which_command) {
  204 
  205     if (StrLength(SMTP->Cmd) >= 6) {
  206         StrBufAppendBuf(SMTP->helo_node, SMTP->Cmd, 5);
  207     }
  208 
  209     if ( (which_command != LHLO) && (SMTP->is_lmtp) ) {
  210         cprintf("500 Only LHLO is allowed when running LMTP\r\n");
  211         return;
  212     }
  213 
  214     if ( (which_command == LHLO) && (SMTP->is_lmtp == 0) ) {
  215         cprintf("500 LHLO is only allowed when running LMTP\r\n");
  216         return;
  217     }
  218 
  219     if (which_command == HELO) {
  220         cprintf("250 Hello %s (%s [%s])\r\n",
  221             ChrPtr(SMTP->helo_node),
  222             CC->cs_host,
  223             CC->cs_addr
  224         );
  225     }
  226     else {
  227         if (which_command == EHLO) {
  228             cprintf("250-Hello %s (%s [%s])\r\n",
  229                 ChrPtr(SMTP->helo_node),
  230                 CC->cs_host,
  231                 CC->cs_addr
  232             );
  233         }
  234         else {
  235             cprintf("250-Greetings and joyous salutations.\r\n");
  236         }
  237         cprintf("250-HELP\r\n");
  238         cprintf("250-SIZE %ld\r\n", CtdlGetConfigLong("c_maxmsglen"));
  239 
  240 #ifdef HAVE_OPENSSL
  241         /*
  242          * Offer TLS, but only if TLS is not already active.
  243          * Furthermore, only offer TLS when running on
  244          * the SMTP-MSA port, not on the SMTP-MTA port, due to
  245          * questionable reliability of TLS in certain sending MTA's.
  246          */
  247         if ( (!CC->redirect_ssl) && (SMTP->is_msa) ) {
  248             cprintf("250-STARTTLS\r\n");
  249         }
  250 #endif  /* HAVE_OPENSSL */
  251 
  252         cprintf("250-AUTH LOGIN PLAIN\r\n"
  253             "250-AUTH=LOGIN PLAIN\r\n"
  254             "250 8BITMIME\r\n"
  255         );
  256     }
  257 }
  258 
  259 
  260 /*
  261  * Backend function for smtp_webcit_preferences_hack().
  262  * Look at a message and determine if it's the preferences file.
  263  */
  264 void smtp_webcit_preferences_hack_backend(long msgnum, void *userdata) {
  265     struct CtdlMessage *msg;
  266     char **webcit_conf = (char **) userdata;
  267 
  268     if (*webcit_conf) {
  269         return; // already got it
  270     }
  271 
  272     msg = CtdlFetchMessage(msgnum, 1);
  273     if (msg == NULL) {
  274         return;
  275     }
  276 
  277     if ( !CM_IsEmpty(msg, eMsgSubject) && (!strcasecmp(msg->cm_fields[eMsgSubject], "__ WebCit Preferences __"))) {
  278         /* This is it!  Change ownership of the message text so it doesn't get freed. */
  279         *webcit_conf = (char *)msg->cm_fields[eMesageText];
  280         msg->cm_fields[eMesageText] = NULL;
  281     }
  282     CM_Free(msg);
  283 }
  284 
  285 
  286 /*
  287  * The configuration item for the user's preferred display name for outgoing email is, unfortunately,
  288  * stored in the account's WebCit configuration.  We have to fetch it now.
  289  */
  290 void smtp_webcit_preferences_hack(void) {
  291     char config_roomname[ROOMNAMELEN];
  292     char *webcit_conf = NULL;
  293 
  294     snprintf(config_roomname, sizeof config_roomname, "%010ld.%s", CC->user.usernum, USERCONFIGROOM);
  295     if (CtdlGetRoom(&CC->room, config_roomname) != 0) {
  296         return;
  297     }
  298 
  299     /*
  300      * Find the WebCit configuration message
  301      */
  302 
  303     CtdlForEachMessage(MSGS_ALL, 1, NULL, NULL, NULL, smtp_webcit_preferences_hack_backend, (void *)&webcit_conf);
  304 
  305     if (!webcit_conf) {
  306         return;
  307     }
  308 
  309     /* Parse the webcit configuration and attempt to do something useful with it */
  310     char *str = webcit_conf;
  311     char *saveptr = str;
  312     char *this_line = NULL;
  313     while (this_line = strtok_r(str, "\n", &saveptr), this_line != NULL) {
  314         str = NULL;
  315         if (!strncasecmp(this_line, "defaultfrom|", 12)) {
  316             SMTP->preferred_sender_email = NewStrBufPlain(&this_line[12], -1);
  317         }
  318         if (!strncasecmp(this_line, "defaultname|", 12)) {
  319             SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
  320         }
  321         if ((!strncasecmp(this_line, "defaultname|", 12)) && (SMTP->preferred_sender_name == NULL)) {
  322             SMTP->preferred_sender_name = NewStrBufPlain(&this_line[12], -1);
  323         }
  324 
  325     }
  326     free(webcit_conf);
  327 }
  328 
  329 
  330 /*
  331  * Implement HELP command.
  332  */
  333 void smtp_help(void) {
  334     cprintf("214 RTFM http://www.ietf.org/rfc/rfc2821.txt\r\n");
  335 }
  336 
  337 
  338 /*
  339  *
  340  */
  341 void smtp_get_user(int offset) {
  342     char buf[SIZ];
  343 
  344     StrBuf *UserName = NewStrBufDup(SMTP->Cmd);
  345     StrBufCutLeft(UserName, offset);
  346     StrBufDecodeBase64(UserName);
  347 
  348     if (CtdlLoginExistingUser(ChrPtr(UserName)) == login_ok) {
  349         size_t len = CtdlEncodeBase64(buf, "Password:", 9, 0);
  350 
  351         if (buf[len - 1] == '\n') {
  352             buf[len - 1] = '\0';
  353         }
  354         cprintf("334 %s\r\n", buf);
  355         SMTP->command_state = smtp_password;
  356     }
  357     else {
  358         cprintf("500 No such user.\r\n");
  359         SMTP->command_state = smtp_command;
  360     }
  361     FreeStrBuf(&UserName);
  362 }
  363 
  364 
  365 /*
  366  *
  367  */
  368 void smtp_get_pass(void)
  369 {
  370     char password[SIZ];
  371 
  372     memset(password, 0, sizeof(password));
  373     StrBufDecodeBase64(SMTP->Cmd);
  374     syslog(LOG_DEBUG, "serv_smtp: trying <%s>", password);
  375     if (CtdlTryPassword(SKEY(SMTP->Cmd)) == pass_ok) {
  376         smtp_auth_greeting();
  377     }
  378     else {
  379         cprintf("535 Authentication failed.\r\n");
  380     }
  381     SMTP->command_state = smtp_command;
  382 }
  383 
  384 
  385 /*
  386  * Back end for PLAIN auth method (either inline or multistate)
  387  */
  388 void smtp_try_plain(void) {
  389     const char*decoded_authstring;
  390     char ident[256] = "";
  391     char user[256] = "";
  392     char pass[256] = "";
  393     int result;
  394 
  395     long decoded_len;
  396     long len = 0;
  397     long plen = 0;
  398 
  399     memset(pass, 0, sizeof(pass));
  400     decoded_len = StrBufDecodeBase64(SMTP->Cmd);
  401 
  402     if (decoded_len > 0) {
  403         decoded_authstring = ChrPtr(SMTP->Cmd);
  404 
  405         len = safestrncpy(ident, decoded_authstring, sizeof ident);
  406 
  407         decoded_len -= len - 1;
  408         decoded_authstring += len + 1;
  409 
  410         if (decoded_len > 0) {
  411             len = safestrncpy(user, decoded_authstring, sizeof user);
  412 
  413             decoded_authstring += len + 1;
  414             decoded_len -= len - 1;
  415         }
  416 
  417         if (decoded_len > 0) {
  418             plen = safestrncpy(pass, decoded_authstring, sizeof pass);
  419 
  420             if (plen < 0)
  421                 plen = sizeof(pass) - 1;
  422         }
  423     }
  424 
  425     SMTP->command_state = smtp_command;
  426 
  427     if (!IsEmptyStr(ident)) {
  428         result = CtdlLoginExistingUser(ident);
  429     }
  430     else {
  431         result = CtdlLoginExistingUser(user);
  432     }
  433 
  434     if (result == login_ok) {
  435         if (CtdlTryPassword(pass, plen) == pass_ok) {
  436             smtp_webcit_preferences_hack();
  437             smtp_auth_greeting();
  438             return;
  439         }
  440     }
  441     cprintf("504 Authentication failed.\r\n");
  442 }
  443 
  444 
  445 /*
  446  * Attempt to perform authenticated SMTP
  447  */
  448 void smtp_auth(void) {
  449     char username_prompt[64];
  450     char method[64];
  451     char encoded_authstring[1024];
  452 
  453     if (CC->logged_in) {
  454         cprintf("504 Already logged in.\r\n");
  455         return;
  456     }
  457 
  458     if (StrLength(SMTP->Cmd) < 6) {
  459         cprintf("501 Syntax error\r\n");
  460         return;
  461     }
  462 
  463     extract_token(method, ChrPtr(SMTP->Cmd) + 5, 0, ' ', sizeof method);
  464 
  465     if (!strncasecmp(method, "login", 5) ) {
  466         if (StrLength(SMTP->Cmd) >= 12) {
  467             syslog(LOG_DEBUG, "serv_smtp: username <%s> supplied inline", ChrPtr(SMTP->Cmd)+11);
  468             smtp_get_user(11);
  469         }
  470         else {
  471             size_t len = CtdlEncodeBase64(username_prompt, "Username:", 9, 0);
  472             if (username_prompt[len - 1] == '\n') {
  473                 username_prompt[len - 1] = '\0';
  474             }
  475             cprintf("334 %s\r\n", username_prompt);
  476             SMTP->command_state = smtp_user;
  477         }
  478         return;
  479     }
  480 
  481     if (!strncasecmp(method, "plain", 5) ) {
  482         long len;
  483         if (num_tokens(ChrPtr(SMTP->Cmd) + 5, ' ') < 2) {
  484             cprintf("334 \r\n");
  485             SMTP->command_state = smtp_plain;
  486             return;
  487         }
  488 
  489         len = extract_token(encoded_authstring, 
  490                     ChrPtr(SMTP->Cmd) + 5,
  491                     1, ' ',
  492                     sizeof encoded_authstring);
  493         StrBufPlain(SMTP->Cmd, encoded_authstring, len);
  494         smtp_try_plain();
  495         return;
  496     }
  497 
  498     cprintf("504 Unknown authentication method.\r\n");
  499     return;
  500 }
  501 
  502 
  503 /*
  504  * Implements the RSET (reset state) command.
  505  * Currently this just zeroes out the state buffer.  If pointers to data
  506  * allocated with malloc() are ever placed in the state buffer, we have to
  507  * be sure to free() them first!
  508  *
  509  * Set do_response to nonzero to output the SMTP RSET response code.
  510  */
  511 void smtp_rset(int do_response) {
  512     /*
  513      * Our entire SMTP state is discarded when a RSET command is issued,
  514      * but we need to preserve this one little piece of information, so
  515      * we save it for later.
  516      */
  517 
  518     FlushStrBuf(SMTP->Cmd);
  519     FlushStrBuf(SMTP->helo_node);
  520     FlushStrBuf(SMTP->from);
  521     FlushStrBuf(SMTP->recipients);
  522     FlushStrBuf(SMTP->OneRcpt);
  523 
  524     SMTP->command_state = 0;
  525     SMTP->number_of_recipients = 0;
  526     SMTP->delivery_mode = 0;
  527     SMTP->message_originated_locally = 0;
  528     SMTP->is_msa = 0;
  529     /*
  530      * we must remember is_lmtp & is_unfiltered.
  531      */
  532 
  533     /*
  534      * It is somewhat ambiguous whether we want to log out when a RSET
  535      * command is issued.  Here's the code to do it.  It is commented out
  536      * because some clients (such as Pine) issue RSET commands before
  537      * each message, but still expect to be logged in.
  538      *
  539      * if (CC->logged_in) {
  540      *  logout(CC);
  541      * }
  542      */
  543 
  544     if (do_response) {
  545         cprintf("250 Zap!\r\n");
  546     }
  547 }
  548 
  549 
  550 /*
  551  * Clear out the portions of the state buffer that need to be cleared out
  552  * after the DATA command finishes.
  553  */
  554 void smtp_data_clear(void) {
  555     FlushStrBuf(SMTP->from);
  556     FlushStrBuf(SMTP->recipients);
  557     FlushStrBuf(SMTP->OneRcpt);
  558     SMTP->number_of_recipients = 0;
  559     SMTP->delivery_mode = 0;
  560     SMTP->message_originated_locally = 0;
  561 }
  562 
  563 
  564 /*
  565  * Implements the "MAIL FROM:" command
  566  */
  567 void smtp_mail(void) {
  568     char user[SIZ];
  569     char node[SIZ];
  570     char name[SIZ];
  571 
  572     if (StrLength(SMTP->from) > 0) {
  573         cprintf("503 Only one sender permitted\r\n");
  574         return;
  575     }
  576 
  577     if (StrLength(SMTP->Cmd) < 6) {
  578         cprintf("501 Syntax error\r\n");
  579         return;
  580     }
  581 
  582     if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "From:", 5)) {
  583         cprintf("501 Syntax error\r\n");
  584         return;
  585     }
  586 
  587     StrBufAppendBuf(SMTP->from, SMTP->Cmd, 5);
  588     StrBufTrim(SMTP->from);
  589     if (strchr(ChrPtr(SMTP->from), '<') != NULL) {
  590         StrBufStripAllBut(SMTP->from, '<', '>');
  591     }
  592 
  593     /* We used to reject empty sender names, until it was brought to our
  594      * attention that RFC1123 5.2.9 requires that this be allowed.  So now
  595      * we allow it, but replace the empty string with a fake
  596      * address so we don't have to contend with the empty string causing
  597      * other code to fail when it's expecting something there.
  598      */
  599     if (StrLength(SMTP->from) == 0) {
  600         StrBufPlain(SMTP->from, HKEY("someone@example.com"));
  601     }
  602 
  603     /* If this SMTP connection is from a logged-in user, force the 'from'
  604      * to be the user's Internet e-mail address as Citadel knows it.
  605      */
  606     if (CC->logged_in) {
  607         StrBufPlain(SMTP->from, CC->cs_inet_email, -1);
  608         cprintf("250 Sender ok <%s>\r\n", ChrPtr(SMTP->from));
  609         SMTP->message_originated_locally = 1;
  610         return;
  611     }
  612 
  613     else if (SMTP->is_lmtp) {
  614         /* Bypass forgery checking for LMTP */
  615     }
  616 
  617     /* Otherwise, make sure outsiders aren't trying to forge mail from
  618      * this system (unless, of course, c_allow_spoofing is enabled)
  619      */
  620     else if (CtdlGetConfigInt("c_allow_spoofing") == 0) {
  621         process_rfc822_addr(ChrPtr(SMTP->from), user, node, name);
  622         syslog(LOG_DEBUG, "serv_smtp: claimed envelope sender is '%s' == '%s' @ '%s' ('%s')",
  623             ChrPtr(SMTP->from), user, node, name
  624         );
  625         if (CtdlHostAlias(node) != hostalias_nomatch) {
  626             cprintf("550 You must log in to send mail from %s\r\n", node);
  627             FlushStrBuf(SMTP->from);
  628             syslog(LOG_DEBUG, "serv_smtp: rejecting unauthenticated mail from %s", node);
  629             return;
  630         }
  631     }
  632 
  633     cprintf("250 Sender ok\r\n");
  634 }
  635 
  636 
  637 /*
  638  * Implements the "RCPT To:" command
  639  */
  640 void smtp_rcpt(void) {
  641     char message_to_spammer[SIZ];
  642     struct recptypes *valid = NULL;
  643 
  644     if (StrLength(SMTP->from) == 0) {
  645         cprintf("503 Need MAIL before RCPT\r\n");
  646         return;
  647     }
  648     
  649     if (StrLength(SMTP->Cmd) < 6) {
  650         cprintf("501 Syntax error\r\n");
  651         return;
  652     }
  653 
  654     if (strncasecmp(ChrPtr(SMTP->Cmd) + 5, "To:", 3)) {
  655         cprintf("501 Syntax error\r\n");
  656         return;
  657     }
  658 
  659     if ( (SMTP->is_msa) && (!CC->logged_in) ) {
  660         cprintf("550 You must log in to send mail on this port.\r\n");
  661         FlushStrBuf(SMTP->from);
  662         return;
  663     }
  664 
  665     FlushStrBuf(SMTP->OneRcpt);
  666     StrBufAppendBuf(SMTP->OneRcpt, SMTP->Cmd, 8);
  667     StrBufTrim(SMTP->OneRcpt);
  668     StrBufStripAllBut(SMTP->OneRcpt, '<', '>');
  669 
  670     if ( (StrLength(SMTP->OneRcpt) + StrLength(SMTP->recipients)) >= SIZ) {
  671         cprintf("452 Too many recipients\r\n");
  672         return;
  673     }
  674 
  675     /* RBL check */
  676     if ( (!CC->logged_in)   /* Don't RBL authenticated users */
  677        && (!SMTP->is_lmtp) ) {  /* Don't RBL LMTP clients */
  678         if (CtdlGetConfigInt("c_rbl_at_greeting") == 0) {   /* Don't RBL again if we already did it */
  679             if (rbl_check(CC->cs_addr, message_to_spammer)) {
  680                 if (server_shutting_down)
  681                     cprintf("421 %s\r\n", message_to_spammer);
  682                 else
  683                     cprintf("550 %s\r\n", message_to_spammer);
  684                 /* no need to free_recipients(valid), it's not allocated yet */
  685                 return;
  686             }
  687         }
  688     }
  689 
  690     valid = validate_recipients(
  691         ChrPtr(SMTP->OneRcpt), 
  692         smtp_get_Recipients(),
  693         (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
  694     );
  695     if (valid->num_error != 0) {
  696         cprintf("550 %s\r\n", valid->errormsg);
  697         free_recipients(valid);
  698         return;
  699     }
  700 
  701     if (valid->num_internet > 0) {
  702         if (CC->logged_in) {
  703                         if (CtdlCheckInternetMailPermission(&CC->user)==0) {
  704                 cprintf("551 <%s> - you do not have permission to send Internet mail\r\n", 
  705                     ChrPtr(SMTP->OneRcpt));
  706                                 free_recipients(valid);
  707                                 return;
  708                         }
  709                 }
  710     }
  711 
  712     if (valid->num_internet > 0) {
  713         if ( (SMTP->message_originated_locally == 0)
  714            && (SMTP->is_lmtp == 0) ) {
  715             cprintf("551 <%s> - relaying denied\r\n", ChrPtr(SMTP->OneRcpt));
  716             free_recipients(valid);
  717             return;
  718         }
  719     }
  720 
  721     cprintf("250 RCPT ok <%s>\r\n", ChrPtr(SMTP->OneRcpt));
  722     if (StrLength(SMTP->recipients) > 0) {
  723         StrBufAppendBufPlain(SMTP->recipients, HKEY(","), 0);
  724     }
  725     StrBufAppendBuf(SMTP->recipients, SMTP->OneRcpt, 0);
  726     SMTP->number_of_recipients ++;
  727     if (valid != NULL)  {
  728         free_recipients(valid);
  729     }
  730 }
  731 
  732 
  733 /*
  734  * Implements the DATA command
  735  */
  736 void smtp_data(void) {
  737     StrBuf *body;
  738     StrBuf *defbody; 
  739     struct CtdlMessage *msg = NULL;
  740     long msgnum = (-1L);
  741     char nowstamp[SIZ];
  742     struct recptypes *valid;
  743     int scan_errors;
  744     int i;
  745 
  746     if (StrLength(SMTP->from) == 0) {
  747         cprintf("503 Need MAIL command first.\r\n");
  748         return;
  749     }
  750 
  751     if (SMTP->number_of_recipients < 1) {
  752         cprintf("503 Need RCPT command first.\r\n");
  753         return;
  754     }
  755 
  756     cprintf("354 Transmit message now - terminate with '.' by itself\r\n");
  757     
  758     datestring(nowstamp, sizeof nowstamp, time(NULL), DATESTRING_RFC822);
  759     defbody = NewStrBufPlain(NULL, SIZ);
  760 
  761     if (defbody != NULL) {
  762         if (SMTP->is_lmtp && (CC->cs_UDSclientUID != -1)) {
  763             StrBufPrintf(
  764                 defbody,
  765                 "Received: from %s (Citadel from userid %ld)\n"
  766                 "   by %s; %s\n",
  767                 ChrPtr(SMTP->helo_node),
  768                 (long int) CC->cs_UDSclientUID,
  769                 CtdlGetConfigStr("c_fqdn"),
  770                 nowstamp);
  771         }
  772         else {
  773             StrBufPrintf(
  774                 defbody,
  775                 "Received: from %s (%s [%s])\n"
  776                 "   by %s; %s\n",
  777                 ChrPtr(SMTP->helo_node),
  778                 CC->cs_host,
  779                 CC->cs_addr,
  780                 CtdlGetConfigStr("c_fqdn"),
  781                 nowstamp);
  782         }
  783     }
  784     body = CtdlReadMessageBodyBuf(HKEY("."), CtdlGetConfigLong("c_maxmsglen"), defbody, 1);
  785     FreeStrBuf(&defbody);
  786     if (body == NULL) {
  787         cprintf("550 Unable to save message: internal error.\r\n");
  788         return;
  789     }
  790 
  791     syslog(LOG_DEBUG, "serv_smtp: converting message...");
  792     msg = convert_internet_message_buf(&body);
  793 
  794     /* If the user is locally authenticated, FORCE the From: header to
  795      * show up as the real sender.  Yes, this violates the RFC standard,
  796      * but IT MAKES SENSE.  If you prefer strict RFC adherence over
  797      * common sense, you can disable this in the configuration.
  798      *
  799      * We also set the "message room name" ('O' field) to MAILROOM
  800      * (which is Mail> on most systems) to prevent it from getting set
  801      * to something ugly like "0000058008.Sent Items>" when the message
  802      * is read with a Citadel client.
  803      */
  804     if ( (CC->logged_in) && (CtdlGetConfigInt("c_rfc822_strict_from") != CFG_SMTP_FROM_NOFILTER) ) {
  805         int validemail = 0;
  806         
  807         if (!CM_IsEmpty(msg, erFc822Addr)       &&
  808             ((CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_CORRECT) || 
  809              (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)    )  )
  810         {
  811             if (!IsEmptyStr(CC->cs_inet_email))
  812                 validemail = strcmp(CC->cs_inet_email, msg->cm_fields[erFc822Addr]) == 0;
  813             if ((!validemail) && 
  814                 (!IsEmptyStr(CC->cs_inet_other_emails)))
  815             {
  816                 int num_secondary_emails = 0;
  817                 int i;
  818                 num_secondary_emails = num_tokens(CC->cs_inet_other_emails, '|');
  819                 for (i=0; i < num_secondary_emails && !validemail; ++i) {
  820                     char buf[256];
  821                     extract_token(buf, CC->cs_inet_other_emails,i,'|',sizeof CC->cs_inet_other_emails);
  822                     validemail = strcmp(buf, msg->cm_fields[erFc822Addr]) == 0;
  823                 }
  824             }
  825         }
  826 
  827         if (!validemail && (CtdlGetConfigInt("c_rfc822_strict_from") == CFG_SMTP_FROM_REJECT)) {
  828             syslog(LOG_ERR, "serv_smtp: invalid sender '%s' - rejecting this message", msg->cm_fields[erFc822Addr]);
  829             cprintf("550 Invalid sender '%s' - rejecting this message.\r\n", msg->cm_fields[erFc822Addr]);
  830             return;
  831         }
  832 
  833             CM_SetField(msg, eOriginalRoom, HKEY(MAILROOM));
  834         if (SMTP->preferred_sender_name != NULL)
  835             CM_SetField(msg, eAuthor, SKEY(SMTP->preferred_sender_name));
  836         else 
  837             CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
  838 
  839         if (!validemail) {
  840             if (SMTP->preferred_sender_email != NULL) {
  841                 CM_SetField(msg, erFc822Addr, SKEY(SMTP->preferred_sender_email));
  842             }
  843             else {
  844                 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
  845             }
  846         }
  847     }
  848 
  849     /* Set the "envelope from" address */
  850     CM_SetField(msg, eMessagePath, SKEY(SMTP->from));
  851 
  852     /* Set the "envelope to" address */
  853     CM_SetField(msg, eenVelopeTo, SKEY(SMTP->recipients));
  854 
  855     /* Submit the message into the Citadel system. */
  856     valid = validate_recipients(
  857         ChrPtr(SMTP->recipients),
  858         smtp_get_Recipients(),
  859         (SMTP->is_lmtp)? POST_LMTP: (CC->logged_in)? POST_LOGGED_IN: POST_EXTERNAL
  860     );
  861 
  862     /* If there are modules that want to scan this message before final
  863      * submission (such as virus checkers or spam filters), call them now
  864      * and give them an opportunity to reject the message.
  865      */
  866     if (SMTP->is_unfiltered) {
  867         scan_errors = 0;
  868     }
  869     else {
  870         scan_errors = PerformMessageHooks(msg, valid, EVT_SMTPSCAN);
  871     }
  872 
  873     if (scan_errors > 0) {  /* We don't want this message! */
  874 
  875         if (CM_IsEmpty(msg, eErrorMsg)) {
  876             CM_SetField(msg, eErrorMsg, HKEY("Message rejected by filter"));
  877         }
  878 
  879         StrBufPrintf(SMTP->OneRcpt, "550 %s\r\n", msg->cm_fields[eErrorMsg]);
  880     }
  881     
  882     else {          /* Ok, we'll accept this message. */
  883         msgnum = CtdlSubmitMsg(msg, valid, "");
  884         if (msgnum > 0L) {
  885             StrBufPrintf(SMTP->OneRcpt, "250 Message accepted.\r\n");
  886         }
  887         else {
  888             StrBufPrintf(SMTP->OneRcpt, "550 Internal delivery error\r\n");
  889         }
  890     }
  891 
  892     /* For SMTP and ESMTP, just print the result message.  For LMTP, we
  893      * have to print one result message for each recipient.  Since there
  894      * is nothing in Citadel which would cause different recipients to
  895      * have different results, we can get away with just spitting out the
  896      * same message once for each recipient.
  897      */
  898     if (SMTP->is_lmtp) {
  899         for (i=0; i<SMTP->number_of_recipients; ++i) {
  900             cputbuf(SMTP->OneRcpt);
  901         }
  902     }
  903     else {
  904         cputbuf(SMTP->OneRcpt);
  905     }
  906 
  907     /* Write something to the syslog(which may or may not be where the
  908      * rest of the Citadel logs are going; some sysadmins want LOG_MAIL).
  909      */
  910     syslog((LOG_MAIL | LOG_INFO),
  911             "%ld: from=<%s>, nrcpts=%d, relay=%s [%s], stat=%s",
  912             msgnum,
  913             ChrPtr(SMTP->from),
  914             SMTP->number_of_recipients,
  915             CC->cs_host,
  916             CC->cs_addr,
  917             ChrPtr(SMTP->OneRcpt)
  918     );
  919 
  920     /* Clean up */
  921     CM_Free(msg);
  922     free_recipients(valid);
  923     smtp_data_clear();  /* clear out the buffers now */
  924 }
  925 
  926 
  927 /*
  928  * Implements the STARTTLS command
  929  */
  930 void smtp_starttls(void) {
  931     char ok_response[SIZ];
  932     char nosup_response[SIZ];
  933     char error_response[SIZ];
  934 
  935     sprintf(ok_response, "220 Begin TLS negotiation now\r\n");
  936     sprintf(nosup_response, "554 TLS not supported here\r\n");
  937     sprintf(error_response, "554 Internal error\r\n");
  938     CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
  939     smtp_rset(0);
  940 }
  941 
  942 
  943 /*
  944  * Implements the NOOP (NO OPeration) command
  945  */
  946 void smtp_noop(void) {
  947     cprintf("250 NOOP\r\n");
  948 }
  949 
  950 
  951 /*
  952  * Implements the QUIT command
  953  */
  954 void smtp_quit(void) {
  955     cprintf("221 Goodbye...\r\n");
  956     CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
  957 }
  958 
  959 
  960 /* 
  961  * Main command loop for SMTP server sessions.
  962  */
  963 void smtp_command_loop(void) {
  964     static const ConstStr AuthPlainStr = {HKEY("AUTH PLAIN")};
  965 
  966     if (SMTP == NULL) {
  967         syslog(LOG_ERR, "serv_smtp: Session SMTP data is null.  WTF?  We will crash now.");
  968         abort();
  969     }
  970 
  971     time(&CC->lastcmd);
  972     if (CtdlClientGetLine(SMTP->Cmd) < 1) {
  973         syslog(LOG_INFO, "SMTP: client disconnected: ending session.");
  974         CC->kill_me = KILLME_CLIENT_DISCONNECTED;
  975         return;
  976     }
  977 
  978     if (SMTP->command_state == smtp_user) {
  979         if (!strncmp(ChrPtr(SMTP->Cmd), AuthPlainStr.Key, AuthPlainStr.len)) {
  980             smtp_try_plain();
  981         }
  982         else {
  983             smtp_get_user(5);
  984         }
  985         return;
  986     }
  987 
  988     else if (SMTP->command_state == smtp_password) {
  989         smtp_get_pass();
  990         return;
  991     }
  992 
  993     else if (SMTP->command_state == smtp_plain) {
  994         smtp_try_plain();
  995         return;
  996     }
  997 
  998     syslog(LOG_DEBUG, "serv_smtp: client sent command <%s>", ChrPtr(SMTP->Cmd));
  999 
 1000     if (!strncasecmp(ChrPtr(SMTP->Cmd), "NOOP", 4)) {
 1001         smtp_noop();
 1002         return;
 1003     }
 1004 
 1005     if (!strncasecmp(ChrPtr(SMTP->Cmd), "QUIT", 4)) {
 1006         smtp_quit();
 1007         return;
 1008     }
 1009 
 1010     if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELO", 4)) {
 1011         smtp_hello(HELO);
 1012         return;
 1013     }
 1014 
 1015     if (!strncasecmp(ChrPtr(SMTP->Cmd), "EHLO", 4)) {
 1016         smtp_hello(EHLO);
 1017         return;
 1018     }
 1019 
 1020     if (!strncasecmp(ChrPtr(SMTP->Cmd), "LHLO", 4)) {
 1021         smtp_hello(LHLO);
 1022         return;
 1023     }
 1024 
 1025     if (!strncasecmp(ChrPtr(SMTP->Cmd), "RSET", 4)) {
 1026         smtp_rset(1);
 1027         return;
 1028     }
 1029 
 1030     if (!strncasecmp(ChrPtr(SMTP->Cmd), "AUTH", 4)) {
 1031         smtp_auth();
 1032         return;
 1033     }
 1034 
 1035     if (!strncasecmp(ChrPtr(SMTP->Cmd), "DATA", 4)) {
 1036         smtp_data();
 1037         return;
 1038     }
 1039 
 1040     if (!strncasecmp(ChrPtr(SMTP->Cmd), "HELP", 4)) {
 1041         smtp_help();
 1042         return;
 1043     }
 1044 
 1045     if (!strncasecmp(ChrPtr(SMTP->Cmd), "MAIL", 4)) {
 1046         smtp_mail();
 1047         return;
 1048     }
 1049     
 1050     if (!strncasecmp(ChrPtr(SMTP->Cmd), "RCPT", 4)) {
 1051         smtp_rcpt();
 1052         return;
 1053     }
 1054 #ifdef HAVE_OPENSSL
 1055     if (!strncasecmp(ChrPtr(SMTP->Cmd), "STARTTLS", 8)) {
 1056         smtp_starttls();
 1057         return;
 1058     }
 1059 #endif
 1060 
 1061     cprintf("502 I'm afraid I can't do that.\r\n");
 1062 }
 1063 
 1064 
 1065 /*****************************************************************************/
 1066 /*                      MODULE INITIALIZATION STUFF                          */
 1067 /*****************************************************************************/
 1068 /*
 1069  * This cleanup function blows away the temporary memory used by
 1070  * the SMTP server.
 1071  */
 1072 void smtp_cleanup_function(void)
 1073 {
 1074     /* Don't do this stuff if this is not an SMTP session! */
 1075     if (CC->h_command_function != smtp_command_loop) return;
 1076 
 1077     syslog(LOG_DEBUG, "Performing SMTP cleanup hook");
 1078 
 1079     FreeStrBuf(&SMTP->Cmd);
 1080     FreeStrBuf(&SMTP->helo_node);
 1081     FreeStrBuf(&SMTP->from);
 1082     FreeStrBuf(&SMTP->recipients);
 1083     FreeStrBuf(&SMTP->OneRcpt);
 1084     FreeStrBuf(&SMTP->preferred_sender_email);
 1085     FreeStrBuf(&SMTP->preferred_sender_name);
 1086 
 1087     free(SMTP);
 1088 }
 1089 
 1090 const char *CitadelServiceSMTP_MTA="SMTP-MTA";
 1091 const char *CitadelServiceSMTPS_MTA="SMTPs-MTA";
 1092 const char *CitadelServiceSMTP_MSA="SMTP-MSA";
 1093 const char *CitadelServiceSMTP_LMTP="LMTP";
 1094 const char *CitadelServiceSMTP_LMTP_UNF="LMTP-UnF";
 1095 
 1096 
 1097 CTDL_MODULE_INIT(smtp)
 1098 {
 1099     if (!threading) {
 1100         CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtp_port"),    /* SMTP MTA */
 1101                     NULL,
 1102                     smtp_mta_greeting,
 1103                     smtp_command_loop,
 1104                     NULL, 
 1105                     CitadelServiceSMTP_MTA);
 1106 
 1107 #ifdef HAVE_OPENSSL
 1108         CtdlRegisterServiceHook(CtdlGetConfigInt("c_smtps_port"),   /* SMTPS MTA */
 1109                     NULL,
 1110                     smtps_greeting,
 1111                     smtp_command_loop,
 1112                     NULL,
 1113                     CitadelServiceSMTPS_MTA);
 1114 #endif
 1115 
 1116         CtdlRegisterServiceHook(CtdlGetConfigInt("c_msa_port"),     /* SMTP MSA */
 1117                     NULL,
 1118                     smtp_msa_greeting,
 1119                     smtp_command_loop,
 1120                     NULL,
 1121                     CitadelServiceSMTP_MSA);
 1122 
 1123         CtdlRegisterServiceHook(0,          /* local LMTP */
 1124                     file_lmtp_socket,
 1125                     lmtp_greeting,
 1126                     smtp_command_loop,
 1127                     NULL,
 1128                     CitadelServiceSMTP_LMTP);
 1129 
 1130         CtdlRegisterServiceHook(0,          /* local LMTP */
 1131                     file_lmtp_unfiltered_socket,
 1132                     lmtp_unfiltered_greeting,
 1133                     smtp_command_loop,
 1134                     NULL,
 1135                     CitadelServiceSMTP_LMTP_UNF);
 1136 
 1137         CtdlRegisterSessionHook(smtp_cleanup_function, EVT_STOP, PRIO_STOP + 250);
 1138     }
 1139     
 1140     /* return our module name for the log */
 1141     return "smtp";
 1142 }