/*
This file is part of Mailfromd.
Copyright (C) 2003-2020 Sergey Poznyakoff.
Mailfromd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
Mailfromd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Mailfromd. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#ifdef HAVE_GETOPT_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_READLINE_READLINE_H
# include
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include "libmf.h"
#if defined(USE_GNUTLS) && defined(HAVE_GNUTLS_GNUTLS_H)
# include
# define HAVE_TLS
#endif /* USE_GNUTLS and HAVE_GNUTLS_GNUTLS_H */
FILE *trace = NULL; /* diagnostic output */
int port = 0; /* Port number (for smtp mode) */
int verbose;
char *user; /* When started as root, switch to this user privileges */
struct mf_gid_list *grouplist; /* List of additional groups to be retained */
char *sender_hostname;
struct sockaddr *sender_sockaddr;
int mta_daemon (void);
int mta_stdio (void);
int (*mta_mode) (void) = mta_stdio;
char *trace_name = NULL;
int append;
int statedir_option;
#ifdef HAVE_TLS
char *tls_cert; /* TLS sertificate */
char *tls_key; /* TLS key */
char *tls_cafile;
#define DH_BITS 768
#define enable_tls() \
(tls_cafile != NULL || (tls_cert != NULL && tls_key != NULL))
void tls_init (void);
gnutls_dh_params dh_params;
static gnutls_certificate_server_credentials x509_cred;
#endif /* HAVE_TLS */
int interactive;
char *prompt = "(mtasim) ";
void error (const char *, ...);
void smtp_reply (int, char *, ...);
void reset_capa (char *);
void shell_help (void);
void shell (int argc, char **argv);
int define_macro (char *arg);
#define R_CONT 0x8000
#define R_CODEMASK 0xfff
/* Milter-related options */
char *milter_port;
size_t max_body_chunk = 65535;
unsigned long milter_version_option = 0;
unsigned long milter_proto_option = 0;
unsigned long milter_acts_option = 0;
int gacopyz_log_mask;
struct timeval milter_timeouts[GACOPYZ_TO_COUNT] = {
{ GACOPYZ_WRITE_TIMEOUT, 0 },
{ GACOPYZ_READ_TIMEOUT, 0 },
{ GACOPYZ_EOM_TIMEOUT, 0 },
{ GACOPYZ_CONNECT_TIMEOUT, 0 }
};
static void
priv_setup ()
{
if (getuid() == 0 && user)
{
struct passwd *pw = getpwnam (user);
if (!pw)
{
mu_error(_("no such user: %s"), user);
exit (EX_SOFTWARE);
}
if (pw && switch_to_privs (pw->pw_uid, pw->pw_gid, grouplist))
exit (EX_SOFTWARE);
}
}
gacopyz_srv_t gsrv;
void
update_nrcpts (char *name, unsigned n)
{
char buf[128];
snprintf (buf, sizeof buf, "%u", n);
gacopyz_srv_define_macro (gsrv, name, buf);
}
static mu_list_t defnlist;
int
defer_define_macro (char const *arg)
{
char *copy, *p;
copy = mu_strdup (arg);
p = strchr (copy, '=');
if (!p)
{
free (copy);
return 1;
}
*p = 0;
if (!defnlist)
{
mu_list_create (&defnlist);
mu_list_set_destroy_item (defnlist, mu_list_free_item);
}
mu_list_append (defnlist, copy);
return 0;
}
static int
do_define (void *item, void *data)
{
char *name = item;
char *value = name + strlen (name) + 1;
gacopyz_srv_t srv = data;
gacopyz_srv_define_macro (srv, name, value);
return 0;
}
void
flush_deferred_defns (gacopyz_srv_t srv)
{
if (srv)
mu_list_foreach (defnlist, do_define, srv);
mu_list_destroy (&defnlist);
}
/* Expect stuff */
static char expected_code[4];
void
free_expected_code ()
{
expected_code[0] = 0;
}
int
set_expected_code (const char *str)
{
size_t len = strlen (str);
if (len == 0 || !strchr ("12345", str[0]))
return 1;
if (len > 1)
{
if (!mu_isdigit (str[1])
|| (len > 2 && !mu_isdigit (str[2])))
return 1;
}
if (len > 3)
len = 3;
memcpy (expected_code, str, len);
expected_code[len] = 0;
return 0;
}
void
check_expected_code (char *str)
{
int i;
for (i = 0; i < 3 && expected_code[i]; i++)
if (str[i] != expected_code[i])
{
/* fill in the expected code */
for (i = strlen(expected_code); i < 3; i++)
expected_code[i] = '.';
mu_error (_("expected %s, but got %3.3s"), expected_code, str);
if (gsrv)
{
gacopyz_srv_abort (gsrv);
gacopyz_srv_quit (gsrv);
gacopyz_srv_close (gsrv);
gacopyz_srv_destroy (&gsrv);
}
exit (1);
}
expected_code[0] = 0;
}
static char prog_doc[] = N_("mtasim -- MTA simulator for mailfromd");
#ifndef WITH_READLINE
# define OPT_INTERACTIVE MU_OPTION_HIDDEN
#else
# define OPT_INTERACTIVE MU_OPTION_DEFAULT
#endif
static void
opt_mta_mode(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
switch (arg[0])
{
case 'd':
mta_mode = mta_daemon;
break;
case 's':
mta_mode = mta_stdio;
break;
default:
mu_parseopt_error (po, _("unsupported mode"));
exit(po->po_exit_error);
}
}
static void
opt_group(struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
struct group *group = getgrnam(arg);
if (group)
{
if (!grouplist)
grouplist = mf_gid_list_alloc ();
mf_gid_list_add (grouplist, group->gr_gid);
}
else
{
mu_parseopt_error(po, _("unknown group: %s"), arg);
exit (EX_DATAERR);
}
}
static void
opt_define (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
{
if (defer_define_macro (arg))
{
mu_parseopt_error (po, _("wrong assignment format: %s"), arg);
exit (po->po_exit_error);
}
}
static void
opt_milter_version (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
char *p;
unsigned long maj, min, pat;
maj = strtoul (arg, &p, 0);
if (*p == 0)
{
milter_version_option = maj;
return;
}
else if (*p == '.')
{
min = strtoul (p + 1, &p, 0);
if (*p == '.')
{
pat = strtoul (p + 1, &p, 0);
if (*p == 0)
{
milter_version_option = GACOPYZ_SM_MKVER (maj, min, pat);
return;
}
}
else if (*p == 0)
{
milter_version_option = GACOPYZ_SM_MKVER (maj, min, 0);
return;
}
}
mu_parseopt_error (po, _("invalid version syntax (near %s)"), p);
exit (po->po_exit_error);
}
static void
opt_milter_timeout (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
while (*arg)
{
int ind;
struct timeval tv;
char *p;
while (*arg && isspace (*arg))
arg++;
switch (*arg)
{
case 'w':
case 'W':
case 's':
case 'S':
ind = GACOPYZ_TO_WRITE;
break;
case 'r':
case 'R':
ind = GACOPYZ_TO_READ;
break;
case 'e':
case 'E':
ind = GACOPYZ_TO_EOM;
break;
case 'c':
case 'C':
ind = GACOPYZ_TO_CONNECT;
break;
default:
mu_parseopt_error (po, _("unknown timeout: %c"), *arg);
exit (po->po_exit_error);
}
if (*++arg != '=')
{
mu_parseopt_error (po, _("missing '=' after %c"), *arg);
exit (po->po_exit_error);
}
tv.tv_sec = strtoul (arg + 1, &p, 10);
if (*p == '.')
tv.tv_usec = strtoul (p + 1, &p, 10);
while (*p && isspace (*p))
p++;
if (*p == ',')
arg = p + 1;
else if (*p == 0)
arg = p;
else
{
mu_parseopt_error (po, _("unexpected character: %c"), *p);
exit (po->po_exit_error);
}
tv.tv_usec = 0;
milter_timeouts[ind] = tv;
}
}
static void
_sockaddr_error_getopt (void *data, const char *msg, const char *arg)
{
struct mu_parseopt *po = data;
if (arg)
mu_parseopt_error (po, "%s: %s", msg, arg);
else
mu_parseopt_error (po, "%s", msg);
exit (po->po_exit_error);
}
/* family [hostname address [port]] */
static int
convert_sender_sockaddr (int argc, char **argv,
void (*errfun)(void *, const char *, const char *),
void *errdata)
{
int family;
char *p;
struct sockaddr *sa;
if (argc < 1 || argc > 4)
{
errfun (errdata, "Bad number of arguments", NULL);
return -1;
}
if (strcmp (argv[0], "stdio") == 0)
{
switch (argc)
{
case 1:
sender_hostname = mu_strdup ("localhost");
break;
case 2:
sender_hostname = mu_strdup (argv[1]);
break;
default:
errfun (errdata, "Too many arguments for this address family", NULL);
return -1;
}
return 0;
}
if (argc < 3)
{
errfun (errdata, "Too few arguments", NULL);
return -1;
}
if (strcmp (argv[0], "unix") == 0)
family = AF_UNIX;
else if (strcmp (argv[0], "inet") == 0)
family = AF_INET;
else if (strcmp (argv[0], "inet6") == 0)
family = AF_INET6;
else
{
family = strtoul (argv[0], &p, 10);
if (*p)
{
errfun (errdata, "Unrecognized family", NULL);
return -1;
}
}
switch (family)
{
case AF_UNIX: {
struct sockaddr_un *sun;
if (argc == 4)
{
errfun (errdata, "Too many arguments for this address family", NULL);
return -1;
}
sun = mu_alloc (sizeof (sun) + strlen (argv[2]) + 1);
sun->sun_family = family;
strcpy (sun->sun_path, argv[2]);
sa = (struct sockaddr*) sun;
break;
}
case AF_INET: {
struct sockaddr_in *s_in;
unsigned long n;
if (argc < 4)
{
errfun (errdata, "Not enough arguments for this address family",
NULL);
return -1;
}
s_in = mu_alloc (sizeof *s_in);
s_in->sin_family = family;
if (inet_aton (argv[2],
(struct in_addr *) &s_in->sin_addr) != 1)
{
errfun (errdata, "Invalid IP address", NULL);
free (s_in);
return -1;
}
errno = 0;
n = strtoul (argv[3], &p, 10);
if (*p || errno || (s_in->sin_port = n) != n)
{
errfun (errdata, "Invalid port number", NULL);
return -1;
}
s_in->sin_port = htons (s_in->sin_port);
sa = (struct sockaddr*) s_in;
break;
}
case AF_INET6: {
#ifdef GACOPYZ_IPV6
struct addrinfo *res;
int rc;
if (argc < 4)
{
errfun (errdata, "Not enough arguments for this address family",
NULL);
return -1;
}
rc = getaddrinfo(argv[2], argv[3], NULL, &res);
if (rc)
{
errfun (errdata, "Invalid address or service",
gai_strerror(rc));
return -1;
}
sa = mu_alloc (res->ai_addrlen);
memcpy (sa, res->ai_addr, res->ai_addrlen);
freeaddrinfo (res);
#endif
break;
}
default:
errfun (errdata, "Unsupported family", NULL);
return -1;
}
free (sender_hostname);
free (sender_sockaddr);
sender_hostname = mu_strdup (argv[1]);
sender_sockaddr = sa;
return 0;
}
static void
opt_sender_sockaddr (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
struct mu_wordsplit ws;
int rc;
ws.ws_delim = ",";
rc = mu_wordsplit (arg, &ws, MU_WRDSF_DEFFLAGS|MU_WRDSF_DELIM);
if (rc)
{
mu_parseopt_error (po, _("error parsing --sender-sockaddr argument: %s"),
mu_wordsplit_strerror (&ws));
exit (po->po_exit_error);
}
convert_sender_sockaddr (ws.ws_wordc, ws.ws_wordv,
_sockaddr_error_getopt, po);
mu_wordsplit_free (&ws);
}
static void
opt_gacopyz_log (struct mu_parseopt *po, struct mu_option *opt,
char const *arg)
{
int lev = gacopyz_string_to_log_level (arg);
if (lev == -1)
{
mu_parseopt_error (po, _("%s: invalid log level"), arg);
exit (po->po_exit_error);
}
gacopyz_log_mask = SMI_LOG_FROM (lev);
}
static struct mu_option mtasim_options[] = {
MU_OPTION_GROUP (N_("Mode selection")),
{ NULL, 'b', N_("MODE"), MU_OPTION_DEFAULT,
N_("set MTA mode (-bd or -bs)"),
mu_c_string, NULL, opt_mta_mode },
{ "stdio", 0, NULL, MU_OPTION_DEFAULT,
N_("use the SMTP protocol on standard input and output (same as -bs)"),
mu_c_string, NULL, opt_mta_mode, "s" },
{ "daemon", 0, NULL, MU_OPTION_DEFAULT,
N_("run as daemon (same as -bd)"),
mu_c_string, NULL, opt_mta_mode, "d" },
{ "user", 'u', N_("NAME"), MU_OPTION_DEFAULT,
N_("run with this user privileges"),
mu_c_string, &user },
{ "group", 'g', N_("NAME"), MU_OPTION_DEFAULT,
N_("retain the supplementary group NAME when switching to user "
"privileges"),
mu_c_string, NULL, opt_group },
MU_OPTION_GROUP (N_("Operation modifiers")),
{ "define", 'D', N_("MACRO=VALUE"), MU_OPTION_DEFAULT,
N_("define Sendmail macro"),
mu_c_string, NULL, opt_define },
{ "port", 'X', N_("PORT"), MU_OPTION_DEFAULT,
N_("communicate with Milter PORT"),
mu_c_string, &milter_port },
{ "statedir", 0, NULL, MU_OPTION_DEFAULT,
N_("pass temporary directory to mailfromd as its state dir (with -Xauto)"),
mu_c_bool, &statedir_option },
{ "body-chunk", 0, N_("NUMBER"), MU_OPTION_DEFAULT,
N_("set the body chunk for xxfi_body calls"),
mu_c_size, &max_body_chunk },
{ "milter-version", 0, N_("VER"), MU_OPTION_DEFAULT,
N_("force using the given Milter protocol version number"),
mu_c_string, NULL, opt_milter_version },
{ "milter-actions", 0, N_("BITMASK"), MU_OPTION_DEFAULT,
N_("force the given Milter actions"),
mu_c_ulong, &milter_acts_option },
{ "milter-proto", 0, N_("BITMASK"), MU_OPTION_DEFAULT,
N_("set Milter protocol capabilities"),
mu_c_ulong, &milter_proto_option },
{ "milter-timeout", 0, N_("F=V[,F=V...]"), MU_OPTION_DEFAULT,
N_("set milter timeouts"),
mu_c_string, NULL, opt_milter_timeout },
{ "sender-sockaddr", 0,
N_("FAMILY[,HOSTNAME,ADDRESS[,PORT]]"), MU_OPTION_DEFAULT,
N_("set sender socket address"),
mu_c_string, NULL, opt_sender_sockaddr },
{ "interactive", 0, NULL, OPT_INTERACTIVE,
N_("interactive mode"),
mu_c_bool, &interactive },
{ "prompt", 0, N_("STRING"), MU_OPTION_DEFAULT,
N_("set readline prompt"),
mu_c_string, &prompt },
MU_OPTION_GROUP(N_("Debugging and tracing")),
{ "append", 'a', NULL, MU_OPTION_DEFAULT,
N_("append to the trace file"),
mu_c_bool, &append },
{ "trace-file", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("set name of the trace file"),
mu_c_string, &trace_name },
{ "verbose", 'v', NULL, MU_OPTION_DEFAULT,
N_("increase verbosity level"),
mu_c_incr, &verbose },
{ "gacopyz-log", 0, N_("LEVEL"), MU_OPTION_DEFAULT,
N_("set Gacopyz log level"),
mu_c_string, NULL, opt_gacopyz_log },
#ifdef HAVE_TLS
MU_OPTION_GROUP (N_("TLS options")),
{ "tls-cert", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("set name of the TLS certificate file"),
mu_c_string, &tls_cert },
{ "tls-ca", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("set name of the TLS CA file"),
mu_c_string, &tls_cafile },
{ "tls-key", 0, N_("FILE"), MU_OPTION_DEFAULT,
N_("set name of the TLS key file"),
mu_c_string, &tls_key},
#endif
MU_OPTION_END
}, *options[] = { mtasim_options, NULL };
void
alloc_die_func ()
{
mu_error (_("not enough memory"));
abort ();
}
static int
portspec_p (const char *str)
{
size_t len = strlen (str);
static char *proto[] = { "/", "unix:", "local:", "inet:", "inet6:", NULL };
char **p;
for (p = proto; *p; p++)
{
size_t n = strlen (*p);
if (len > n && memcmp (str, *p, n) == 0)
return 1;
}
return 0;
}
pid_t child_pid;
static char *tmpdir;
static int got_sigchld;
static RETSIGTYPE
sig_child (int sig)
{
int status;
got_sigchld = 1;
waitpid((pid_t)-1, &status, 0);
}
static int rmdir_r (const char *name);
static int
recursive_rmdir (const char *name)
{
int rc;
DIR *dir;
struct dirent *ent;
if (chdir (name))
{
mu_error (_("cannot change to directory %s: %s"),
name, mu_strerror (errno));
return 1;
}
dir = opendir (".");
if (!dir)
{
mu_error (_("cannot open directory %s: %s"), name, mu_strerror (errno));
return 1;
}
for (rc = 0; rc == 0 && (ent = readdir (dir));)
{
struct stat st;
if (strcmp (ent->d_name, ".") == 0
|| strcmp (ent->d_name, "..") == 0)
continue;
if (stat (ent->d_name, &st) && errno != ENOENT)
{
mu_error (_("cannot stat file `%s': %s"),
name, mu_strerror (errno));
rc = 1;
}
else if (S_ISDIR (st.st_mode))
rc = rmdir_r (ent->d_name);
else if ((rc = unlink (ent->d_name)) != 0 && errno != ENOENT)
mu_error (_("cannot unlink %s: %s"), ent->d_name, mu_strerror (errno));
}
closedir (dir);
return rc;
}
struct cwd
{
int cwd_fd;
char *cwd_name;
};
static int
save_cwd (struct cwd *p)
{
#ifndef O_SEARCH
# define O_SEARCH 0
#endif
p->cwd_fd = open (".", O_SEARCH);
if (p->cwd_fd != -1)
{
int flags = fcntl (p->cwd_fd, F_GETFD, 0);
if (flags != -1 && fcntl (p->cwd_fd, F_SETFD, flags | FD_CLOEXEC) != -1)
{
p->cwd_name = NULL;
return 0;
}
close (p->cwd_fd);
p->cwd_fd = -1;
}
p->cwd_name = mu_getcwd ();
if (!p->cwd_name)
return -1;
return 0;
}
static int
restore_cwd (struct cwd *p)
{
int rc;
if (p->cwd_fd != -1)
rc = fchdir (p->cwd_fd);
else if (p->cwd_name)
{
rc = chdir (p->cwd_name);
free (p->cwd_name);
}
else
rc = 0;
return rc;
}
static int
rmdir_r (const char *name)
{
int rc;
struct cwd cwd;
if (save_cwd (&cwd))
{
mu_error (_("cannot save current directory: %s"), mu_strerror (errno));
return 1;
}
rc = recursive_rmdir (name);
if (restore_cwd (&cwd))
{
mu_error (_("cannot restore current directory: %s"),
mu_strerror (errno));
rc = 1;
}
if (rc == 0 && rmdir (name))
{
mu_error (_("cannot remove directory %s: %s"),
name, mu_strerror (errno));
return 1;
}
return rc;
}
void
stop_mailfromd (void)
{
if (child_pid > 0)
{
int status;
pid_t pid;
signal (SIGCHLD, SIG_DFL);
kill (child_pid, SIGTERM);
pid = waitpid (child_pid, &status, 0);
if (pid == (pid_t) -1)
mu_error ("waitpid: %s", mu_strerror (errno));
else if (WIFEXITED (status))
{
status = WEXITSTATUS (status);
if (status != 0)
mu_error (_("mailfromd exited with status %d"), status);
}
else if (WIFSIGNALED (status))
mu_error (_("mailfromd terminated on signal %d"), WTERMSIG (status));
else
mu_error (_("mailfromd terminated with unrecognized status"));
}
rmdir_r (tmpdir);
}
void
start_mailfromd (int argc, char **argv)
{
tmpdir = mu_strdup ("/tmp/mtasim-XXXXXX");
if (!mkdtemp (tmpdir))
{
mu_error (_("cannot create temporary directory (%s): %s"),
tmpdir, mu_strerror (errno));
exit (EX_OSERR);
}
atexit (stop_mailfromd);
mu_asprintf (&milter_port, "unix://%s/milter", tmpdir);
signal (SIGCHLD, sig_child);
child_pid = fork ();
if (child_pid == -1)
{
mu_error (_("cannot fork: %s"), mu_strerror (errno));
exit (EX_OSERR);
}
if (child_pid == 0)
{
int xargc = argc + 7 + (statedir_option ? 2 : 0);
char **xargv = mu_alloc ((xargc + 1) * sizeof xargv[0]);
int i;
char *callout_port = NULL;
mu_asprintf (&callout_port, "unix://%s/callout", tmpdir);
xargv[0] = "mailfromd";
for (i = 1; i <= argc; i++)
xargv[i] = argv[i-1];
xargv[i++] = "--mtasim";
xargv[i++] = "--port";
xargv[i++] = milter_port;
xargv[i++] = "--callout-socket";
xargv[i++] = callout_port;
if (statedir_option)
{
xargv[i++] = "--state-directory";
xargv[i++] = tmpdir;
}
xargv[i] = 0;
if (verbose)
{
fprintf (stderr, "executing ");
for (i = 0; i < xargc; i++)
fprintf (stderr, "%s ", xargv[i]);
fprintf (stderr, "\n");
}
execvp (xargv[0], xargv);
mu_error (_("cannot execute mailfromd: %s"), mu_strerror (errno));
exit (127);
}
if (verbose)
fprintf (stderr, "mailfromd PID: %lu\n", (unsigned long) child_pid);
while (access (milter_port + 5, F_OK))
{
struct timeval tv;
if (got_sigchld)
{
mu_error (_("child process exited unexpectedly"));
exit (EX_UNAVAILABLE);
}
tv.tv_sec = 0;
tv.tv_usec = 5;
select (0, NULL, NULL, NULL, &tv);
}
}
#ifdef WITH_READLINE
static char **mta_command_completion (char *cmd, int start, int end);
static char *get_history_file_name (void);
#endif
struct mu_cli_setup cli = {
.optv = options,
.prog_doc = prog_doc,
};
int
main (int argc, char **argv)
{
int status;
mf_init_nls ();
interactive = isatty(0);
mu_alloc_die_hook = alloc_die_func;
mf_getopt (&cli, &argc, &argv, NULL, MF_GETOPT_NO_CONFIG);
priv_setup ();
#ifdef WITH_READLINE
if (interactive)
{
rl_readline_name = mu_program_name;
rl_attempted_completion_function = (rl_completion_func_t*) mta_command_completion;
read_history (get_history_file_name ());
}
#endif
if (trace_name)
{
char *mode = append ? "a" : "w";
trace = fopen (trace_name, mode);
if (!trace)
{
mu_error (_("cannot open trace output: %s"), trace_name);
return 1;
}
}
if (!mta_mode)
{
mu_error (_("use either --stdio or --daemon"));
exit (EX_USAGE);
}
if (milter_port)
{
int rc;
if (gacopyz_log_mask == 0)
{
gacopyz_log_mask = SMI_DEFAULT_LOG_MASK;
if (verbose)
gacopyz_log_mask |= SMI_LOG_MASK (SMI_LOG_DEBUG);
}
gacopyz_set_logger (gacopyz_stderr_log_printer);
if (strcmp (milter_port, "auto") == 0)
start_mailfromd (argc, argv);
if (portspec_p (milter_port))
rc = gacopyz_srv_create (&gsrv, "Test", milter_port, gacopyz_log_mask);
else
rc = gacopyz_srv_create_X (&gsrv, milter_port, gacopyz_log_mask);
if (rc != MI_SUCCESS)
{
mu_error (_("cannot create gacopyz server"));
exit (EX_UNAVAILABLE);
}
if (milter_version_option)
{
unsigned long acts = SMFI_DEFAULT_ACTS, proto = SMFI_DEFAULT_PROT;
if (milter_version_option == 2 || milter_version_option == 3)
{
acts = SMFI_V2_ACTS;
proto = SMFI_V2_PROT;
}
gacopyz_srv_set_version (gsrv, milter_version_option);
gacopyz_srv_set_protocol (gsrv, proto);
gacopyz_srv_set_actions (gsrv, acts);
}
if (milter_proto_option)
gacopyz_srv_set_protocol (gsrv, milter_proto_option);
if (milter_acts_option)
gacopyz_srv_set_actions (gsrv, milter_acts_option);
gacopyz_srv_set_all_timeouts (gsrv, milter_timeouts);
if (gacopyz_srv_open (gsrv) != MI_SUCCESS)
{
mu_error (_("cannot connect to the milter using %s"), milter_port);
exit (EX_UNAVAILABLE);
}
gacopyz_srv_negotiate (gsrv);
if (sender_hostname)
gacopyz_srv_connect (gsrv, sender_hostname, sender_sockaddr);
}
flush_deferred_defns (gsrv);
#ifdef HAVE_TLS
tls_init ();
#endif
status = mta_mode ();
if (trace)
fclose (trace);
#ifdef WITH_READLINE
if (interactive)
write_history (get_history_file_name ());
#endif
return status;
}
static void *in, *out;
static const char *
_def_strerror (int rc)
{
return rc == -1 ? _("end of file reached") : strerror (rc);
}
static int
_def_write (void *sd, char *data, size_t size, size_t * nbytes)
{
int n = write (*(int*) sd, data, size);
if (n != size)
return errno;
if (nbytes)
*nbytes = n;
return 0;
}
static int
_def_read (void *sd, char *data, size_t size, size_t * nbytes)
{
int n = read (*(int*) sd, data, size);
if (n && n != size)
return errno;
if (nbytes)
*nbytes = n;
return 0;
}
static int
_def_close (void *sd)
{
return close (*(int*) sd);
}
int (*_mta_read) (void *, char *, size_t, size_t *) = _def_read;
int (*_mta_write) (void *, char *, size_t, size_t *) = _def_write;
int (*_mta_close) (void *) = _def_close;
const char *(*_mta_strerror) (int) = _def_strerror;
#ifdef HAVE_TLS
static void
_tls_cleanup_x509 (void)
{
if (x509_cred)
gnutls_certificate_free_credentials (x509_cred);
}
static void
generate_dh_params (void)
{
gnutls_dh_params_init (&dh_params);
gnutls_dh_params_generate2 (dh_params, DH_BITS);
}
void
tls_init (void)
{
if (!enable_tls ())
return;
gnutls_global_init ();
atexit (gnutls_global_deinit);
gnutls_certificate_allocate_credentials (&x509_cred);
atexit (_tls_cleanup_x509);
if (tls_cafile)
{
int rc = gnutls_certificate_set_x509_trust_file (x509_cred,
tls_cafile,
GNUTLS_X509_FMT_PEM);
if (rc < 0)
{
gnutls_perror (rc);
return;
}
}
if (tls_cert && tls_key)
gnutls_certificate_set_x509_key_file (x509_cred,
tls_cert, tls_key,
GNUTLS_X509_FMT_PEM);
generate_dh_params ();
gnutls_certificate_set_dh_params (x509_cred, dh_params);
}
static ssize_t
_tls_fd_pull (gnutls_transport_ptr fd, void *buf, size_t size)
{
int rc;
do
{
rc = read ((int) fd, buf, size);
}
while (rc == -1 && errno == EAGAIN);
return rc;
}
static ssize_t
_tls_fd_push (gnutls_transport_ptr fd, const void *buf, size_t size)
{
int rc;
do
{
rc = write ((int) fd, buf, size);
}
while (rc == -1 && errno == EAGAIN);
return rc;
}
static const char *
_tls_strerror (int rc)
{
return gnutls_strerror (rc);
}
static int
_tls_write (void *sd, char *data, size_t size, size_t * nbytes)
{
int rc;
do
rc = gnutls_record_send (sd, data, size);
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc >= 0)
{
if (nbytes)
*nbytes = rc;
return 0;
}
return rc;
}
static int
_tls_read (void *sd, char *data, size_t size, size_t * nbytes)
{
int rc = gnutls_record_recv (sd, data, size);
if (rc >= 0)
{
if (nbytes)
*nbytes = rc;
return 0;
}
return rc;
}
static int
_tls_close (void *sd)
{
if (sd)
{
gnutls_bye (sd, GNUTLS_SHUT_RDWR);
gnutls_deinit (sd);
}
return 0;
}
static gnutls_session
tls_session_init (void)
{
gnutls_session session = 0;
int rc;
gnutls_init (&session, GNUTLS_SERVER);
gnutls_set_default_priority (session);
gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_certificate_server_set_request (session, GNUTLS_CERT_REQUEST);
gnutls_dh_set_prime_bits (session, DH_BITS);
gnutls_transport_set_pull_function (session, _tls_fd_pull);
gnutls_transport_set_push_function (session, _tls_fd_push);
gnutls_transport_set_ptr2 (session,
(gnutls_transport_ptr) in,
(gnutls_transport_ptr) out);
rc = gnutls_handshake (session);
if (rc < 0)
{
gnutls_deinit (session);
gnutls_perror (rc);
return 0;
}
return (gnutls_session) session;
}
void
smtp_starttls (void)
{
gnutls_session session;
smtp_reply (220, "Ready to start TLS");
session = tls_session_init ();
if (session)
{
in = out = session;
_mta_read = _tls_read;
_mta_write = _tls_write;
_mta_close = _tls_close;
_mta_strerror = _tls_strerror;
reset_capa ("STARTTLS");
}
else
smtp_reply (530, "TLS negotiation failed");
}
#endif /* HAVE_TLS */
void
smtp_send_string (char *str, size_t len)
{
int rc;
if (!len)
len = strlen (str);
if (trace)
fprintf (trace, "%s\n", str);
rc = _mta_write (out, str, len, NULL);
if (rc == 0 && str[len-1] != '\n')
rc = _mta_write (out, "\r\n", 2, NULL);
if (rc)
{
mu_error (_("write failed: %s"), _mta_strerror (rc));
abort ();
}
check_expected_code (str);
}
void
smtp_reply (int code, char *fmt, ...)
{
va_list ap;
int cont = code & R_CONT ? '-' : ' ';
static char obuf[512];
int n, rc;
va_start (ap, fmt);
n = snprintf (obuf, sizeof obuf, "%d%c", code & R_CODEMASK, cont);
n += vsnprintf (obuf + n, sizeof obuf - n, fmt, ap);
va_end (ap);
if (trace)
fprintf (trace, "%s\n", obuf);
n += snprintf (obuf + n, sizeof obuf - n, "\r\n");
rc = _mta_write (out, obuf, n, NULL);
if (rc)
{
mu_error (_("write failed: %s"), _mta_strerror (rc));
abort ();
}
check_expected_code (obuf);
}
int
get_input_line (char *buf, size_t bufsize)
{
int i, rc;
#ifdef WITH_READLINE
if (interactive)
{
char *p = readline (prompt);
if (!p)
return -1;
strncpy (buf, p, bufsize);
buf[bufsize - 1] = 0;
add_history (buf);
strcat (buf, "\n");
free (p);
return strlen (buf);
}
#endif
for (i = 0; i < bufsize - 1; i++)
{
size_t n;
rc = _mta_read (in, buf + i, 1, &n);
if (rc)
{
mu_error (_("read failed: %s"), _mta_strerror (rc));
exit (EX_IOERR);
}
if (n == 0)
break;
if (buf[i] == '\n')
{
if (buf[i-1] == '\r')
buf[i-1] = '\n';
else
i++;
break;
}
}
buf[i] = 0;
return i;
}
#define STATE_INIT 0
#define STATE_EHLO 1
#define STATE_MAIL 2
#define STATE_RCPT 3
#define STATE_HEADERS 4
#define STATE_DATA 5
#define STATE_QUIT 6
#define STATE_DOT 7
#define KW_EHLO 0
#define KW_HELO 1
#define KW_MAIL 2
#define KW_RCPT 3
#define KW_DATA 4
#define KW_HELP 5
#define KW_QUIT 6
#define KW_STARTTLS 7
#define KW_RSET 8
struct keyword
{
char *name;
int code;
};
static struct keyword kw[] =
{
{ "EHLO", KW_EHLO },
{ "HELO", KW_HELO },
{ "MAIL", KW_MAIL },
{ "RCPT", KW_RCPT },
{ "DATA", KW_DATA },
{ "HELP", KW_HELP },
{ "QUIT", KW_QUIT },
{ "HELP", KW_HELP },
#ifdef HAVE_TLS
{ "STARTTLS", KW_STARTTLS },
#endif
{ "RSET", KW_RSET },
{ NULL },
};
int
smtp_kw (const char *name)
{
int i;
for (i = 0; kw[i].name != NULL; i++)
if (strcasecmp (name, kw[i].name) == 0)
return kw[i].code;
return -1;
}
#ifdef WITH_READLINE
#define HISTFILE_SUFFIX "_history"
static char *
get_history_file_name ()
{
static char *filename = NULL;
if (!filename)
{
size_t size;
char *home = getenv ("HOME");
if (!home)
{
struct passwd *pw = getpwuid (getuid ());
if (!pw)
return NULL;
home = pw->pw_dir;
}
size = strlen (home) + 2 + strlen (rl_readline_name)
+ sizeof HISTFILE_SUFFIX;
filename = mu_alloc (size);
strcpy (filename, home);
strcat (filename, "/.");
strcat (filename, rl_readline_name);
strcat (filename, HISTFILE_SUFFIX);
}
return filename;
}
static char *
mta_command_generator (const char *text, int state)
{
static int i, len;
const char *name;
if (!state)
{
i = 0;
len = strlen (text);
}
while ((name = kw[i].name))
{
i++;
if (strncasecmp (name, text, len) == 0)
return mu_strdup (name);
}
return NULL;
}
char **
mta_command_completion (char *cmd, int start, int end)
{
if (start == 0)
return rl_completion_matches (cmd, mta_command_generator);
return NULL;
}
#endif
char *mta_capa[] = {
#ifdef HAVE_TLS
"STARTTLS",
#endif
NULL
};
void
reset_capa (char *name)
{
int i;
for (i = 0; mta_capa[i]; i++)
if (strcmp (mta_capa[i], name) == 0)
{
mta_capa[i] = NULL;
break;
}
}
static int tempfail;
static int discard;
static char *nullmailer;
unsigned nrcpt = 0;
unsigned nbadrcpts = 0;
void
smtp_ehlo (int extended, char *domain)
{
int i;
if (gsrv)
{
gacopyz_srv_define_macro (gsrv, "s", domain);
switch (gacopyz_srv_helo (gsrv, domain))
{
case SMFIR_REPLYCODE:
{
if (gacopyz_srv_reply (gsrv, &nullmailer, NULL))
{
if (errno == ENOMEM)
mu_alloc_die ();
else
{
mu_error (_("%s:%d: INTERNAL ERROR: no reply (%s)"),
__FILE__, __LINE__, mu_strerror (errno));
abort ();
}
}
break;
}
case SMFIR_REJECT:
nullmailer = mu_strdup ("Command rejected");
break;
case SMFIR_TEMPFAIL:
tempfail = 1;
break;
case SMFIR_DISCARD:
case SMFIR_SHUTDOWN:
break;
}
}
if (!extended)
{
smtp_reply (250, "pleased to meet you");
return;
}
smtp_reply (R_CONT | 250, "pleased to meet you");
for (i = 0; mta_capa[i]; i++)
smtp_reply (R_CONT | 250, "%s", mta_capa[i]);
smtp_reply (250, "HELP");
}
void
smtp_help (void)
{
int i;
smtp_reply (250|R_CONT, "mtasim (%s); supported SMTP commands:", PACKAGE_STRING);
for (i = 0; kw[i].name; i++)
smtp_reply (250|R_CONT, " %s", kw[i].name);
if (milter_port)
{
smtp_reply (250|R_CONT, "Supported administrative commands:");
shell_help ();
}
smtp_reply (250, "End of help output");
}
#define MSG_TEMPFAIL "451 4.3.2 Please try again later"
#define MSG_REJECT "550 5.7.1 Command rejected"
#define MSG_SHUTDOWN "421 4.7.0 bitbucket closing connection"
int
parse_email_addr (char *arg, char **psender, char **addr, char **host)
{
size_t len;
char *p = arg, *q;
if (*p == '<')
{
len = strlen (p);
if (p[len-1] != '>')
return 1;
p++;
*psender = mu_alloc (len - 1);
if (*psender)
{
memcpy (*psender, p, len - 2);
(*psender)[len - 2] = 0;
}
}
else
*psender = mu_strdup (arg);
if (!*psender)
return 1;
p = *psender;
q = strchr (p, '@');
if (q)
len = q - p;
else
len = strlen (p);
*addr = mu_alloc (len + 1);
if (!*addr)
{
free (*psender);
return 1;
}
memcpy (*addr, p, len);
(*addr)[len] = 0;
if (q)
q++;
else
q = "localhost";
*host = mu_strdup (q);
if (!*host)
{
free (*psender);
free (*addr);
return 1;
}
return 0;
}
static int
process_gacopyz_reply (char *sname, char *arg, int rc, int *state)
{
switch (rc)
{
case SMFIR_REPLYCODE:
{
char *msg;
if (gacopyz_srv_reply (gsrv, &msg, NULL))
{
if (errno == ENOMEM)
mu_alloc_die ();
else
{
mu_error (_("%s:%d: INTERNAL ERROR: no reply (%s)"),
__FILE__, __LINE__, mu_strerror (errno));
abort ();
}
}
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, msg);
smtp_send_string (msg, 0);
free (msg);
return 1;
}
case SMFIR_REJECT:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_REJECT);
smtp_send_string (MSG_REJECT, 0);
return 1;
case SMFIR_DISCARD:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, discard",
sname, arg);
discard = 1;
return 1;
case SMFIR_TEMPFAIL:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_TEMPFAIL);
smtp_send_string (MSG_TEMPFAIL, 0);
return 1;
case SMFIR_SHUTDOWN:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_SHUTDOWN);
smtp_send_string (MSG_SHUTDOWN, 0);
*state = STATE_QUIT;
return 1;
}
return 0;
}
static int
process_data_reply (char *sname, char *arg, int rc, int *state, char **reply)
{
switch (rc)
{
case SMFIR_REPLYCODE:
{
if (gacopyz_srv_reply (gsrv, reply, NULL))
{
if (errno == ENOMEM)
mu_alloc_die ();
else
{
mu_error (_("%s:%d: INTERNAL ERROR: no reply (%s)"),
__FILE__, __LINE__, mu_strerror (errno));
abort ();
}
}
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, *reply);
return 1;
}
case SMFIR_REJECT:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_REJECT);
*reply = mu_strdup (MSG_REJECT);
return 1;
case SMFIR_DISCARD:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, discard",
sname, arg);
discard = 1;
return 1;
case SMFIR_TEMPFAIL:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_TEMPFAIL);
*reply = mu_strdup (MSG_TEMPFAIL);
return 1;
case SMFIR_SHUTDOWN:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_SHUTDOWN);
smtp_send_string (MSG_SHUTDOWN, 0);
*state = STATE_QUIT;
return 1;
}
return 0;
}
/* Check if ws->ws_wordv[1] begins with the string PFX, followed by ':'.
On success, return index of the first word after 'PFX:', removing the
prefix if necessary.
Return 0 otherwise.
*/
size_t
check_address_command (const char *pfx, struct mu_wordsplit *ws)
{
int pfxlen = strlen (pfx);
int arglen = strlen (ws->ws_wordv[1]);
if (ws->ws_wordc >= 2 && arglen > pfxlen
&& strncasecmp (ws->ws_wordv[1], pfx, pfxlen) == 0
&& ws->ws_wordv[1][pfxlen] == ':') {
if (arglen == pfxlen + 1)
return 2;
else
{
memmove (ws->ws_wordv[1], ws->ws_wordv[1] + pfxlen + 1,
arglen - pfxlen);
return 1;
}
}
return 0;
}
static void
smtp_mail (struct mu_wordsplit *ws, int *state)
{
size_t n;
n = check_address_command ("from", ws);
if (n == 0)
{
smtp_reply (501, "Syntax error");
}
else
{
int rc = 0;
char *sender;
char *addr;
char *host;
if (parse_email_addr (ws->ws_wordv[n], &sender, &addr, &host))
{
smtp_reply (553, "5.0.0 sender address syntactically incorrect");
return;
}
if (gsrv)
{
gacopyz_srv_define_macro (gsrv, "f", sender);
gacopyz_srv_define_macro (gsrv, "r", "SMTP");
/* FIXME:
gacopyz_srv_define_macro (gsrv, "s", host);
*/
gacopyz_srv_define_macro (gsrv, "ntries", "0");
gacopyz_srv_define_macro (gsrv, "nrcpts", "0");
gacopyz_srv_define_macro (gsrv, "nbadrcpts", "0");
/* FIXME */
gacopyz_srv_define_macro (gsrv, "mail_mailer", "local");
gacopyz_srv_define_macro (gsrv, "mail_host", host);
gacopyz_srv_define_macro (gsrv, "mail_addr", addr);
gacopyz_srv_define_macro (gsrv, "mail_from", sender);
rc = gacopyz_srv_envfrom (gsrv, ws->ws_wordv + n);
rc = process_gacopyz_reply ("from", ws->ws_wordv[n], rc, state);
}
free (sender);
free (addr);
free (host);
if (rc == 0)
{
smtp_reply (250, "Sender OK");
*state = STATE_MAIL;
}
}
}
static void
smtp_rcpt (struct mu_wordsplit *ws, int *state)
{
size_t n;
nrcpt++;
n = check_address_command ("to", ws);
if (n == 0)
{
smtp_reply (501, "Syntax error");
}
else
{
int rc = 0;
char *sender;
char *addr;
char *host;
if (parse_email_addr (ws->ws_wordv[n], &sender, &addr, &host))
{
smtp_reply (553, "5.0.0 recipient address syntactically incorrect");
nbadrcpts++;
return;
}
if (gsrv)
{
update_nrcpts ("nrcpts", nrcpt);
update_nrcpts ("nbadrcpts", nbadrcpts);
gacopyz_srv_define_macro (gsrv, "rcpt_host", host);
gacopyz_srv_define_macro (gsrv, "rcpt_addr", addr);
rc = gacopyz_srv_envrcpt (gsrv, ws->ws_wordv + n);
rc = process_gacopyz_reply ("to", ws->ws_wordv[n], rc, state);
}
free (sender);
free (addr);
free (host);
if (rc == 0)
{
smtp_reply (250, "Recipient OK");
*state = STATE_RCPT;
}
}
}
int
process_header (mu_opool_t pool, int *state, char **reply)
{
int status = 0;
char *hn, *hv;
mu_opool_append_char (pool, 0);
hn = mu_opool_finish (pool, NULL);
hv = strchr (hn, ':');
if (hv)
{
int rc, len;
*hv++ = 0;
while (*hv && mu_isspace (*hv))
hv++;
len = strlen (hv);
if (len > 0 && hv[len - 1] == '\n')
{
if (--len > 0 && hv[len - 1] == '\r')
len--;
hv[len] = 0;
}
rc = gacopyz_srv_header (gsrv, hn, hv);
status = process_data_reply ("cmd", "header", rc, state, reply);
}
mu_opool_free (pool, hn);
return status;
}
struct body_buf
{
char *bufptr;
size_t level;
};
int
send_body (int state, char *ptr, size_t len, struct body_buf *buf)
{
while (len > 0)
{
size_t s = max_body_chunk - buf->level;
if (s > len)
s = len;
memcpy (buf->bufptr + buf->level, ptr, s);
ptr += s;
len -= s;
buf->level += s;
if (buf->level == max_body_chunk)
{
char *datareply = NULL;
int rc = gacopyz_srv_body (gsrv, (unsigned char*) buf->bufptr,
buf->level);
buf->level = 0;
if (process_data_reply ("cmd", "data", rc, &state, &datareply))
{
if (datareply)
{
smtp_send_string (datareply, 0);
free (datareply);
datareply = 0;
}
}
}
}
return state;
}
void
smtp (void)
{
int state;
char buf[128];
mu_opool_t pool = NULL;
char *datareply = NULL;
struct body_buf body_buf;
struct mu_wordsplit ws;
int wsflags;
body_buf.bufptr = mu_alloc (max_body_chunk);
body_buf.level = 0;
if (milter_port)
{
smtp_reply (220|R_CONT, "mtasim (%s) ready", PACKAGE_STRING);
smtp_reply (220, "Connected to milter %s", milter_port);
}
else
smtp_reply (220, "mtasim (%s) ready", PACKAGE_STRING);
wsflags = MU_WRDSF_NOVAR
| MU_WRDSF_NOCMD
| MU_WRDSF_SQUEEZE_DELIMS
| MU_WRDSF_SHOWERR
| MU_WRDSF_ENOMEMABRT
| MU_WRDSF_WS;
for (state = STATE_INIT; state != STATE_QUIT;)
{
int kw, len;
if (get_input_line (buf, sizeof buf) <= 0)
{
state = STATE_QUIT;
continue;
}
len = strlen (buf);
if (trace)
fprintf (trace, "%s", buf);
if (state != STATE_HEADERS && state != STATE_DATA)
{
mu_rtrim_class (buf, MU_CTYPE_ENDLN);
mu_wordsplit (buf, &ws, wsflags);
wsflags |= MU_WRDSF_REUSE;
if (ws.ws_wordc == 0)
continue;
if (ws.ws_wordv[0][0] == '\\' && milter_port)
{
shell (ws.ws_wordc, ws.ws_wordv);
continue;
}
kw = smtp_kw (ws.ws_wordv[0]);
if (kw != KW_HELP && !sender_hostname)
{
sender_hostname = mu_strdup ("localhost");
if (gsrv)
gacopyz_srv_connect (gsrv, sender_hostname, NULL);
}
switch (kw)
{
case KW_QUIT:
state = STATE_QUIT;
smtp_reply (221, "Done");
continue;
case KW_HELP:
smtp_help ();
continue;
case KW_RSET:
if (gsrv)
gacopyz_srv_abort (gsrv);
state = STATE_INIT;
free (nullmailer);
nullmailer = NULL;
tempfail = 0;
nrcpt = 0;
nbadrcpts = 0;
discard = 0;
free (datareply);
datareply = NULL;
if (pool)
mu_opool_destroy (&pool);
smtp_reply(250, "2.0.0 Reset state");
continue;
}
}
else if (len > 0 && buf[len - 1] == '\n' && buf[len - 2] == '\r')
{
buf[len - 2] = '\n';
len--;
}
buf[len] = 0;
if (state != STATE_INIT)
{
if (nullmailer)
{
smtp_send_string (nullmailer, 0);
continue;
}
if (tempfail)
{
smtp_send_string (MSG_TEMPFAIL, 0);
continue;
}
}
switch (state) {
case STATE_INIT:
switch (kw) {
case KW_EHLO:
case KW_HELO:
if (ws.ws_wordc == 2)
{
smtp_ehlo (kw == KW_EHLO, ws.ws_wordv[1]);
state = STATE_EHLO;
}
else
smtp_reply (501, "%s requires domain address", ws.ws_wordv[0]);
break;
default:
smtp_reply (503, "Polite people say HELO first");
break;
}
break;
case STATE_EHLO:
switch (kw) {
case KW_EHLO:
if (ws.ws_wordc == 2)
{
smtp_ehlo (1, ws.ws_wordv[1]);
}
else
smtp_reply (501, "%s requires domain address", ws.ws_wordv[0]);
break;
case KW_MAIL:
smtp_mail (&ws, &state);
break;
#ifdef HAVE_TLS
case KW_STARTTLS:
smtp_starttls ();
break;
#endif
default:
smtp_reply (503, "Need MAIL command");
}
break;
case STATE_MAIL:
switch (kw) {
case KW_MAIL:
smtp_reply (503, "5.5.0 Sender already specified");
break;
case KW_RCPT:
smtp_rcpt (&ws, &state);
break;
default:
smtp_reply (503, "Need RCPT command");
}
break;
case STATE_RCPT:
switch (kw) {
case KW_RCPT:
smtp_rcpt (&ws, &state);
break;
case KW_DATA:
if (gsrv)
{
int rc = gacopyz_srv_data (gsrv);
if (process_gacopyz_reply ("cmd", "data", rc, &state))
continue;
}
smtp_reply (354,
"Enter mail, end with \".\" on a line by itself");
if (!pool)
mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT);
state = STATE_HEADERS;
break;
default:
smtp_reply (501, "Syntax error");
}
break;
case STATE_HEADERS:
if (strcmp (buf, "\n") == 0)
{
if (gsrv)
{
int rc;
if (pool && mu_opool_head (pool, NULL))
{
if (process_header (pool, &state, &datareply))
continue;
}
rc = gacopyz_srv_eoh (gsrv);
if (process_data_reply ("cmd", "eoh", rc, &state, &datareply))
continue;
}
body_buf.level = 0;
state = STATE_DATA;
}
else if (buf[0] == ' ' || buf[0] == '\t')
{
if (gsrv)
{
mu_opool_append (pool, buf, len - 1);
mu_opool_appendz (pool, "\r\n");
}
}
else if (strcmp (buf, ".") == 0)
{
if (gsrv)
{
int rc = gacopyz_srv_eom (gsrv, NULL, 0);
process_data_reply ("cmd", "eom", rc, &state, &datareply);
/* FIXME: Clear macro table, except for the entries from
command line */
gacopyz_srv_clear_macros (gsrv);
}
if (datareply)
{
smtp_send_string (datareply, 0);
free (datareply);
datareply = 0;
}
else
smtp_reply (250, "Mail accepted for delivery");
state = STATE_EHLO;
}
else if (gsrv)
{
if (pool && mu_opool_head (pool, NULL))
{
if (process_header (pool, &state, &datareply))
continue;
}
mu_opool_append (pool, buf, len - 1);
mu_opool_appendz (pool, "\r\n");
}
break;
case STATE_DATA:
if (strcmp (buf, ".\n") == 0)
{
if (gsrv)
{
int rc;
rc = gacopyz_srv_eom (gsrv, (unsigned char *)body_buf.bufptr,
body_buf.level);
process_data_reply ("cmd", "eom", rc, &state, &datareply);
/* FIXME: Clear macro table, except for the entries from
the command line */
gacopyz_srv_clear_macros (gsrv);
}
if (datareply)
{
smtp_send_string (datareply, 0);
free (datareply);
datareply = 0;
}
else
smtp_reply (250, "Mail accepted for delivery");
state = STATE_EHLO;
}
else if (gsrv)
{
state = send_body (state, buf, len - 1, &body_buf);
if (state == STATE_DATA)
state = send_body (state, "\r\n", 2, &body_buf);
}
break;
}
}
if (wsflags & MU_WRDSF_REUSE)
mu_wordsplit_free (&ws);
if (pool)
mu_opool_destroy (&pool);
if (gsrv)
{
gacopyz_srv_quit (gsrv);
gacopyz_srv_close (gsrv);
gacopyz_srv_destroy (&gsrv);
}
free (body_buf.bufptr);
}
int
mta_daemon ()
{
int on = 1;
struct sockaddr_in address;
int fd;
fd = socket (PF_INET, SOCK_STREAM, 0);
if (fd < 0)
{
perror ("socket");
return 1;
}
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on));
memset (&address, 0, sizeof (address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
if (port)
address.sin_port = htons (port);
else
address.sin_port = 0;
if (bind (fd, (struct sockaddr *) &address, sizeof (address)) < 0)
{
close (fd);
perror ("bind");
return 1;
}
if (!port)
{
socklen_t len = sizeof (address);
int rc = getsockname (fd, (struct sockaddr *) &address, &len);
if (rc)
{
close (fd);
mu_error ("getsockname: %s", mu_strerror (errno));
return 1;
}
port = ntohs (address.sin_port);
printf ("%d\n", port);
fclose (stdout);
}
listen (fd, 5);
while (1)
{
fd_set rfds;
struct sockaddr_in his_addr;
int sfd, status;
socklen_t len;
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
status = select (fd + 1, &rfds, NULL, NULL, NULL);
if (status == -1)
{
if (errno == EINTR)
continue;
perror ("select");
return 1;
}
len = sizeof (his_addr);
if ((sfd = accept (fd, (struct sockaddr *) &his_addr, &len)) < 0)
{
perror ("accept");
return 1;
}
in = out = &fd;
smtp ();
break;
}
return 0;
}
int
mta_stdio ()
{
int ifd = fileno (stdin), ofd = fileno (stdout);
in = &ifd;
out = &ofd;
smtp ();
return 0;
}
int
define_macro (char *arg)
{
char *p;
p = strchr (arg, '=');
if (!p)
return 1;
*p++ = 0;
gacopyz_srv_define_macro (gsrv, arg, p);
return 0;
}
void
undefine_macro (char *arg)
{
gacopyz_srv_del_macro (gsrv, arg);
}
void
undefine_all_macros ()
{
gacopyz_srv_clear_macros (gsrv);
}
void
list_macro (char *name)
{
const char *val;
if (gacopyz_srv_find_macro (gsrv, name, &val) == MI_SUCCESS)
smtp_reply (220, "%s=%s", name, val);
else
smtp_reply (220, "%s undefined", name);
}
struct macro_itr
{
size_t num;
size_t count;
};
int
macprint (const char *name, const char *value, void *data)
{
struct macro_itr *itr = data;
int code = 220;
itr->num++;
if (itr->num < itr->count)
code |= R_CONT;
smtp_reply (code, "%s=%s", name, value);
return 0;
}
void
list_all_macros ()
{
struct macro_itr itr;
itr.num = itr.count = 0;
gacopyz_srv_count_macros (gsrv, &itr.count);
if (itr.count == 0)
smtp_reply (220, "No macros defined");
else
gacopyz_srv_iterate_macros (gsrv, macprint, &itr);
}
static void
_sockaddr_error_shell (void *data, const char *msg, const char *arg)
{
if (arg)
smtp_reply (502, "%s: %s", msg, arg);
else
smtp_reply (502, "%s", msg);
}
/* \Sfamily hostname address [port] */
void
set_sender_socket (int argc, char **argv)
{
if (sender_hostname)
smtp_reply (502, "Sender sockaddr already set");
else
{
int reset = 0;
if (strcmp (argv[0], "\\S") == 0)
{
++argv;
--argc;
}
else
{
argv[0] += 2;
reset = 1;
}
if (convert_sender_sockaddr (argc, argv,
_sockaddr_error_shell, NULL) == 0)
gacopyz_srv_connect (gsrv, sender_hostname, sender_sockaddr);
if (reset)
argv[0] -= 2;
}
}
void
shell_help ()
{
static char *hstr[] = {
" \\Dname=value [name=value...] Define Sendmail macros",
" \\Ecode Expect given SMTP reply code",
" \\L[name] [name...] List macros",
" \\Uname [name...] Undefine Sendmail macros",
" \\Sfamily hostname address [port] Define sender socket address",
};
int hcount = sizeof (hstr) / sizeof (hstr[0]);
int i;
if (sender_hostname)
hcount--;
for (i = 0; i < hcount; i++)
smtp_reply (((i < hcount-1) ? R_CONT : 0) | 250, "%s", hstr[i]);
}
void
shell (int argc, char **argv)
{
switch (argv[0][1])
{
case 'd':
case 'D':
{
int rc = 0;
if (argv[0][2])
rc = define_macro (&argv[0][2]);
while (rc == 0 && --argc)
rc = define_macro (*++argv);
if (rc)
smtp_reply (502, "Malformed administrative command");
}
break;
case 'u':
case 'U':
if (argv[0][2])
{
undefine_macro (&argv[0][2]);
while (--argc)
undefine_macro (*++argv);
}
else if (argc > 1)
while (--argc)
undefine_macro (*++argv);
else
undefine_all_macros ();
break;
case 'l':
case 'L':
if (argv[0][2])
{
list_macro (&argv[0][2]);
while (--argc)
list_macro (*++argv);
}
else if (argc > 1)
while (--argc)
list_macro (*++argv);
else
list_all_macros ();
break;
case 'e':
case 'E':
if (set_expected_code (argv[0] + 2))
smtp_reply (502, "Invalid SMPT code: %s", argv[0] + 2);
break;
case 's':
case 'S':
/* \Sfamily hostname address [port] */
set_sender_socket (argc, argv);
break;
case '?':
shell_help ();
break;
default:
smtp_reply (502, "Unknown administrative command");
break;
}
}
/*
Local Variables:
c-file-style: "gnu"
End:
*/
/* EOF */