"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/msmtpd.c" between
msmtp-1.8.16.tar.xz and msmtp-1.8.17.tar.xz

About: msmtp is an SMTP client with a sendmail compatible interface. It can be used with Mutt and other mail user agents.

msmtpd.c  (msmtp-1.8.16.tar.xz):msmtpd.c  (msmtp-1.8.17.tar.xz)
skipping to change at line 27 skipping to change at line 27
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# include "config.h" # include "config.h"
#endif #endif
#include <stdio.h> #include <stdio.h>
#include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <errno.h> #include <errno.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <time.h>
#include <syslog.h>
#include <sysexits.h> #include <sysexits.h>
#include <getopt.h> #include <getopt.h>
extern char *optarg; extern char *optarg;
extern int optind; extern int optind;
#include "base64.h"
#include "password.h"
#include "xalloc.h"
/* Built-in defaults */ /* Built-in defaults */
static const char* DEFAULT_INTERFACE = "127.0.0.1"; static const char* DEFAULT_INTERFACE = "127.0.0.1";
static const int DEFAULT_PORT = 25; static const int DEFAULT_PORT = 25;
static const int MAX_ACTIVE_SESSIONS = 16;
static const int AUTH_DELAY_SECONDS = 1;
static const int AUTH_DELAY_EXPIRATION_SECONDS = 60;
static const char* DEFAULT_COMMAND = BINDIR "/msmtp -f %F"; static const char* DEFAULT_COMMAND = BINDIR "/msmtp -f %F";
static const size_t SMTP_BUFSIZE = 1024; /* must be at least 512 according to RF C2821 */ static const size_t SMTP_BUFSIZE = 1024; /* must be at least 512 according to RF C2821 */
static const size_t CMD_BLOCK_SIZE = 4096; /* initial buffer size for command */ static const size_t CMD_BLOCK_SIZE = 4096; /* initial buffer size for command */
static const size_t CMD_MAX_BLOCKS = 16; /* limit memory allocation */ static const size_t CMD_MAX_BLOCKS = 16; /* limit memory allocation */
/* Logging */
typedef enum {
log_info = 0,
log_error = 1,
log_nothing = 2
} log_level_t;
typedef struct {
FILE* file; /* if NULL then use syslog */
log_level_t level;
} log_t;
void log_open(int log_to_syslog, const char* log_file_name, log_t* log)
{
log->file = NULL;
log->level = log_info; /* default currently hard-coded */
int log_file_open_failure = 0;
if (log_file_name) {
log->file = fopen(log_file_name, "a");
if (!log->file) {
log_file_open_failure = 1;
log_to_syslog = 1;
}
}
if (log_to_syslog) {
openlog("msmtpd", LOG_PID, LOG_MAIL);
if (log_file_open_failure)
syslog(LOG_ERR, "cannot open log file, using syslog instead");
}
if (!log_file_name && !log_to_syslog) {
log->level = log_nothing;
}
}
void log_close(log_t* log)
{
if (log->level < log_nothing) {
if (log->file)
fclose(log->file);
else
closelog();
}
}
void
#ifdef __GNUC__
__attribute__ ((format (printf, 3, 4)))
#endif
log_msg(log_t* log,
log_level_t msg_level,
const char* msg_format, ...)
{
if (msg_level >= log->level) {
if (log->file) {
long long pid = getpid();
time_t t = time(NULL);
struct tm* tm = localtime(&t);
char time_str[128];
strftime(time_str, sizeof(time_str), "%F %T", tm);
fprintf(log->file, "%s msmtpd[%lld] %s: ", time_str, pid,
msg_level >= log_error ? "error" : "info");
va_list args;
va_start(args, msg_format);
vfprintf(log->file, msg_format, args);
va_end(args);
fputc('\n', log->file);
} else {
int priority = (msg_level >= log_error ? LOG_ERR : LOG_INFO);
va_list args;
va_start(args, msg_format);
vsyslog(priority, msg_format, args);
va_end(args);
}
}
}
/* Read SMTP command from client */ /* Read SMTP command from client */
int read_smtp_cmd(FILE* in, char* buf, int bufsize) int read_smtp_cmd(FILE* in, char* buf, int bufsize)
{ {
if (!fgets(buf, bufsize, in)) if (!fgets(buf, bufsize, in))
return 1; return 1;
size_t len = strlen(buf); size_t len = strlen(buf);
if (buf[len - 1] != '\n') if (buf[len - 1] != '\n')
return 1; return 1;
buf[len - 1] = '\0'; buf[len - 1] = '\0';
if (len - 1 > 0 && buf[len - 2] == '\r') if (len - 1 > 0 && buf[len - 2] == '\r')
skipping to change at line 169 skipping to change at line 256
return 1; return 1;
} }
if (fflush(pipe) != 0) if (fflush(pipe) != 0)
return 1; return 1;
return 0; return 0;
} }
/* SMTP session with input and output from FILE descriptors. /* SMTP session with input and output from FILE descriptors.
* Mails are piped to the given command, where the first occurrence of %F * Mails are piped to the given command, where the first occurrence of %F
* will be replaced with the envelope-from address, and all recipient addresses * will be replaced with the envelope-from address, and all recipient addresses
* will be appended as arguments. */ * will be appended as arguments.
int msmtpd_session(FILE* in, FILE* out, const char* command) *
* Return value:
* 0 if the session ended normally / successfully
* 1 if the session had errors and/or was aborted
* 2 same as 1 and the session had authentication failures
* */
int msmtpd_session(log_t* log, FILE* in, FILE* out,
const char* command,
const char* user, const char* password, int impose_auth_delay)
{ {
char buf[SMTP_BUFSIZE]; char buf[SMTP_BUFSIZE];
char addrbuf[SMTP_BUFSIZE]; char buf2[SMTP_BUFSIZE];
size_t addrlen; size_t addrlen;
char* cmd; char* cmd;
char* tmpcmd; char* tmpcmd;
size_t cmd_blocks; size_t cmd_blocks;
size_t cmd_index; size_t cmd_index;
int envfrom_was_handled; int envfrom_was_handled;
int recipient_was_seen; int recipient_was_seen;
FILE* pipe; FILE* pipe;
int pipe_status; int pipe_status;
size_t i; size_t i;
log_msg(log, log_info, "starting SMTP session");
setlinebuf(out); setlinebuf(out);
fprintf(out, "220 localhost ESMTP msmtpd\r\n"); fprintf(out, "220 localhost ESMTP msmtpd\r\n");
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_error, "client did not send initial command, session ab
orted");
return 1; return 1;
}
if (strncasecmp(buf, "EHLO ", 5) != 0 && strncasecmp(buf, "HELO ", 5) != 0) { if (strncasecmp(buf, "EHLO ", 5) != 0 && strncasecmp(buf, "HELO ", 5) != 0) {
fprintf(out, "500 Expected EHLO or HELO\r\n"); fprintf(out, "500 Expected EHLO or HELO\r\n");
log_msg(log, log_error, "client did not start with EHLO or HELO, session aborted");
return 1; return 1;
} }
fprintf(out, "250 localhost\r\n"); if (user && strncasecmp(buf, "EHLO ", 5) == 0) {
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) fprintf(out, "250-localhost\r\n");
fprintf(out, "250 AUTH PLAIN\r\n");
} else {
fprintf(out, "250 localhost\r\n");
}
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_error, "client did not send second command, session abo
rted");
return 1; return 1;
}
if (user) {
if (strcmp(buf, "QUIT") == 0) {
fprintf(out, "221 Bye\r\n");
log_msg(log, log_info, "client ended session");
return 0;
}
if (strncasecmp(buf, "AUTH PLAIN", 10) != 0) {
fprintf(out, "530 Authentication required\r\n");
log_msg(log, log_info, "client did not authenticate, session aborted
");
return 1;
}
const char* b64 = NULL;
if (buf[10] == ' ') {
b64 = buf + 11;
} else {
fprintf(out, "334 \r\n");
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_error, "client did not send authentication info
rmation, session aborted");
return 1;
}
b64 = buf;
}
size_t buf2_len = SMTP_BUFSIZE;
bool r = base64_decode_ctx(NULL, b64, strlen(b64), buf2, &buf2_len);
int authenticated = 0;
if (r && buf2_len == 1 + strlen(user) + 1 + strlen(password)
&& buf2[0] == '\0'
&& strncmp(buf2 + 1, user, strlen(user)) == 0
&& buf2[1 + strlen(user)] == '\0'
&& strncmp(buf2 + 1 + strlen(user) + 1, password, strlen(passwor
d)) == 0) {
authenticated = 1;
}
if (impose_auth_delay)
sleep(AUTH_DELAY_SECONDS);
if (!authenticated) {
fprintf(out, "535 Authentication failed\r\n");
log_msg(log, log_error, "authentication failed, session aborted");
return 2;
} else {
fprintf(out, "235 Authentication successful\r\n");
log_msg(log, log_info, "authenticated user %s", user);
}
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_error, "client did not send command after authentic
ation, session aborted");
return 1;
}
}
for (;;) { for (;;) {
cmd_index = 0; cmd_index = 0;
envfrom_was_handled = 0; envfrom_was_handled = 0;
recipient_was_seen = 0; recipient_was_seen = 0;
if (strncasecmp(buf, "MAIL FROM:", 10) != 0 && strcasecmp(buf, "QUIT") ! = 0) { if (strncasecmp(buf, "MAIL FROM:", 10) != 0 && strcasecmp(buf, "QUIT") ! = 0) {
fprintf(out, "500 Expected MAIL FROM:<addr> or QUIT\r\n"); fprintf(out, "500 Expected MAIL FROM:<addr> or QUIT\r\n");
log_msg(log, log_error, "client did not send MAIL FROM or QUIT, sess ion aborted");
return 1; return 1;
} }
if (strcasecmp(buf, "QUIT") == 0) { if (strcasecmp(buf, "QUIT") == 0) {
fprintf(out, "221 Bye\r\n"); fprintf(out, "221 Bye\r\n");
log_msg(log, log_info, "client ended session");
return 0; return 0;
} }
if (get_addr(buf + 10, addrbuf, 1, &addrlen) != 0) { if (get_addr(buf + 10, buf2, 1, &addrlen) != 0) {
fprintf(out, "501 Invalid address\r\n"); fprintf(out, "501 Invalid address\r\n");
log_msg(log, log_error, "invalid address in MAIL FROM, session abort ed");
return 1; return 1;
} }
cmd_blocks = 1; cmd_blocks = 1;
while (cmd_blocks * CMD_BLOCK_SIZE < strlen(command) + addrlen + 2 * SMT P_BUFSIZE) while (cmd_blocks * CMD_BLOCK_SIZE < strlen(command) + addrlen + 2 * SMT P_BUFSIZE)
cmd_blocks++; cmd_blocks++;
cmd = malloc(cmd_blocks * CMD_BLOCK_SIZE); cmd = malloc(cmd_blocks * CMD_BLOCK_SIZE);
if (!cmd) { if (!cmd) {
fprintf(out, "554 %s\r\n", strerror(ENOMEM)); fprintf(out, "554 %s\r\n", strerror(ENOMEM));
log_msg(log, log_error, "%s, session aborted", strerror(ENOMEM));
return 1; return 1;
} }
for (i = 0; command[i];) { for (i = 0; command[i];) {
if (!envfrom_was_handled && command[i] == '%' && command[i + 1] == ' F') { if (!envfrom_was_handled && command[i] == '%' && command[i + 1] == ' F') {
memcpy(cmd + cmd_index, addrbuf, addrlen); memcpy(cmd + cmd_index, buf2, addrlen);
cmd_index += addrlen; cmd_index += addrlen;
i += 2; i += 2;
envfrom_was_handled = 1; envfrom_was_handled = 1;
} else { } else {
cmd[cmd_index] = command[i]; cmd[cmd_index] = command[i];
cmd_index++; cmd_index++;
i++; i++;
} }
} }
fprintf(out, "250 Ok\r\n"); fprintf(out, "250 Ok\r\n");
for (;;) { for (;;) {
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) { if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_error, "client did not send command, session ab orted");
free(cmd); free(cmd);
return 1; return 1;
} }
if (!recipient_was_seen) { if (!recipient_was_seen) {
if (strncasecmp(buf, "RCPT TO:", 8) != 0) { if (strncasecmp(buf, "RCPT TO:", 8) != 0) {
fprintf(out, "500 Expected RCPT TO:<addr>\r\n"); fprintf(out, "500 Expected RCPT TO:<addr>\r\n");
log_msg(log, log_error, "client did not send RCPT TO, sessio n aborted");
free(cmd); free(cmd);
return 1; return 1;
} }
} else { } else {
if (strncasecmp(buf, "RCPT TO:", 8) != 0 && strcasecmp(buf, "DAT A") != 0) { if (strncasecmp(buf, "RCPT TO:", 8) != 0 && strcasecmp(buf, "DAT A") != 0) {
fprintf(out, "500 Expected RCPT TO:<addr> or DATA\r\n"); fprintf(out, "500 Expected RCPT TO:<addr> or DATA\r\n");
log_msg(log, log_error, "client did not send RCPT TO or DATA , session aborted");
free(cmd); free(cmd);
return 1; return 1;
} }
} }
if (strcasecmp(buf, "DATA") == 0) { if (strcasecmp(buf, "DATA") == 0) {
break; break;
} else { } else {
if (get_addr(buf + 8, addrbuf, 0, &addrlen) != 0) { if (get_addr(buf + 8, buf2, 0, &addrlen) != 0) {
fprintf(out, "501 Invalid address\r\n"); fprintf(out, "501 Invalid address\r\n");
log_msg(log, log_error, "invalid address in RCPT TO, session aborted");
free(cmd); free(cmd);
return 1; return 1;
} }
if (cmd_index + 1 + addrlen + 1 >= cmd_blocks * CMD_BLOCK_SIZE) { if (cmd_index + 1 + addrlen + 1 >= cmd_blocks * CMD_BLOCK_SIZE) {
cmd_blocks++; cmd_blocks++;
if (cmd_blocks > CMD_MAX_BLOCKS) { if (cmd_blocks > CMD_MAX_BLOCKS) {
fprintf(out, "554 Too many recipients\r\n"); fprintf(out, "554 Too many recipients\r\n");
log_msg(log, log_error, "too many recipients, session ab orted");
free(cmd); free(cmd);
return 1; return 1;
} }
tmpcmd = realloc(cmd, cmd_blocks * CMD_MAX_BLOCKS); tmpcmd = realloc(cmd, cmd_blocks * CMD_MAX_BLOCKS);
if (!tmpcmd) { if (!tmpcmd) {
free(cmd);
fprintf(out, "554 %s\r\n", strerror(ENOMEM)); fprintf(out, "554 %s\r\n", strerror(ENOMEM));
log_msg(log, log_error, "%s, session aborted", strerror(
ENOMEM));
free(cmd);
return 1; return 1;
} }
cmd = tmpcmd; cmd = tmpcmd;
} }
cmd[cmd_index++] = ' '; cmd[cmd_index++] = ' ';
memcpy(cmd + cmd_index, addrbuf, addrlen); memcpy(cmd + cmd_index, buf2, addrlen);
cmd_index += addrlen; cmd_index += addrlen;
fprintf(out, "250 Ok\r\n"); fprintf(out, "250 Ok\r\n");
recipient_was_seen = 1; recipient_was_seen = 1;
} }
} }
cmd[cmd_index++] = '\0'; cmd[cmd_index++] = '\0';
log_msg(log, log_info, "pipe command is %s", cmd);
pipe = popen(cmd, "w"); pipe = popen(cmd, "w");
free(cmd); free(cmd);
if (!pipe) { if (!pipe) {
fprintf(out, "554 Cannot start pipe command\r\n"); fprintf(out, "554 Cannot start pipe command\r\n");
log_msg(log, log_error, "cannot start pipe command, session aborted" );
return 1; return 1;
} }
fprintf(out, "354 Send data\r\n"); fprintf(out, "354 Send data\r\n");
if (smtp_pipe(in, pipe, buf, SMTP_BUFSIZE) != 0) { if (smtp_pipe(in, pipe, buf, SMTP_BUFSIZE) != 0) {
fprintf(out, "554 Cannot pipe mail to command\r\n"); fprintf(out, "554 Cannot pipe mail to command\r\n");
log_msg(log, log_error, "cannot pipe mail to command, session aborte d");
return 1; return 1;
} }
pipe_status = pclose(pipe); pipe_status = pclose(pipe);
if (pipe_status == -1 || !WIFEXITED(pipe_status)) { if (pipe_status == -1 || !WIFEXITED(pipe_status)) {
fprintf(out, "554 Pipe command failed to execute\r\n"); fprintf(out, "554 Pipe command failed to execute\r\n");
log_msg(log, log_error, "pipe command failed to execute, session abo rted");
return 1; return 1;
} else if (WEXITSTATUS(pipe_status) != 0) { } else if (WEXITSTATUS(pipe_status) != 0) {
int return_code = 554; /* permanent error */ int return_code = 554; /* permanent error */
switch (WEXITSTATUS(pipe_status)) { switch (WEXITSTATUS(pipe_status)) {
case EX_NOHOST: case EX_NOHOST:
case EX_UNAVAILABLE: case EX_UNAVAILABLE:
case EX_OSERR: case EX_OSERR:
case EX_TEMPFAIL: case EX_TEMPFAIL:
return_code = 451; /* temporary error */ return_code = 451; /* temporary error */
break; break;
skipping to change at line 326 skipping to change at line 494
case EX_OSFILE: case EX_OSFILE:
case EX_CANTCREAT: case EX_CANTCREAT:
case EX_IOERR: case EX_IOERR:
case EX_PROTOCOL: case EX_PROTOCOL:
case EX_NOPERM: case EX_NOPERM:
case EX_CONFIG: case EX_CONFIG:
default: default:
break; break;
} }
fprintf(out, "%d Pipe command reported error %d\r\n", return_code, W EXITSTATUS(pipe_status)); fprintf(out, "%d Pipe command reported error %d\r\n", return_code, W EXITSTATUS(pipe_status));
log_msg(log, log_error, "pipe command reported error %d, session abo rted", WEXITSTATUS(pipe_status));
return 1; return 1;
} }
fprintf(out, "250 Ok, mail was piped\r\n"); fprintf(out, "250 Ok, mail was piped\r\n");
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) log_msg(log, log_info, "mail was piped successfully");
if (read_smtp_cmd(in, buf, SMTP_BUFSIZE) != 0) {
log_msg(log, log_info, "client ended session without sending QUIT");
break; /* ignore missing QUIT */ break; /* ignore missing QUIT */
}
} }
return 0; return 0;
} }
/* Manage the maximum number of concurrent sessions and impose a delay to
* authentication requests after an authentication failure occured, to make
* brute force attacks infeasible */
volatile sig_atomic_t active_sessions_count;
volatile sig_atomic_t auth_failure_occurred;
volatile time_t last_auth_failure_time;
void sigchld_action(int signum, siginfo_t* si, void* ucontext)
{
(void)signum; /* unused */
(void)ucontext; /* unused */
int wstatus;
if (waitpid(si->si_pid, &wstatus, 0) == si->si_pid) {
int child_exit_status = -1;
if (WIFEXITED(wstatus))
child_exit_status = WEXITSTATUS(wstatus);
if (child_exit_status >= 2) {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
last_auth_failure_time = tp.tv_sec;
auth_failure_occurred = 1;
}
active_sessions_count--;
}
}
/* Parse the command line */ /* Parse the command line */
int parse_command_line(int argc, char* argv[], int parse_command_line(int argc, char* argv[],
int* print_version, int* print_help, int* print_version, int* print_help,
int* inetd, int* inetd,
const char** interface, int* port, const char** interface, int* port,
const char** command) int* log_to_syslog, const char** log_file,
const char** command,
char** user, char** password)
{ {
enum { enum {
msmtpd_option_version, msmtpd_option_version,
msmtpd_option_help, msmtpd_option_help,
msmtpd_option_inetd, msmtpd_option_inetd,
msmtpd_option_port, msmtpd_option_port,
msmtpd_option_interface, msmtpd_option_interface,
msmtpd_option_command msmtpd_option_log,
msmtpd_option_command,
msmtpd_option_auth
}; };
struct option options[] = { struct option options[] = {
{ "version", no_argument, 0, msmtpd_option_version }, { "version", no_argument, 0, msmtpd_option_version },
{ "help", no_argument, 0, msmtpd_option_help }, { "help", no_argument, 0, msmtpd_option_help },
{ "inetd", no_argument, 0, msmtpd_option_inetd }, { "inetd", no_argument, 0, msmtpd_option_inetd },
{ "port", required_argument, 0, msmtpd_option_port }, { "port", required_argument, 0, msmtpd_option_port },
{ "interface", required_argument, 0, msmtpd_option_interface }, { "interface", required_argument, 0, msmtpd_option_interface },
{ "log", required_argument, 0, msmtpd_option_log },
{ "command", required_argument, 0, msmtpd_option_command }, { "command", required_argument, 0, msmtpd_option_command },
{ "auth", required_argument, 0, msmtpd_option_auth },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
}; };
for (;;) { for (;;) {
int option_index = -1; int option_index = -1;
int c = getopt_long(argc, argv, "", options, &option_index); int c = getopt_long(argc, argv, "", options, &option_index);
if (c == -1) if (c == -1)
break; break;
if (optarg && optarg[0] == '\0') { if (optarg && optarg[0] == '\0') {
fprintf(stderr, "%s: option '--%s' requires non-empty argument\n", a rgv[0], fprintf(stderr, "%s: option '--%s' requires non-empty argument\n", a rgv[0],
skipping to change at line 388 skipping to change at line 593
break; break;
case msmtpd_option_inetd: case msmtpd_option_inetd:
*inetd = 1; *inetd = 1;
break; break;
case msmtpd_option_port: case msmtpd_option_port:
*port = atoi(optarg); *port = atoi(optarg);
break; break;
case msmtpd_option_interface: case msmtpd_option_interface:
*interface = optarg; *interface = optarg;
break; break;
case msmtpd_option_log:
if (strcmp(optarg, "none") == 0) {
*log_to_syslog = 0;
*log_file = NULL;
} else if (strcmp(optarg, "syslog") == 0) {
*log_to_syslog = 1;
*log_file = NULL;
} else {
*log_to_syslog = 0;
*log_file = optarg;
}
break;
case msmtpd_option_command: case msmtpd_option_command:
*command = optarg; *command = optarg;
break; break;
case msmtpd_option_auth:
{
char* comma = strchr(optarg, ',');
if (!comma) {
char* tmp_user = xstrdup(optarg);
char* tmp_password = password_get("localhost", tmp_user, pas
sword_service_smtp, 0, 0);
if (!tmp_password) {
fprintf(stderr, "%s: cannot get password for (localhost,
smtp, %s)\n",
argv[0], tmp_user);
free(tmp_user);
return 1;
}
free(*user);
*user = tmp_user;
free(*password);
*password = tmp_password;
} else {
char* tmp_user = xstrndup(optarg, comma - optarg);
char* tmp_password = NULL;
char* errstr = NULL;
if (password_eval(comma + 1, &tmp_password, &errstr) != 0) {
fprintf(stderr, "%s: cannot get password: %s\n", argv[0]
, errstr);
free(tmp_user);
free(errstr);
return 1;
}
free(*user);
*user = tmp_user;
free(*password);
*password = tmp_password;
}
}
break;
default: default:
return 1; return 1;
break; break;
} }
} }
if (argc - optind > 0) { if (argc - optind > 0) {
fprintf(stderr, "%s: too many arguments\n", argv[0]); fprintf(stderr, "%s: too many arguments\n", argv[0]);
return 1; return 1;
} }
return 0; return 0;
skipping to change at line 415 skipping to change at line 665
/* Exit status values according to LSB init script recommendations */ /* Exit status values according to LSB init script recommendations */
const int exit_ok = 0; const int exit_ok = 0;
const int exit_not_running = 3; const int exit_not_running = 3;
/* Configuration */ /* Configuration */
int print_version = 0; int print_version = 0;
int print_help = 0; int print_help = 0;
int inetd = 0; int inetd = 0;
const char* interface = DEFAULT_INTERFACE; const char* interface = DEFAULT_INTERFACE;
int port = DEFAULT_PORT; int port = DEFAULT_PORT;
int log_to_syslog = 0;
const char* log_file = NULL;
const char* command = DEFAULT_COMMAND; const char* command = DEFAULT_COMMAND;
char* user = NULL;
char* password = NULL;
/* Command line */ /* Command line */
if (parse_command_line(argc, argv, if (parse_command_line(argc, argv,
&print_version, &print_help, &print_version, &print_help,
&inetd, &interface, &port, &command) != 0) { &inetd, &interface, &port,
&log_to_syslog, &log_file,
&command,
&user, &password) != 0) {
return exit_not_running; return exit_not_running;
} }
if (print_version) { if (print_version) {
printf("msmtpd version %s\n", VERSION); printf("msmtpd version %s\n", VERSION);
printf("Copyright (C) 2021 Martin Lambers.\n" printf("Copyright (C) 2021 Martin Lambers.\n"
"This is free software. You may redistribute copies of it under the terms of\n" "This is free software. You may redistribute copies of it under the terms of\n"
"the GNU General Public License <http://www.gnu.org/licenses/gpl .html>.\n" "the GNU General Public License <http://www.gnu.org/licenses/gpl .html>.\n"
"There is NO WARRANTY, to the extent permitted by law.\n"); "There is NO WARRANTY, to the extent permitted by law.\n");
return exit_ok; return exit_ok;
} }
if (print_help) { if (print_help) {
printf("Usage: msmtpd [option...]\n"); printf("Usage: msmtpd [option...]\n");
printf("Options:\n"); printf("Options:\n");
printf(" --version print version\n"); printf(" --version print version\n");
printf(" --help print help\n"); printf(" --help print help\n");
printf(" --inetd start single SMTP session on stdin/stdout\n"); printf(" --inetd start single SMTP session on stdin/stdout\n");
printf(" --interface=ip listen on ip instead of %s\n", DEFAULT_INTERFA CE); printf(" --interface=ip listen on ip instead of %s\n", DEFAULT_INTERFA CE);
printf(" --port=number listen on port number instead of %d\n", DEFAUL T_PORT); printf(" --port=number listen on port number instead of %d\n", DEFAUL T_PORT);
printf(" --log=none|syslog|FILE do not log anything (default)\n");
printf(" or log to syslog or log to the given file\n");
printf(" --command=cmd pipe mails to cmd instead of %s\n", DEFAULT_CO MMAND); printf(" --command=cmd pipe mails to cmd instead of %s\n", DEFAULT_CO MMAND);
printf(" --auth=user[,passwordeval] require authentication with this us
er name;\n");
printf(" the password will be retrieved from the given\
n");
printf(" passwordeval command or, if none is given, fro
m\n");
printf(" the key ring or, if that fails, from a prompt.
\n");
return exit_ok; return exit_ok;
} }
/* Do it */ /* Do it */
int ret = exit_ok;
signal(SIGPIPE, SIG_IGN); /* Do not terminate when piping fails; we want to handle that error */ signal(SIGPIPE, SIG_IGN); /* Do not terminate when piping fails; we want to handle that error */
if (inetd) { if (inetd) {
/* We are no daemon, so we can just signal error with exit status 1 and success with 0 */ /* We are no daemon, so we can just signal error with exit status 1 and success with 0 */
return msmtpd_session(stdin, stdout, command); log_t log;
log_open(log_to_syslog, log_file, &log);
int impose_auth_delay = 1; /* since we cannot keep track of auth failure
s in inetd mode */
ret = msmtpd_session(&log, stdin, stdout, command, user, password, impos
e_auth_delay);
ret = (ret == 0 ? 0 : 1);
log_close(&log);
} else { } else {
int ipv6; int ipv6;
struct sockaddr_in6 sa6; struct sockaddr_in6 sa6;
struct sockaddr_in sa4; struct sockaddr_in sa4;
int listen_fd; int listen_fd;
int on = 1; int on = 1;
/* Set interface */ /* Set interface */
memset(&sa6, 0, sizeof(sa6)); memset(&sa6, 0, sizeof(sa6));
if (inet_pton(AF_INET6, interface, &sa6.sin6_addr) != 0) { if (inet_pton(AF_INET6, interface, &sa6.sin6_addr) != 0) {
skipping to change at line 469 skipping to change at line 738
sa6.sin6_family = AF_INET6; sa6.sin6_family = AF_INET6;
sa6.sin6_port = htons(port); sa6.sin6_port = htons(port);
} else { } else {
memset(&sa4, 0, sizeof(sa4)); memset(&sa4, 0, sizeof(sa4));
if (inet_pton(AF_INET, interface, &sa4.sin_addr) != 0) { if (inet_pton(AF_INET, interface, &sa4.sin_addr) != 0) {
ipv6 = 0; ipv6 = 0;
sa4.sin_family = AF_INET; sa4.sin_family = AF_INET;
sa4.sin_port = htons(port); sa4.sin_port = htons(port);
} else { } else {
fprintf(stderr, "%s: invalid interface\n", argv[0]); fprintf(stderr, "%s: invalid interface\n", argv[0]);
return exit_not_running; ret = exit_not_running;
goto exit;
} }
} }
/* Create and set up listening socket */ /* Create and set up listening socket */
listen_fd = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0); listen_fd = socket(ipv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) { if (listen_fd < 0) {
fprintf(stderr, "%s: cannot create socket: %s\n", argv[0], strerror( errno)); fprintf(stderr, "%s: cannot create socket: %s\n", argv[0], strerror( errno));
return exit_not_running; ret = exit_not_running;
goto exit;
} }
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0 ) { if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0 ) {
fprintf(stderr, "%s: cannot set socket option: %s\n", argv[0], strer ror(errno)); fprintf(stderr, "%s: cannot set socket option: %s\n", argv[0], strer ror(errno));
return exit_not_running; ret = exit_not_running;
goto exit;
} }
if (bind(listen_fd, if (bind(listen_fd,
ipv6 ? (struct sockaddr*)&sa6 : (struct sockaddr*)&sa4, ipv6 ? (struct sockaddr*)&sa6 : (struct sockaddr*)&sa4,
ipv6 ? sizeof(sa6) : sizeof(sa4)) < 0) { ipv6 ? sizeof(sa6) : sizeof(sa4)) < 0) {
fprintf(stderr, "%s: cannot bind to %s:%d: %s\n", argv[0], interface , port, strerror(errno)); fprintf(stderr, "%s: cannot bind to %s:%d: %s\n", argv[0], interface , port, strerror(errno));
return exit_not_running; ret = exit_not_running;
goto exit;
} }
if (listen(listen_fd, 128) < 0) { if (listen(listen_fd, 128) < 0) {
fprintf(stderr, "%s: cannot listen on socket: %s\n", argv[0], strerr or(errno)); fprintf(stderr, "%s: cannot listen on socket: %s\n", argv[0], strerr or(errno));
return exit_not_running; ret = exit_not_running;
goto exit;
} }
/* Set up signal handling, in part conforming to freedesktop.org modern daemon requirements */ /* Set up signal handling, in part conforming to freedesktop.org modern daemon requirements */
signal(SIGHUP, SIG_IGN); /* Reloading configuration does not make sense for us */ signal(SIGHUP, SIG_IGN); /* Reloading configuration does not make sense for us */
signal(SIGTERM, SIG_DFL); /* We can be terminated as long as there is no running session */ signal(SIGTERM, SIG_DFL); /* We can be terminated as long as there is no running session */
signal(SIGCHLD, SIG_IGN); /* Make sure child processes do not become zom
bies */ /* Set SIGCHLD signal handler to manage maximum number of active session
s and impose
* authentication delays in case of authentication failures (to make bru
te force attacks unfeasible) */
active_sessions_count = 0;
auth_failure_occurred = 0;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO | SA_RESTART; /* SA_RESTART: res
tart accept() after SIGCHLD */
sa.sa_sigaction = sigchld_action;
sigaction(SIGCHLD, &sa, NULL); /* cannot fail */
/* Accept connection */ /* Accept connection */
for (;;) { for (;;) {
int conn_fd = accept(listen_fd, NULL, NULL); while (active_sessions_count >= MAX_ACTIVE_SESSIONS) {
sleep(1);
}
socklen_t client_len = ipv6 ? sizeof(sa6) : sizeof(sa4);
int conn_fd = accept(listen_fd, ipv6 ? (struct sockaddr*)&sa6 : (str
uct sockaddr*)&sa4, &client_len);
if (conn_fd < 0) { if (conn_fd < 0) {
fprintf(stderr, "%s: cannot accept connection: %s\n", argv[0], s trerror(errno)); fprintf(stderr, "%s: cannot accept connection: %s\n", argv[0], s trerror(errno));
return exit_not_running; ret = exit_not_running;
break;
} }
if (fork() == 0) { char client_ip_str[INET6_ADDRSTRLEN];
int client_port;
if (ipv6) {
inet_ntop(AF_INET6, &sa6.sin6_addr, client_ip_str, sizeof(client
_ip_str));
client_port = ntohs(sa6.sin6_port);
} else {
inet_ntop(AF_INET, &sa4.sin_addr, client_ip_str, sizeof(client_i
p_str));
client_port = ntohs(sa4.sin_port);
}
pid_t pid = fork();
if (pid == 0) {
/* Child process */ /* Child process */
FILE* conn; int impose_auth_delay = 0;
int ret; if (auth_failure_occurred) {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
int seconds_since_last_auth_failure = tp.tv_sec - last_auth_
failure_time;
if (seconds_since_last_auth_failure <= AUTH_DELAY_EXPIRATION
_SECONDS)
impose_auth_delay = 1;
}
signal(SIGTERM, SIG_IGN); /* A running session should not be ter minated */ signal(SIGTERM, SIG_IGN); /* A running session should not be ter minated */
signal(SIGCHLD, SIG_DFL); /* Make popen()/pclose() work again */ signal(SIGCHLD, SIG_DFL); /* Make popen()/pclose() work again */
conn = fdopen(conn_fd, "rb+"); log_t log;
ret = msmtpd_session(conn, conn, command); log_open(log_to_syslog, log_file, &log);
log_msg(&log, log_info, "connection from %s port %d, active sess
ions %d (max %d), auth_delay=%s",
client_ip_str, client_port,
active_sessions_count + 1, MAX_ACTIVE_SESSIONS, impose_a
uth_delay ? "yes" : "no");
FILE* conn = fdopen(conn_fd, "rb+");
int ret = msmtpd_session(&log, conn, conn, command, user, passwo
rd, impose_auth_delay);
fclose(conn); fclose(conn);
exit(ret); /* exit status does not really matter since nobody ch log_msg(&log, log_info, "connection closed");
ecks it, but still... */ log_close(&log);
exit(ret);
} else { } else {
/* Parent process */ /* Parent process */
close(conn_fd); close(conn_fd);
if (pid > 0) {
active_sessions_count++;
} else {
fprintf(stderr, "%s: fork failed: %s\n", argv[0], strerror(e
rrno));
ret = exit_not_running;
break;
}
} }
} }
} }
return exit_ok; exit:
free(user);
free(password);
return ret;
}
/* Die if memory allocation fails. Note that we only use xalloc() etc
* during startup; one msmtpd is running, out of memory conditions are
* handled gracefully. */
void xalloc_die(void)
{
fputs(strerror(ENOMEM), stderr);
fputc('\n', stderr);
exit(3);
} }
 End of changes. 63 change blocks. 
33 lines changed or deleted 391 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)