"Fossies" - the Fresh Open Source Software Archive

Member "msmtp-1.8.5/src/msmtpd.c" (9 Jan 2019, 16388 Bytes) of package /linux/privat/msmtp-1.8.5.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 last Fossies "Diffs" side-by-side code changes report: 1.8.1_vs_1.8.2.

    1 /*
    2  * msmtpd.c
    3  *
    4  * This file is part of msmtp, an SMTP client.
    5  *
    6  * Copyright (C) 2018, 2019  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 <stdlib.h>
   28 #include <string.h>
   29 #include <errno.h>
   30 #include <unistd.h>
   31 #include <signal.h>
   32 #include <arpa/inet.h>
   33 #include <netinet/in.h>
   34 #include <sys/types.h>
   35 #include <sys/socket.h>
   36 #include <sys/wait.h>
   37 #include <getopt.h>
   38 extern char *optarg;
   39 extern int optind;
   40 
   41 
   42 /* Built-in defaults */
   43 static const char* DEFAULT_INTERFACE = "127.0.0.1";
   44 static const int DEFAULT_PORT = 25;
   45 static const char* DEFAULT_COMMAND = BINDIR "/msmtp -f %F";
   46 static const size_t SMTP_BUFSIZE = 1024; /* must be at least 512 according to RFC2821 */
   47 static const size_t CMD_BLOCK_SIZE = 4096; /* initial buffer size for command */
   48 static const size_t CMD_MAX_BLOCKS = 16; /* limit memory allocation */
   49 
   50 /* Read SMTP command from client */
   51 int read_smtp_cmd(FILE* in, char* buf, int bufsize)
   52 {
   53     if (!fgets(buf, bufsize, in))
   54         return 1;
   55     size_t len = strlen(buf);
   56     if (buf[len - 1] != '\n')
   57         return 1;
   58     buf[len - 1] = '\0';
   59     if (len - 1 > 0 && buf[len - 2] == '\r')
   60         buf[len - 2] = '\0';
   61     return 0;
   62 }
   63 
   64 /* Read a mail address enclosed in < and > */
   65 int get_addr(const char* inbuf, char* outbuf, int allow_empty, size_t* addrlen)
   66 {
   67     char* p;
   68 
   69     /* Skip spaces */
   70     while (*inbuf == ' ')
   71         inbuf++;
   72     /* Copy content between '<' and '>' */
   73     if (inbuf[0] != '<')
   74         return 1;
   75     strcpy(outbuf, inbuf + 1);
   76     size_t len = strlen(outbuf);
   77     if (len == 0 || outbuf[len - 1] != '>')
   78         return 1;
   79     outbuf[--len] = '\0';
   80     /* Check if characters are valid */
   81     for (p = outbuf; *p; p++) {
   82         if ((*p >= 'a' && *p <= 'z')
   83                 || (*p >= 'A' && *p <= 'Z')
   84                 || (*p >= '0' && *p <= '9')
   85                 || *p == '.' || *p == '@' || *p == '_' || *p == '-'
   86                 || *p == '+' || *p == '/') {
   87             /* Character allowed. Note that this set is very restrictive;
   88              * more characters might be added to the whitelist if the need
   89              * arises */
   90             continue;
   91         } else {
   92             /* Invalid character */
   93             return 1;
   94         }
   95     }
   96     /* Check for special case of zero length */
   97     if (outbuf[0] == '\0') {
   98         if (allow_empty) {
   99             strcpy(outbuf, "MAILER-DAEMON");
  100             len = 13;
  101         } else {
  102             return 1;
  103         }
  104     }
  105     /* Store length */
  106     *addrlen = len;
  107     return 0;
  108 }
  109 
  110 /* Pipe a mail */
  111 int smtp_pipe(FILE* in, FILE* pipe, char* buf, size_t bufsize)
  112 {
  113     int line_starts;
  114     int line_continues;
  115     size_t len;
  116     char *p;
  117 
  118     line_continues = 0;
  119     for (;;) {
  120         line_starts = !line_continues;
  121         if (!fgets(buf, bufsize, in))
  122             return 1;
  123         len = strlen(buf);
  124         if (len > 0 && buf[len - 1] == '\n') {
  125             /* first case: we have a line end */
  126             buf[--len] = '\0';
  127             if (len > 0 && buf[len - 1] == '\r')
  128                 buf[--len] = '\0';
  129             line_continues = 0;
  130         } else if (len == bufsize - 1) {
  131             /* second case: the line continues */
  132             if (buf[len - 1] == '\r') {
  133                 /* We have CRLF that is divided by the buffer boundary. Since CR
  134                  * may not appear alone in a mail according to RFC2822, we
  135                  * know that the next buffer will be "\n\0", so it's safe to
  136                  * just delete the CR. */
  137                 buf[--len] = '\0';
  138             }
  139             line_continues = 1;
  140         } else {
  141             /* third case: this is the last line, and it lacks a newline
  142              * character */
  143             line_continues = 0;
  144         }
  145         p = buf;
  146         if (line_starts && buf[0] == '.') {
  147             if (buf[1] == '\0') {
  148                 /* end of mail */
  149                 break;
  150             } else {
  151                 /* remove leading dot */
  152                 p = buf + 1;
  153                 len--;
  154             }
  155         }
  156         if (fwrite(p, sizeof(char), len, pipe) != len)
  157             return 1;
  158         if (!line_continues && fputc('\n', pipe) == EOF)
  159             return 1;
  160     }
  161     if (fflush(pipe) != 0)
  162         return 1;
  163     return 0;
  164 }
  165 
  166 /* SMTP session with input and output from FILE descriptors.
  167  * Mails are piped to the given command, where the first occurrence of %F
  168  * will be replaced with the envelope-from address, and all recipient addresses
  169  * will be appended as arguments. */
  170 int msmtpd_session(FILE* in, FILE* out, const char* command)
  171 {
  172     char buf[SMTP_BUFSIZE];
  173     char addrbuf[SMTP_BUFSIZE];
  174     size_t addrlen;
  175     char* cmd;
  176     char* tmpcmd;
  177     size_t cmd_blocks;
  178     size_t cmd_index = 0;
  179     int envfrom_was_handled = 0;
  180     int recipient_was_seen = 0;
  181     FILE* pipe;
  182     int pipe_status;
  183     size_t i;
  184 
  185     setlinebuf(out);
  186     fprintf(out, "220 localhost ESMTP msmtpd\r\n");
  187     if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0)
  188         return 1;
  189     if (strncmp(buf, "EHLO ", 5) != 0 && strncmp(buf, "HELO ", 5) != 0) {
  190         fprintf(out, "500 Expected EHLO or HELO\r\n");
  191         return 1;
  192     }
  193     fprintf(out, "250 localhost\r\n");
  194     if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0)
  195         return 1;
  196     if (strncmp(buf, "MAIL FROM:", 10) != 0 && strcmp(buf, "QUIT") != 0) {
  197         fprintf(out, "500 Expected MAIL FROM:<addr> or QUIT\r\n");
  198         return 1;
  199     }
  200     if (strcmp(buf, "QUIT") == 0) {
  201         fprintf(out, "221 Bye\r\n");
  202         return 0;
  203     }
  204     if (get_addr(buf + 10, addrbuf, 1, &addrlen) != 0) {
  205         fprintf(out, "501 Invalid address\r\n");
  206         return 1;
  207     }
  208 
  209     cmd_blocks = 1;
  210     while (cmd_blocks * CMD_BLOCK_SIZE < strlen(command) + addrlen + 2 * SMTP_BUFSIZE)
  211         cmd_blocks++;
  212     cmd = malloc(cmd_blocks * CMD_BLOCK_SIZE);
  213     if (!cmd) {
  214         fprintf(out, "554 %s\r\n", strerror(ENOMEM));
  215         return 1;
  216     }
  217 
  218     for (i = 0; command[i];) {
  219         if (!envfrom_was_handled && command[i] == '%' && command[i + 1] == 'F') {
  220             memcpy(cmd + cmd_index, addrbuf, addrlen);
  221             cmd_index += addrlen;
  222             i += 2;
  223             envfrom_was_handled = 1;
  224         } else {
  225             cmd[cmd_index] = command[i];
  226             cmd_index++;
  227             i++;
  228         }
  229     }
  230     fprintf(out, "250 Ok\r\n");
  231 
  232     for (;;) {
  233         if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
  234             free(cmd);
  235             return 1;
  236         }
  237         if (!recipient_was_seen) {
  238             if (strncmp(buf, "RCPT TO:", 8) != 0) {
  239                 fprintf(out, "500 Expected RCPT TO:<addr>\r\n");
  240                 free(cmd);
  241                 return 1;
  242             }
  243         } else {
  244             if (strncmp(buf, "RCPT TO:", 8) != 0 && strcmp(buf, "DATA") != 0) {
  245                 fprintf(out, "500 Expected RCPT TO:<addr> or DATA\r\n");
  246                 free(cmd);
  247                 return 1;
  248             }
  249         }
  250         if (strcmp(buf, "DATA") == 0) {
  251             break;
  252         } else {
  253             if (get_addr(buf + 8, addrbuf, 0, &addrlen) != 0) {
  254                 fprintf(out, "501 Invalid address\r\n");
  255                 free(cmd);
  256                 return 1;
  257             }
  258             if (cmd_index + 1 + addrlen + 1 >= cmd_blocks * CMD_BLOCK_SIZE) {
  259                 cmd_blocks++;
  260                 if (cmd_blocks > CMD_MAX_BLOCKS) {
  261                     fprintf(out, "554 Too many recipients\r\n");
  262                     free(cmd);
  263                     return 1;
  264                 }
  265                 tmpcmd = realloc(cmd, cmd_blocks * CMD_MAX_BLOCKS);
  266                 if (!tmpcmd) {
  267                     free(cmd);
  268                     fprintf(out, "554 %s\r\n", strerror(ENOMEM));
  269                     return 1;
  270                 }
  271                 cmd = tmpcmd;
  272             }
  273             cmd[cmd_index++] = ' ';
  274             memcpy(cmd + cmd_index, addrbuf, addrlen);
  275             cmd_index += addrlen;
  276             fprintf(out, "250 Ok\r\n");
  277             recipient_was_seen = 1;
  278         }
  279     }
  280     cmd[cmd_index++] = '\0';
  281 
  282     pipe = popen(cmd, "w");
  283     free(cmd);
  284     if (!pipe) {
  285         fprintf(out, "554 Cannot start pipe command\r\n");
  286         return 1;
  287     }
  288     fprintf(out, "354 Send data\r\n");
  289     if (smtp_pipe(in, pipe, buf, SMTP_BUFSIZE) != 0) {
  290         fprintf(out, "554 Cannot pipe mail to command\r\n");
  291         return 1;
  292     }
  293     pipe_status = pclose(pipe);
  294     if (pipe_status == -1 || !WIFEXITED(pipe_status)) {
  295         fprintf(out, "554 Pipe command failed to execute\r\n");
  296         return 1;
  297     } else if (WEXITSTATUS(pipe_status) != 0) {
  298         fprintf(out, "554 Pipe command reported error %d\r\n", WEXITSTATUS(pipe_status));
  299         return 1;
  300     }
  301 
  302     fprintf(out, "250 Ok, mail was piped\r\n");
  303     if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0)
  304         return 0; /* ignore missing QUIT */
  305     if (strcmp(buf, "QUIT") != 0) {
  306         fprintf(out, "500 Expected QUIT\r\n");
  307         return 1;
  308     }
  309     fprintf(out, "221 Bye\r\n");
  310     return 0;
  311 }
  312 
  313 /* Parse the command line */
  314 int parse_command_line(int argc, char* argv[],
  315         int* print_version, int* print_help,
  316         int* inetd,
  317         const char** interface, int* port,
  318         const char** command)
  319 {
  320     enum {
  321         msmtpd_option_version,
  322         msmtpd_option_help,
  323         msmtpd_option_inetd,
  324         msmtpd_option_port,
  325         msmtpd_option_interface,
  326         msmtpd_option_command
  327     };
  328 
  329     struct option options[] = {
  330         { "version", no_argument, 0, msmtpd_option_version },
  331         { "help", no_argument, 0, msmtpd_option_help },
  332         { "inetd", no_argument, 0, msmtpd_option_inetd },
  333         { "port", required_argument, 0, msmtpd_option_port },
  334         { "interface", required_argument, 0, msmtpd_option_interface },
  335         { "command", required_argument, 0, msmtpd_option_command },
  336         { 0, 0, 0, 0 }
  337     };
  338 
  339     for (;;) {
  340         int c = getopt_long(argc, argv, "", options, NULL);
  341         if (c == -1)
  342             break;
  343         switch (c) {
  344         case msmtpd_option_version:
  345             *print_version = 1;
  346             break;
  347         case msmtpd_option_help:
  348             *print_help = 1;
  349             break;
  350         case msmtpd_option_inetd:
  351             *inetd = 1;
  352             break;
  353         case msmtpd_option_port:
  354             *port = atoi(optarg);
  355             break;
  356         case msmtpd_option_interface:
  357             *interface = optarg;
  358             break;
  359         case msmtpd_option_command:
  360             *command = optarg;
  361             break;
  362         default:
  363             return 1;
  364             break;
  365         }
  366     }
  367     if (argc - optind > 0) {
  368         fprintf(stderr, "%s: too many arguments\n", argv[0]);
  369         return 1;
  370     }
  371     return 0;
  372 }
  373 
  374 int main(int argc, char* argv[])
  375 {
  376     /* Exit status values according to LSB init script recommendations */
  377     const int exit_ok = 0;
  378     const int exit_not_running = 3;
  379 
  380     /* Configuration */
  381     int print_version = 0;
  382     int print_help = 0;
  383     int inetd = 0;
  384     const char* interface = DEFAULT_INTERFACE;
  385     int port = DEFAULT_PORT;
  386     const char* command = DEFAULT_COMMAND;
  387 
  388     /* Command line */
  389     if (parse_command_line(argc, argv,
  390                 &print_version, &print_help,
  391                 &inetd, &interface, &port, &command) != 0) {
  392         return exit_not_running;
  393     }
  394     if (print_version) {
  395         printf("msmtpd version %s\n", VERSION);
  396         printf("Copyright (C) 2018 Martin Lambers.\n"
  397                 "This is free software.  You may redistribute copies of it under the terms of\n"
  398                 "the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.\n"
  399                 "There is NO WARRANTY, to the extent permitted by law.\n");
  400         return exit_ok;
  401     }
  402     if (print_help) {
  403         printf("Usage: msmtpd [option...]\n");
  404         printf("Options:\n");
  405         printf("  --version       print version\n");
  406         printf("  --help          print help\n");
  407         printf("  --inetd         start single SMTP session on stdin/stdout\n");
  408         printf("  --interface=ip  listen on ip instead of %s\n", DEFAULT_INTERFACE);
  409         printf("  --port=number   listen on port number instead of %d\n", DEFAULT_PORT);
  410         printf("  --command=cmd   pipe mails to cmd instead of %s\n", DEFAULT_COMMAND);
  411         return exit_ok;
  412     }
  413 
  414     /* Do it */
  415     signal(SIGPIPE, SIG_IGN); /* Do not terminate when piping fails; we want to handle that error */
  416     if (inetd) {
  417         /* We are no daemon, so we can just signal error with exit status 1 and success with 0 */
  418         return msmtpd_session(stdin, stdout, command);
  419     } else {
  420         int ipv6;
  421         struct sockaddr_in6 sa6;
  422         struct sockaddr_in sa4;
  423         int listen_fd;
  424         int on = 1;
  425 
  426         /* Set interface */
  427         memset(&sa6, 0, sizeof(sa6));
  428         if (inet_pton(AF_INET6, interface, &sa6.sin6_addr) != 0) {
  429             ipv6 = 1;
  430             sa6.sin6_family = AF_INET6;
  431             sa6.sin6_port = htons(port);
  432         } else {
  433             memset(&sa4, 0, sizeof(sa4));
  434             if (inet_pton(AF_INET, interface, &sa4.sin_addr) != 0) {
  435                 ipv6 = 0;
  436                 sa4.sin_family = AF_INET;
  437                 sa4.sin_port = htons(port);
  438             } else {
  439                 fprintf(stderr, "%s: invalid interface\n", argv[0]);
  440                 return exit_not_running;
  441             }
  442         }
  443 
  444         /* Create and set up listening socket */
  445         listen_fd = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0);
  446         if (listen_fd < 0) {
  447             fprintf(stderr, "%s: cannot create socket: %s\n", argv[0], strerror(errno));
  448             return exit_not_running;
  449         }
  450         if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
  451             fprintf(stderr, "%s: cannot set socket option: %s\n", argv[0], strerror(errno));
  452             return exit_not_running;
  453         }
  454         if (bind(listen_fd,
  455                     ipv6 ? (struct sockaddr*)&sa6 : (struct sockaddr*)&sa4,
  456                     ipv6 ? sizeof(sa6) : sizeof(sa4)) < 0) {
  457             fprintf(stderr, "%s: cannot bind to %s:%d: %s\n", argv[0], interface, port, strerror(errno));
  458             return exit_not_running;
  459         }
  460         if (listen(listen_fd, 128) < 0) {
  461             fprintf(stderr, "%s: cannot listen on socket: %s\n", argv[0], strerror(errno));
  462             return exit_not_running;
  463         }
  464 
  465         /* Set up signal handling, in part conforming to freedesktop.org modern daemon requirements */
  466         signal(SIGHUP, SIG_IGN); /* Reloading configuration does not make sense for us */
  467         signal(SIGTERM, SIG_DFL); /* We can be terminated as long as there is no running session */
  468         signal(SIGCHLD, SIG_IGN); /* Make sure child processes do not become zombies */
  469 
  470         /* Accept connection */
  471         for (;;) {
  472             int conn_fd = accept(listen_fd, NULL, NULL);
  473             if (conn_fd < 0) {
  474                 fprintf(stderr, "%s: cannot accept connection: %s\n", argv[0], strerror(errno));
  475                 return exit_not_running;
  476             }
  477             if (fork() == 0) {
  478                 /* Child process */
  479                 FILE* conn;
  480                 int ret;
  481                 signal(SIGTERM, SIG_IGN); /* A running session should not be terminated */
  482                 signal(SIGCHLD, SIG_DFL); /* Make popen()/pclose() work again */
  483                 conn = fdopen(conn_fd, "rb+");
  484                 ret = msmtpd_session(conn, conn, command);
  485                 fclose(conn);
  486                 exit(ret); /* exit status does not really matter since nobody checks it, but still... */
  487             } else {
  488                 /* Parent process */
  489                 close(conn_fd);
  490             }
  491         }
  492     }
  493 
  494     return exit_ok;
  495 }