"Fossies" - the Fresh Open Source Software Archive

Member "msmtp-1.8.17/src/msmtpd.c" (30 Sep 2021, 30817 Bytes) of package /linux/privat/msmtp-1.8.17.tar.xz:


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 "msmtpd.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.8.16_vs_1.8.17.

    1 /*
    2  * msmtpd.c
    3  *
    4  * This file is part of msmtp, an SMTP client.
    5  *
    6  * Copyright (C) 2018, 2019, 2020, 2021  Martin Lambers <marlam@marlam.de>
    7  *
    8  *   This program is free software; you can redistribute it and/or modify
    9  *   it under the terms of the GNU General Public License as published by
   10  *   the Free Software Foundation; either version 3 of the License, or
   11  *   (at your option) any later version.
   12  *
   13  *   This program is distributed in the hope that it will be useful,
   14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16  *   GNU General Public License for more details.
   17  *
   18  *   You should have received a copy of the GNU General Public License
   19  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20  */
   21 
   22 #ifdef HAVE_CONFIG_H
   23 # include "config.h"
   24 #endif
   25 
   26 #include <stdio.h>
   27 #include <stdarg.h>
   28 #include <stdlib.h>
   29 #include <string.h>
   30 #include <strings.h>
   31 #include <errno.h>
   32 #include <unistd.h>
   33 #include <signal.h>
   34 #include <arpa/inet.h>
   35 #include <netinet/in.h>
   36 #include <sys/socket.h>
   37 #include <sys/wait.h>
   38 #include <time.h>
   39 #include <syslog.h>
   40 #include <sysexits.h>
   41 #include <getopt.h>
   42 extern char *optarg;
   43 extern int optind;
   44 
   45 #include "base64.h"
   46 #include "password.h"
   47 #include "xalloc.h"
   48 
   49 
   50 /* Built-in defaults */
   51 static const char* DEFAULT_INTERFACE = "127.0.0.1";
   52 static const int DEFAULT_PORT = 25;
   53 static const int MAX_ACTIVE_SESSIONS = 16;
   54 static const int AUTH_DELAY_SECONDS = 1;
   55 static const int AUTH_DELAY_EXPIRATION_SECONDS = 60;
   56 static const char* DEFAULT_COMMAND = BINDIR "/msmtp -f %F";
   57 static const size_t SMTP_BUFSIZE = 1024; /* must be at least 512 according to RFC2821 */
   58 static const size_t CMD_BLOCK_SIZE = 4096; /* initial buffer size for command */
   59 static const size_t CMD_MAX_BLOCKS = 16; /* limit memory allocation */
   60 
   61 /* Logging */
   62 
   63 typedef enum {
   64     log_info = 0,
   65     log_error = 1,
   66     log_nothing = 2
   67 } log_level_t;
   68 
   69 typedef struct {
   70     FILE* file; /* if NULL then use syslog */
   71     log_level_t level;
   72 } log_t;
   73 
   74 void log_open(int log_to_syslog, const char* log_file_name, log_t* log)
   75 {
   76     log->file = NULL;
   77     log->level = log_info; /* default currently hard-coded */
   78     int log_file_open_failure = 0;
   79     if (log_file_name) {
   80         log->file = fopen(log_file_name, "a");
   81         if (!log->file) {
   82             log_file_open_failure = 1;
   83             log_to_syslog = 1;
   84         }
   85     }
   86     if (log_to_syslog) {
   87         openlog("msmtpd", LOG_PID, LOG_MAIL);
   88         if (log_file_open_failure)
   89             syslog(LOG_ERR, "cannot open log file, using syslog instead");
   90     }
   91     if (!log_file_name && !log_to_syslog) {
   92         log->level = log_nothing;
   93     }
   94 }
   95 
   96 void log_close(log_t* log)
   97 {
   98     if (log->level < log_nothing) {
   99         if (log->file)
  100             fclose(log->file);
  101         else
  102             closelog();
  103     }
  104 }
  105 
  106 void
  107 #ifdef __GNUC__
  108 __attribute__ ((format (printf, 3, 4)))
  109 #endif
  110 log_msg(log_t* log,
  111         log_level_t msg_level,
  112         const char* msg_format, ...)
  113 {
  114     if (msg_level >= log->level) {
  115         if (log->file) {
  116             long long pid = getpid();
  117             time_t t = time(NULL);
  118             struct tm* tm = localtime(&t);
  119             char time_str[128];
  120             strftime(time_str, sizeof(time_str), "%F %T", tm);
  121             fprintf(log->file, "%s msmtpd[%lld] %s: ", time_str, pid,
  122                     msg_level >= log_error ? "error" : "info");
  123             va_list args;
  124             va_start(args, msg_format);
  125             vfprintf(log->file, msg_format, args);
  126             va_end(args);
  127             fputc('\n', log->file);
  128         } else {
  129             int priority = (msg_level >= log_error ? LOG_ERR : LOG_INFO);
  130             va_list args;
  131             va_start(args, msg_format);
  132             vsyslog(priority, msg_format, args);
  133             va_end(args);
  134         }
  135     }
  136 }
  137 
  138 /* Read SMTP command from client */
  139 int read_smtp_cmd(FILE* in, char* buf, int bufsize)
  140 {
  141     if (!fgets(buf, bufsize, in))
  142         return 1;
  143     size_t len = strlen(buf);
  144     if (buf[len - 1] != '\n')
  145         return 1;
  146     buf[len - 1] = '\0';
  147     if (len - 1 > 0 && buf[len - 2] == '\r')
  148         buf[len - 2] = '\0';
  149     return 0;
  150 }
  151 
  152 /* Read a mail address enclosed in < and > */
  153 int get_addr(const char* inbuf, char* outbuf, int allow_empty, size_t* addrlen)
  154 {
  155     char* p;
  156 
  157     /* Skip spaces */
  158     while (*inbuf == ' ')
  159         inbuf++;
  160     /* Copy content between '<' and '>' */
  161     if (inbuf[0] != '<')
  162         return 1;
  163     strcpy(outbuf, inbuf + 1);
  164     size_t len = strlen(outbuf);
  165     if (len == 0 || outbuf[len - 1] != '>')
  166         return 1;
  167     outbuf[--len] = '\0';
  168     /* Check if characters are valid */
  169     for (p = outbuf; *p; p++) {
  170         if ((*p >= 'a' && *p <= 'z')
  171                 || (*p >= 'A' && *p <= 'Z')
  172                 || (*p >= '0' && *p <= '9')
  173                 || *p == '.' || *p == '@' || *p == '_' || *p == '-'
  174                 || *p == '+' || *p == '/') {
  175             /* Character allowed. Note that this set is very restrictive;
  176              * more characters might be added to the whitelist if the need
  177              * arises */
  178             continue;
  179         } else {
  180             /* Invalid character */
  181             return 1;
  182         }
  183     }
  184     /* Check for special case of zero length */
  185     if (outbuf[0] == '\0') {
  186         if (allow_empty) {
  187             strcpy(outbuf, "MAILER-DAEMON");
  188             len = 13;
  189         } else {
  190             return 1;
  191         }
  192     }
  193     /* Store length */
  194     *addrlen = len;
  195     return 0;
  196 }
  197 
  198 /* Pipe a mail */
  199 int smtp_pipe(FILE* in, FILE* pipe, char* buf, size_t bufsize)
  200 {
  201     int line_starts;
  202     int line_continues;
  203     size_t len;
  204     char *p;
  205 
  206     line_continues = 0;
  207     for (;;) {
  208         line_starts = !line_continues;
  209         if (!fgets(buf, bufsize, in))
  210             return 1;
  211         len = strlen(buf);
  212         if (len > 0 && buf[len - 1] == '\n') {
  213             /* first case: we have a line end */
  214             buf[--len] = '\0';
  215             if (len > 0 && buf[len - 1] == '\r')
  216                 buf[--len] = '\0';
  217             line_continues = 0;
  218         } else if (len == bufsize - 1) {
  219             /* second case: the line continues */
  220             if (buf[len - 1] == '\r') {
  221                 /* We have CRLF that is divided by the buffer boundary. Since CR
  222                  * may not appear alone in a mail according to RFC2822, we
  223                  * know that the next buffer will be "\n\0", so it's safe to
  224                  * just delete the CR. */
  225                 buf[--len] = '\0';
  226             }
  227             line_continues = 1;
  228         } else {
  229             /* third case: this is the last line, and it lacks a newline
  230              * character */
  231             line_continues = 0;
  232         }
  233         p = buf;
  234         if (line_starts && buf[0] == '.') {
  235             if (buf[1] == '\0') {
  236                 /* end of mail */
  237                 break;
  238             } else {
  239                 /* remove leading dot */
  240                 p = buf + 1;
  241                 len--;
  242             }
  243         }
  244         if (fwrite(p, sizeof(char), len, pipe) != len)
  245             return 1;
  246         if (!line_continues && fputc('\n', pipe) == EOF)
  247             return 1;
  248     }
  249     if (fflush(pipe) != 0)
  250         return 1;
  251     return 0;
  252 }
  253 
  254 /* SMTP session with input and output from FILE descriptors.
  255  * Mails are piped to the given command, where the first occurrence of %F
  256  * will be replaced with the envelope-from address, and all recipient addresses
  257  * will be appended as arguments.
  258  *
  259  * Return value:
  260  * 0 if the session ended normally / successfully
  261  * 1 if the session had errors and/or was aborted
  262  * 2 same as 1 and the session had authentication failures
  263  * */
  264 int msmtpd_session(log_t* log, FILE* in, FILE* out,
  265         const char* command,
  266         const char* user, const char* password, int impose_auth_delay)
  267 {
  268     char buf[SMTP_BUFSIZE];
  269     char buf2[SMTP_BUFSIZE];
  270     size_t addrlen;
  271     char* cmd;
  272     char* tmpcmd;
  273     size_t cmd_blocks;
  274     size_t cmd_index;
  275     int envfrom_was_handled;
  276     int recipient_was_seen;
  277     FILE* pipe;
  278     int pipe_status;
  279     size_t i;
  280 
  281     log_msg(log, log_info, "starting SMTP session");
  282     setlinebuf(out);
  283     fprintf(out, "220 localhost ESMTP msmtpd\r\n");
  284     if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  285         log_msg(log, log_error, "client did not send initial command, session aborted");
  286         return 1;
  287     }
  288     if (strncasecmp(buf, "EHLO ", 5) != 0 && strncasecmp(buf, "HELO ", 5) != 0) {
  289         fprintf(out, "500 Expected EHLO or HELO\r\n");
  290         log_msg(log, log_error, "client did not start with EHLO or HELO, session aborted");
  291         return 1;
  292     }
  293     if (user && strncasecmp(buf, "EHLO ", 5) == 0) {
  294         fprintf(out, "250-localhost\r\n");
  295         fprintf(out, "250 AUTH PLAIN\r\n");
  296     } else {
  297         fprintf(out, "250 localhost\r\n");
  298     }
  299     if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  300         log_msg(log, log_error, "client did not send second command, session aborted");
  301         return 1;
  302     }
  303 
  304     if (user) {
  305         if (strcmp(buf, "QUIT") == 0) {
  306             fprintf(out, "221 Bye\r\n");
  307             log_msg(log, log_info, "client ended session");
  308             return 0;
  309         }
  310         if (strncasecmp(buf, "AUTH PLAIN", 10) != 0) {
  311             fprintf(out, "530 Authentication required\r\n");
  312             log_msg(log, log_info, "client did not authenticate, session aborted");
  313             return 1;
  314         }
  315         const char* b64 = NULL;
  316         if (buf[10] == ' ') {
  317             b64 = buf + 11;
  318         } else {
  319             fprintf(out, "334 \r\n");
  320             if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  321                 log_msg(log, log_error, "client did not send authentication information, session aborted");
  322                 return 1;
  323             }
  324             b64 = buf;
  325         }
  326         size_t buf2_len = SMTP_BUFSIZE;
  327         bool r = base64_decode_ctx(NULL, b64, strlen(b64), buf2, &buf2_len);
  328         int authenticated = 0;
  329         if (r && buf2_len == 1 + strlen(user) + 1 + strlen(password)
  330                 && buf2[0] == '\0'
  331                 && strncmp(buf2 + 1, user, strlen(user)) == 0
  332                 && buf2[1 + strlen(user)] == '\0'
  333                 && strncmp(buf2 + 1 + strlen(user) + 1, password, strlen(password)) == 0) {
  334             authenticated = 1;
  335         }
  336         if (impose_auth_delay)
  337             sleep(AUTH_DELAY_SECONDS);
  338         if (!authenticated) {
  339             fprintf(out, "535 Authentication failed\r\n");
  340             log_msg(log, log_error, "authentication failed, session aborted");
  341             return 2;
  342         } else {
  343             fprintf(out, "235 Authentication successful\r\n");
  344             log_msg(log, log_info, "authenticated user %s", user);
  345         }
  346         if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  347             log_msg(log, log_error, "client did not send command after authentication, session aborted");
  348             return 1;
  349         }
  350     }
  351 
  352     for (;;) {
  353         cmd_index = 0;
  354         envfrom_was_handled = 0;
  355         recipient_was_seen = 0;
  356 
  357         if (strncasecmp(buf, "MAIL FROM:", 10) != 0 && strcasecmp(buf, "QUIT") != 0) {
  358             fprintf(out, "500 Expected MAIL FROM:<addr> or QUIT\r\n");
  359             log_msg(log, log_error, "client did not send MAIL FROM or QUIT, session aborted");
  360             return 1;
  361         }
  362         if (strcasecmp(buf, "QUIT") == 0) {
  363             fprintf(out, "221 Bye\r\n");
  364             log_msg(log, log_info, "client ended session");
  365             return 0;
  366         }
  367         if (get_addr(buf + 10, buf2, 1, &addrlen) != 0) {
  368             fprintf(out, "501 Invalid address\r\n");
  369             log_msg(log, log_error, "invalid address in MAIL FROM, session aborted");
  370             return 1;
  371         }
  372 
  373         cmd_blocks = 1;
  374         while (cmd_blocks * CMD_BLOCK_SIZE < strlen(command) + addrlen + 2 * SMTP_BUFSIZE)
  375             cmd_blocks++;
  376         cmd = malloc(cmd_blocks * CMD_BLOCK_SIZE);
  377         if (!cmd) {
  378             fprintf(out, "554 %s\r\n", strerror(ENOMEM));
  379             log_msg(log, log_error, "%s, session aborted", strerror(ENOMEM));
  380             return 1;
  381         }
  382 
  383         for (i = 0; command[i];) {
  384             if (!envfrom_was_handled && command[i] == '%' && command[i + 1] == 'F') {
  385                 memcpy(cmd + cmd_index, buf2, addrlen);
  386                 cmd_index += addrlen;
  387                 i += 2;
  388                 envfrom_was_handled = 1;
  389             } else {
  390                 cmd[cmd_index] = command[i];
  391                 cmd_index++;
  392                 i++;
  393             }
  394         }
  395         fprintf(out, "250 Ok\r\n");
  396 
  397         for (;;) {
  398             if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  399                 log_msg(log, log_error, "client did not send command, session aborted");
  400                 free(cmd);
  401                 return 1;
  402             }
  403             if (!recipient_was_seen) {
  404                 if (strncasecmp(buf, "RCPT TO:", 8) != 0) {
  405                     fprintf(out, "500 Expected RCPT TO:<addr>\r\n");
  406                     log_msg(log, log_error, "client did not send RCPT TO, session aborted");
  407                     free(cmd);
  408                     return 1;
  409                 }
  410             } else {
  411                 if (strncasecmp(buf, "RCPT TO:", 8) != 0 && strcasecmp(buf, "DATA") != 0) {
  412                     fprintf(out, "500 Expected RCPT TO:<addr> or DATA\r\n");
  413                     log_msg(log, log_error, "client did not send RCPT TO or DATA, session aborted");
  414                     free(cmd);
  415                     return 1;
  416                 }
  417             }
  418             if (strcasecmp(buf, "DATA") == 0) {
  419                 break;
  420             } else {
  421                 if (get_addr(buf + 8, buf2, 0, &addrlen) != 0) {
  422                     fprintf(out, "501 Invalid address\r\n");
  423                     log_msg(log, log_error, "invalid address in RCPT TO, session aborted");
  424                     free(cmd);
  425                     return 1;
  426                 }
  427                 if (cmd_index + 1 + addrlen + 1 >= cmd_blocks * CMD_BLOCK_SIZE) {
  428                     cmd_blocks++;
  429                     if (cmd_blocks > CMD_MAX_BLOCKS) {
  430                         fprintf(out, "554 Too many recipients\r\n");
  431                         log_msg(log, log_error, "too many recipients, session aborted");
  432                         free(cmd);
  433                         return 1;
  434                     }
  435                     tmpcmd = realloc(cmd, cmd_blocks * CMD_MAX_BLOCKS);
  436                     if (!tmpcmd) {
  437                         fprintf(out, "554 %s\r\n", strerror(ENOMEM));
  438                         log_msg(log, log_error, "%s, session aborted", strerror(ENOMEM));
  439                         free(cmd);
  440                         return 1;
  441                     }
  442                     cmd = tmpcmd;
  443                 }
  444                 cmd[cmd_index++] = ' ';
  445                 memcpy(cmd + cmd_index, buf2, addrlen);
  446                 cmd_index += addrlen;
  447                 fprintf(out, "250 Ok\r\n");
  448                 recipient_was_seen = 1;
  449             }
  450         }
  451         cmd[cmd_index++] = '\0';
  452 
  453         log_msg(log, log_info, "pipe command is %s", cmd);
  454         pipe = popen(cmd, "w");
  455         free(cmd);
  456         if (!pipe) {
  457             fprintf(out, "554 Cannot start pipe command\r\n");
  458             log_msg(log, log_error, "cannot start pipe command, session aborted");
  459             return 1;
  460         }
  461         fprintf(out, "354 Send data\r\n");
  462         if (smtp_pipe(in, pipe, buf, SMTP_BUFSIZE) != 0) {
  463             fprintf(out, "554 Cannot pipe mail to command\r\n");
  464             log_msg(log, log_error, "cannot pipe mail to command, session aborted");
  465             return 1;
  466         }
  467         pipe_status = pclose(pipe);
  468         if (pipe_status == -1 || !WIFEXITED(pipe_status)) {
  469             fprintf(out, "554 Pipe command failed to execute\r\n");
  470             log_msg(log, log_error, "pipe command failed to execute, session aborted");
  471             return 1;
  472         } else if (WEXITSTATUS(pipe_status) != 0) {
  473             int return_code = 554; /* permanent error */
  474             switch (WEXITSTATUS(pipe_status)) {
  475             case EX_NOHOST:
  476             case EX_UNAVAILABLE:
  477             case EX_OSERR:
  478             case EX_TEMPFAIL:
  479                 return_code = 451; /* temporary error */
  480                 break;
  481             case EX_USAGE:
  482             case EX_DATAERR:
  483             case EX_NOINPUT:
  484             case EX_SOFTWARE:
  485             case EX_OSFILE:
  486             case EX_CANTCREAT:
  487             case EX_IOERR:
  488             case EX_PROTOCOL:
  489             case EX_NOPERM:
  490             case EX_CONFIG:
  491             default:
  492                 break;
  493             }
  494             fprintf(out, "%d Pipe command reported error %d\r\n", return_code, WEXITSTATUS(pipe_status));
  495             log_msg(log, log_error, "pipe command reported error %d, session aborted", WEXITSTATUS(pipe_status));
  496             return 1;
  497         }
  498 
  499         fprintf(out, "250 Ok, mail was piped\r\n");
  500         log_msg(log, log_info, "mail was piped successfully");
  501         if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  502             log_msg(log, log_info, "client ended session without sending QUIT");
  503             break; /* ignore missing QUIT */
  504         }
  505     }
  506     return 0;
  507 }
  508 
  509 /* Manage the maximum number of concurrent sessions and impose a delay to
  510  * authentication requests after an authentication failure occured, to make
  511  * brute force attacks infeasible */
  512 
  513 volatile sig_atomic_t active_sessions_count;
  514 volatile sig_atomic_t auth_failure_occurred;
  515 volatile time_t last_auth_failure_time;
  516 
  517 void sigchld_action(int signum, siginfo_t* si, void* ucontext)
  518 {
  519     (void)signum;   /* unused */
  520     (void)ucontext; /* unused */
  521     int wstatus;
  522     if (waitpid(si->si_pid, &wstatus, 0) == si->si_pid) {
  523         int child_exit_status = -1;
  524         if (WIFEXITED(wstatus))
  525             child_exit_status = WEXITSTATUS(wstatus);
  526         if (child_exit_status >= 2) {
  527             struct timespec tp;
  528             clock_gettime(CLOCK_MONOTONIC, &tp);
  529             last_auth_failure_time = tp.tv_sec;
  530             auth_failure_occurred = 1;
  531         }
  532         active_sessions_count--;
  533     }
  534 }
  535 
  536 /* Parse the command line */
  537 int parse_command_line(int argc, char* argv[],
  538         int* print_version, int* print_help,
  539         int* inetd,
  540         const char** interface, int* port,
  541         int* log_to_syslog, const char** log_file,
  542         const char** command,
  543         char** user, char** password)
  544 {
  545     enum {
  546         msmtpd_option_version,
  547         msmtpd_option_help,
  548         msmtpd_option_inetd,
  549         msmtpd_option_port,
  550         msmtpd_option_interface,
  551         msmtpd_option_log,
  552         msmtpd_option_command,
  553         msmtpd_option_auth
  554     };
  555 
  556     struct option options[] = {
  557         { "version", no_argument, 0, msmtpd_option_version },
  558         { "help", no_argument, 0, msmtpd_option_help },
  559         { "inetd", no_argument, 0, msmtpd_option_inetd },
  560         { "port", required_argument, 0, msmtpd_option_port },
  561         { "interface", required_argument, 0, msmtpd_option_interface },
  562         { "log", required_argument, 0, msmtpd_option_log },
  563         { "command", required_argument, 0, msmtpd_option_command },
  564         { "auth", required_argument, 0, msmtpd_option_auth },
  565         { 0, 0, 0, 0 }
  566     };
  567 
  568     for (;;) {
  569         int option_index = -1;
  570         int c = getopt_long(argc, argv, "", options, &option_index);
  571         if (c == -1)
  572             break;
  573         if (optarg && optarg[0] == '\0') {
  574             fprintf(stderr, "%s: option '--%s' requires non-empty argument\n", argv[0],
  575                     options[option_index].name);
  576             return 1;
  577         }
  578         switch (c) {
  579         case msmtpd_option_version:
  580             *print_version = 1;
  581             break;
  582         case msmtpd_option_help:
  583             *print_help = 1;
  584             break;
  585         case msmtpd_option_inetd:
  586             *inetd = 1;
  587             break;
  588         case msmtpd_option_port:
  589             *port = atoi(optarg);
  590             break;
  591         case msmtpd_option_interface:
  592             *interface = optarg;
  593             break;
  594         case msmtpd_option_log:
  595             if (strcmp(optarg, "none") == 0) {
  596                 *log_to_syslog = 0;
  597                 *log_file = NULL;
  598             } else if (strcmp(optarg, "syslog") == 0) {
  599                 *log_to_syslog = 1;
  600                 *log_file = NULL;
  601             } else {
  602                 *log_to_syslog = 0;
  603                 *log_file = optarg;
  604             }
  605             break;
  606         case msmtpd_option_command:
  607             *command = optarg;
  608             break;
  609         case msmtpd_option_auth:
  610             {
  611                 char* comma = strchr(optarg, ',');
  612                 if (!comma) {
  613                     char* tmp_user = xstrdup(optarg);
  614                     char* tmp_password = password_get("localhost", tmp_user, password_service_smtp, 0, 0);
  615                     if (!tmp_password) {
  616                         fprintf(stderr, "%s: cannot get password for (localhost, smtp, %s)\n",
  617                                 argv[0], tmp_user);
  618                         free(tmp_user);
  619                         return 1;
  620                     }
  621                     free(*user);
  622                     *user = tmp_user;
  623                     free(*password);
  624                     *password = tmp_password;
  625                 } else {
  626                     char* tmp_user = xstrndup(optarg, comma - optarg);
  627                     char* tmp_password = NULL;
  628                     char* errstr = NULL;
  629                     if (password_eval(comma + 1, &tmp_password, &errstr) != 0) {
  630                         fprintf(stderr, "%s: cannot get password: %s\n", argv[0], errstr);
  631                         free(tmp_user);
  632                         free(errstr);
  633                         return 1;
  634                     }
  635                     free(*user);
  636                     *user = tmp_user;
  637                     free(*password);
  638                     *password = tmp_password;
  639                 }
  640             }
  641             break;
  642         default:
  643             return 1;
  644             break;
  645         }
  646     }
  647     if (argc - optind > 0) {
  648         fprintf(stderr, "%s: too many arguments\n", argv[0]);
  649         return 1;
  650     }
  651     return 0;
  652 }
  653 
  654 int main(int argc, char* argv[])
  655 {
  656     /* Exit status values according to LSB init script recommendations */
  657     const int exit_ok = 0;
  658     const int exit_not_running = 3;
  659 
  660     /* Configuration */
  661     int print_version = 0;
  662     int print_help = 0;
  663     int inetd = 0;
  664     const char* interface = DEFAULT_INTERFACE;
  665     int port = DEFAULT_PORT;
  666     int log_to_syslog = 0;
  667     const char* log_file = NULL;
  668     const char* command = DEFAULT_COMMAND;
  669     char* user = NULL;
  670     char* password = NULL;
  671 
  672     /* Command line */
  673     if (parse_command_line(argc, argv,
  674                 &print_version, &print_help,
  675                 &inetd, &interface, &port,
  676                 &log_to_syslog, &log_file,
  677                 &command,
  678                 &user, &password) != 0) {
  679         return exit_not_running;
  680     }
  681     if (print_version) {
  682         printf("msmtpd version %s\n", VERSION);
  683         printf("Copyright (C) 2021 Martin Lambers.\n"
  684                 "This is free software.  You may redistribute copies of it under the terms of\n"
  685                 "the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
  686                 "There is NO WARRANTY, to the extent permitted by law.\n");
  687         return exit_ok;
  688     }
  689     if (print_help) {
  690         printf("Usage: msmtpd [option...]\n");
  691         printf("Options:\n");
  692         printf("  --version       print version\n");
  693         printf("  --help          print help\n");
  694         printf("  --inetd         start single SMTP session on stdin/stdout\n");
  695         printf("  --interface=ip  listen on ip instead of %s\n", DEFAULT_INTERFACE);
  696         printf("  --port=number   listen on port number instead of %d\n", DEFAULT_PORT);
  697         printf("  --log=none|syslog|FILE  do not log anything (default)\n");
  698         printf("                  or log to syslog or log to the given file\n");
  699         printf("  --command=cmd   pipe mails to cmd instead of %s\n", DEFAULT_COMMAND);
  700         printf("  --auth=user[,passwordeval] require authentication with this user name;\n");
  701         printf("                  the password will be retrieved from the given\n");
  702         printf("                  passwordeval command or, if none is given, from\n");
  703         printf("                  the key ring or, if that fails, from a prompt.\n");
  704         return exit_ok;
  705     }
  706 
  707     /* Do it */
  708     int ret = exit_ok;
  709     signal(SIGPIPE, SIG_IGN); /* Do not terminate when piping fails; we want to handle that error */
  710     if (inetd) {
  711         /* We are no daemon, so we can just signal error with exit status 1 and success with 0 */
  712         log_t log;
  713         log_open(log_to_syslog, log_file, &log);
  714         int impose_auth_delay = 1; /* since we cannot keep track of auth failures in inetd mode */
  715         ret = msmtpd_session(&log, stdin, stdout, command, user, password, impose_auth_delay);
  716         ret = (ret == 0 ? 0 : 1);
  717         log_close(&log);
  718     } else {
  719         int ipv6;
  720         struct sockaddr_in6 sa6;
  721         struct sockaddr_in sa4;
  722         int listen_fd;
  723         int on = 1;
  724 
  725         /* Set interface */
  726         memset(&sa6, 0, sizeof(sa6));
  727         if (inet_pton(AF_INET6, interface, &sa6.sin6_addr) != 0) {
  728             ipv6 = 1;
  729             sa6.sin6_family = AF_INET6;
  730             sa6.sin6_port = htons(port);
  731         } else {
  732             memset(&sa4, 0, sizeof(sa4));
  733             if (inet_pton(AF_INET, interface, &sa4.sin_addr) != 0) {
  734                 ipv6 = 0;
  735                 sa4.sin_family = AF_INET;
  736                 sa4.sin_port = htons(port);
  737             } else {
  738                 fprintf(stderr, "%s: invalid interface\n", argv[0]);
  739                 ret = exit_not_running;
  740                 goto exit;
  741             }
  742         }
  743 
  744         /* Create and set up listening socket */
  745         listen_fd = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0);
  746         if (listen_fd < 0) {
  747             fprintf(stderr, "%s: cannot create socket: %s\n", argv[0], strerror(errno));
  748             ret = exit_not_running;
  749             goto exit;
  750         }
  751         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
  752             fprintf(stderr, "%s: cannot set socket option: %s\n", argv[0], strerror(errno));
  753             ret = exit_not_running;
  754             goto exit;
  755         }
  756         if (bind(listen_fd,
  757                     ipv6 ? (struct sockaddr*)&sa6 : (struct sockaddr*)&sa4,
  758                     ipv6 ? sizeof(sa6) : sizeof(sa4)) < 0) {
  759             fprintf(stderr, "%s: cannot bind to %s:%d: %s\n", argv[0], interface, port, strerror(errno));
  760             ret = exit_not_running;
  761             goto exit;
  762         }
  763         if (listen(listen_fd, 128) < 0) {
  764             fprintf(stderr, "%s: cannot listen on socket: %s\n", argv[0], strerror(errno));
  765             ret = exit_not_running;
  766             goto exit;
  767         }
  768 
  769         /* Set up signal handling, in part conforming to freedesktop.org modern daemon requirements */
  770         signal(SIGHUP, SIG_IGN); /* Reloading configuration does not make sense for us */
  771         signal(SIGTERM, SIG_DFL); /* We can be terminated as long as there is no running session */
  772 
  773         /* Set SIGCHLD signal handler to manage maximum number of active sessions and impose
  774          * authentication delays in case of authentication failures (to make brute force attacks unfeasible) */
  775         active_sessions_count = 0;
  776         auth_failure_occurred = 0;
  777         struct sigaction sa;
  778         memset(&sa, 0, sizeof(sa));
  779         sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO | SA_RESTART; /* SA_RESTART: restart accept() after SIGCHLD */
  780         sa.sa_sigaction = sigchld_action;
  781         sigaction(SIGCHLD, &sa, NULL); /* cannot fail */
  782 
  783         /* Accept connection */
  784         for (;;) {
  785             while (active_sessions_count >= MAX_ACTIVE_SESSIONS) {
  786                 sleep(1);
  787             }
  788             socklen_t client_len = ipv6 ? sizeof(sa6) : sizeof(sa4);
  789             int conn_fd = accept(listen_fd, ipv6 ? (struct sockaddr*)&sa6 : (struct sockaddr*)&sa4, &client_len);
  790             if (conn_fd < 0) {
  791                 fprintf(stderr, "%s: cannot accept connection: %s\n", argv[0], strerror(errno));
  792                 ret = exit_not_running;
  793                 break;
  794             }
  795             char client_ip_str[INET6_ADDRSTRLEN];
  796             int client_port;
  797             if (ipv6) {
  798                 inet_ntop(AF_INET6, &sa6.sin6_addr, client_ip_str, sizeof(client_ip_str));
  799                 client_port = ntohs(sa6.sin6_port);
  800             } else {
  801                 inet_ntop(AF_INET, &sa4.sin_addr, client_ip_str, sizeof(client_ip_str));
  802                 client_port = ntohs(sa4.sin_port);
  803             }
  804             pid_t pid = fork();
  805             if (pid == 0) {
  806                 /* Child process */
  807                 int impose_auth_delay = 0;
  808                 if (auth_failure_occurred) {
  809                     struct timespec tp;
  810                     clock_gettime(CLOCK_MONOTONIC, &tp);
  811                     int seconds_since_last_auth_failure = tp.tv_sec - last_auth_failure_time;
  812                     if (seconds_since_last_auth_failure <= AUTH_DELAY_EXPIRATION_SECONDS)
  813                         impose_auth_delay = 1;
  814                 }
  815                 signal(SIGTERM, SIG_IGN); /* A running session should not be terminated */
  816                 signal(SIGCHLD, SIG_DFL); /* Make popen()/pclose() work again */
  817                 log_t log;
  818                 log_open(log_to_syslog, log_file, &log);
  819                 log_msg(&log, log_info, "connection from %s port %d, active sessions %d (max %d), auth_delay=%s",
  820                         client_ip_str, client_port,
  821                         active_sessions_count + 1, MAX_ACTIVE_SESSIONS, impose_auth_delay ? "yes" : "no");
  822                 FILE* conn = fdopen(conn_fd, "rb+");
  823                 int ret = msmtpd_session(&log, conn, conn, command, user, password, impose_auth_delay);
  824                 fclose(conn);
  825                 log_msg(&log, log_info, "connection closed");
  826                 log_close(&log);
  827                 exit(ret);
  828             } else {
  829                 /* Parent process */
  830                 close(conn_fd);
  831                 if (pid > 0) {
  832                     active_sessions_count++;
  833                 } else {
  834                     fprintf(stderr, "%s: fork failed: %s\n", argv[0], strerror(errno));
  835                     ret = exit_not_running;
  836                     break;
  837                 }
  838             }
  839         }
  840     }
  841 
  842 exit:
  843     free(user);
  844     free(password);
  845     return ret;
  846 }
  847 
  848 /* Die if memory allocation fails. Note that we only use xalloc() etc
  849  * during startup; one msmtpd is running, out of memory conditions are
  850  * handled gracefully. */
  851 
  852 void xalloc_die(void)
  853 {
  854     fputs(strerror(ENOMEM), stderr);
  855     fputc('\n', stderr);
  856     exit(3);
  857 }