/*
This file is part of Mailfromd.
Copyright (C) 2003, 2004, 2007, 2008, 2009 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
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#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 */
mu_list_t grouplist; /* List of additional groups to be retained */
#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) ";
int mta_daemon (void);
int mta_stdio (void);
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 int
gid_comp (const void *item, const void *data)
{
return (gid_t) item != (gid_t) data;
}
static void
add_group (const char *gname)
{
struct group *group = getgrnam(gname);
if (group)
{
if (!grouplist)
{
int rc = mu_list_create (&grouplist);
if (rc)
{
mu_error(_("Cannot create list: %s"), mu_strerror(rc));
exit (EX_SOFTWARE);
}
mu_list_set_comparator (grouplist, gid_comp);
}
mu_list_append (grouplist, (void*)group->gr_gid);
}
else
{
mu_error(_("Unknown group: %s"), gname);
exit (EX_DATAERR);
}
}
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 *arg)
{
char *p;
p = strchr (arg, '=');
if (!p)
return 1;
*p++ = 0;
if (!defnlist)
mu_list_create (&defnlist);
mu_list_append (defnlist, arg);
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_do (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 (!c_isdigit (str[1])
|| (len > 2 && !c_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;
}
const char *argp_program_version = "mtasim (" PACKAGE_STRING ")";
const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";
static char doc[] = N_("mtasim -- MTA simulator for mailfromd");
enum {
OPTION_STDIO = 128,
OPTION_GACOPYZ_LOG,
OPTION_DAEMON,
OPTION_TLS_CERT,
OPTION_TLS_CA,
OPTION_TLS_KEY,
OPTION_TRACE_FILE,
OPTION_BODY_CHUNK,
OPTION_MILTER_VERSION,
OPTION_MILTER_PROTO,
OPTION_MILTER_ACTS,
OPTION_NO_INTERACTIVE,
OPTION_PROMPT,
OPTION_STATEDIR
};
#ifndef WITH_READLINE
# define OPT_INTERACTIVE OPTION_HIDDEN
#else
# define OPT_INTERACTIVE 0
#endif
static struct argp_option options[] = {
#define GRP 1
{ NULL, 0, NULL, 0,
N_("Mode selection"), GRP },
{ NULL, 'b', N_("MODE"), 0,
N_("Set MTA mode (-bd or -bs)"), GRP+1 },
{ "stdio", OPTION_STDIO, NULL, 0,
N_("Use the SMTP protocol on standard input and output (same as -bs)"),
GRP+1 },
{ "daemon", OPTION_DAEMON, NULL, 0,
N_("Run as daemon (same as -bd)"),
GRP+1 },
{ "user", 'u', N_("NAME"), 0,
N_("Run with this user privileges"),
GRP+1 },
{ "group", 'g', N_("NAME"), 0,
N_("Retain the supplementary group NAME when switching to user "
"privileges"),
GRP+1 },
#undef GRP
#define GRP 10
{ NULL, 0, NULL, 0,
N_("Operation modifiers"), GRP },
{ "define", 'D', N_("MACRO=VALUE"), 0,
N_("Define Sendmail macro"),
GRP+1 },
{ "port", 'X', N_("PORT"), 0,
N_("Communicate with Milter PORT"),
GRP+1 },
{ "statedir", OPTION_STATEDIR, NULL, 0,
N_("Pass temporary directory to mailfromd as its state dir (with -Xauto)"),
GRP+1 },
{ "body-chunk", OPTION_BODY_CHUNK, N_("NUMBER"), 0,
N_("Set the body chunk for xxfi_body calls"),
GRP+1 },
{ "milter-version", OPTION_MILTER_VERSION, N_("VER"), 0,
N_("Force using the given Milter protocol version number"), GRP+1 },
{ "milter-actions", OPTION_MILTER_ACTS, N_("BITMASK"), 0,
N_("Force the given Milter actions"), GRP+1 },
{ "milter-proto", OPTION_MILTER_PROTO, N_("BITMASK"), 0,
N_("Set Milter protocol capabilities"), GRP+1 },
#undef GRP
#define GRP 20
{ "no-interactive", OPTION_NO_INTERACTIVE, NULL, OPT_INTERACTIVE,
N_("Not-interactive mode (disable readline)"), GRP+1 },
{ "prompt", OPTION_PROMPT, N_("STRING"), OPT_INTERACTIVE,
N_("Set readline prompt"), GRP+1 },
#undef GRP
#define GRP 30
{ NULL, 0, NULL, 0,
N_("Debugging and tracing"), GRP },
{ "append", 'a', NULL, 0,
N_("Append to the trace file"), GRP+1 },
{ "trace-file", OPTION_TRACE_FILE, N_("FILE"), 0,
N_("Set name of the trace file"), GRP+1 },
{ "verbose", 'v', NULL, 0,
N_("Increase verbosity level"),
GRP+1 },
{ "gacopyz-log", OPTION_GACOPYZ_LOG, N_("LEVEL"), 0,
N_("Set Gacopyz log level"), GRP+1 },
#undef GRP
#define GRP 40
#ifdef HAVE_TLS
{ NULL, 0, NULL, 0,
N_("TLS options"), GRP },
{ "tls-cert", OPTION_TLS_CERT, N_("FILE"), 0,
N_("Set name of the TLS certificate file"),
GRP+1 },
{ "tls-ca", OPTION_TLS_CA, N_("FILE"), 0,
N_("Set name of the TLS CA file"),
GRP+1 },
{ "tls-key", OPTION_TLS_KEY, N_("FILE"), 0,
N_("Set name of the TLS key file"),
GRP+1 },
#endif
#undef GRP
{ NULL }
};
int (*mta_mode) (void) = mta_stdio;
char *trace_name = NULL;
int append;
int statedir_option;
static unsigned long
parse_version (char *arg, struct argp_state *state)
{
char *p;
unsigned long maj, min, pat;
maj = strtoul (arg, &p, 0);
if (*p == 0)
return maj;
else if (*p == '.')
{
min = strtoul (p + 1, &p, 0);
if (*p == '.')
{
pat = strtoul (p + 1, &p, 0);
if (*p == 0)
return GACOPYZ_SM_MKVER (maj, min, pat);
}
else if (*p == 0)
return GACOPYZ_SM_MKVER (maj, min, 0);
}
argp_error (state, _("invalid version syntax (near %s)"), p);
return 0;
}
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
char *p;
switch (key)
{
case 'a':
append = 1;
break;
case 'b':
switch (arg[0])
{
case 'd':
mta_mode = mta_daemon;
break;
case 's':
mta_mode = mta_stdio;
break;
default:
argp_error (state, _("unsupported mode"));
}
break;
case OPTION_GACOPYZ_LOG:
{
int lev = gacopyz_string_to_log_level(arg);
if (lev == -1)
argp_error(state, _("%s: invalid log level"), arg);
gacopyz_log_mask = SMI_LOG_FROM (lev);
}
break;
case OPTION_STDIO:
mta_mode = mta_stdio;
break;
case OPTION_DAEMON:
mta_mode = mta_daemon;
break;
#ifdef HAVE_TLS
case OPTION_TLS_CERT:
tls_cert = arg;
break;
case OPTION_TLS_CA:
tls_cafile = arg;
break;
case OPTION_TLS_KEY:
tls_key = arg;
break;
#endif
case 'D':
if (defer_define_macro (arg))
mu_error (_("wrong assignment format: %s"), arg);
break;
case 'g':
add_group (arg);
break;
case OPTION_TRACE_FILE:
trace_name = arg;
break;
case 'u':
user = arg;
break;
case 'X':
milter_port = arg;
break;
case 'v':
verbose++;
break;
case OPTION_BODY_CHUNK:
max_body_chunk = strtoul (arg, &p, 0);
if (*p)
argp_error (state, _("invalid number: %s"), arg);
break;
case OPTION_MILTER_VERSION:
milter_version_option = parse_version (arg, state);
break;
case OPTION_MILTER_PROTO:
milter_proto_option = strtoul (arg, &p, 0);
if (*p)
argp_error (state, _("invalid number: %s"), arg);
break;
case OPTION_MILTER_ACTS:
milter_acts_option = strtoul (arg, &p, 0);
if (*p)
argp_error (state, _("invalid number: %s"), arg);
break;
#ifdef WITH_READLINE
case OPTION_NO_INTERACTIVE:
interactive = 0;
break;
case OPTION_PROMPT:
prompt = arg;
break;
#endif
case OPTION_STATEDIR:
statedir_option = 1;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = {
options,
parse_opt,
NULL,
doc,
NULL,
NULL,
NULL
};
void
xalloc_die ()
{
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;
}
static int
rmdir_r (const char *name)
{
int rc;
struct saved_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 = xstrdup ("/tmp/mtasim-XXXXXX");
if (!mkdtemp (tmpdir))
{
mu_error (_("cannot create temprorary directory (%s): %s"),
tmpdir, mu_strerror (errno));
exit (EX_OSERR);
}
atexit (stop_mailfromd);
milter_port = xstrdup ("unix:/tmp/mtasim-XXXXXX/socket");
memcpy (milter_port + 5, tmpdir, strlen (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 + 5 + (statedir_option ? 2 : 0);
char **xargv = xmalloc ((xargc + 1) * sizeof xargv[0]);
int i;
xargv[0] = "mailfromd";
for (i = 1; i <= argc; i++)
xargv[i] = argv[i-1];
xargv[i++] = "--mtasim";
xargv[i++] = "--remove";
xargv[i++] = "--port";
xargv[i++] = milter_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);
}
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
static void
version (FILE *stream, struct argp_state *state)
{
mailfromd_version("mtasim", stream);
}
int
main (int argc, char **argv)
{
int status, index;
mf_init_nls ();
interactive = isatty(0);
if (!program_invocation_short_name)
program_invocation_short_name = argv[0];
argp_program_version_hook = version;
if (argp_parse (&argp, argc, argv, 0, &index, NULL))
exit (EX_USAGE);
priv_setup ();
#ifdef WITH_READLINE
if (interactive)
{
rl_readline_name = program_invocation_short_name;
rl_attempted_completion_function = (CPPFunction*) 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;
}
}
argc -= index;
argv += index;
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_connect (gsrv) != MI_SUCCESS)
{
mu_error (_("cannot connect to the milter using %s"), milter_port);
exit (EX_UNAVAILABLE);
}
gacopyz_srv_negotiate (gsrv);
}
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;
strcat(buf, "\n");
free (p);
add_history (buf);
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 = xmalloc (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 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 *
skipws (char *str)
{
while (*str && c_isspace (*(u_char *) str))
str++;
return str;
}
char *
skipword (char *str)
{
while (*str && !c_isspace (*(u_char *) str))
str++;
return str;
}
int
argcv_split (char *buf, int *pargc, char ***pargv)
{
char *t;
int i, argc = 0;
char **argv;
t = buf;
do
{
argc++;
t = skipws (t);
}
while (*t && (t = skipword (t)));
argv = calloc (argc, sizeof (*argv));
for (i = 0, t = strtok (buf, " \t"); t; i++, t = strtok (NULL, " \t"))
argv[i] = strdup (t);
argv[i] = NULL;
*pargc = argc - 1;
*pargv = argv;
return 0;
}
int
argcv_free (int argc, char **argv)
{
while (--argc >= 0)
if (argv[argc])
free (argv[argc]);
free (argv);
return 1;
}
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:
{
char *msg;
size_t size;
gacopyz_srv_reply (gsrv, &msg, &size);
nullmailer = strdup (msg);
break;
}
case SMFIR_REJECT:
nullmailer = 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 = malloc (len - 1);
if (*psender)
{
memcpy (*psender, p, len - 2);
(*psender)[len - 2] = 0;
}
}
else
*psender = strdup (arg);
if (!*psender)
return 1;
p = *psender;
q = strchr (p, '@');
if (q)
len = q - p;
else
len = strlen (p);
*addr = malloc (len + 1);
if (!*addr)
{
free (*psender);
return 1;
}
memcpy (*addr, p, len);
(*addr)[len] = 0;
if (q)
q++;
else
q = "localhost";
*host = 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;
size_t size;
gacopyz_srv_reply (gsrv, &msg, &size);
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, msg);
smtp_send_string (msg, 0);
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:
{
char *msg;
size_t size;
gacopyz_srv_reply (gsrv, &msg, &size);
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, msg);
*reply = strdup (msg);
return 1;
}
case SMFIR_REJECT:
if (verbose)
fprintf (stderr,
"Gacopyz: %s=%s, reply=%s",
sname, arg, MSG_REJECT);
*reply = 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 = 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 (*PARGV)[1] begins with the string PFX, followed by ':'.
Return 0 if so, 1 otherwise.
If any characters follow the semicolon, reformat *PARGV so that
[1] contains only PFX:, [2] contains the characters in question,
and the rest of entries after [2] is properly reindexed.
*/
int
check_address_command(const char *pfx, int *pargc, char ***pargv)
{
int argc = *pargc;
char **argv = *pargv;
int pfxlen = strlen (pfx);
int arglen = strlen (argv[1]);
if (argc >= 2 && arglen > pfxlen
&& strncasecmp (argv[1], pfx, pfxlen) == 0
&& argv[1][pfxlen] == ':') {
if (arglen > pfxlen + 1)
{
argc++;
argv = xrealloc (argv, (argc + 1) * sizeof (argv[0]));
memmove (&argv[2], &argv[1], (argc - 1) * sizeof argv[1]);
argv[2] = xstrdup (argv[1] + pfxlen + 1);
argv[1][pfxlen + 1] = 0;
*pargc = argc;
*pargv = argv;
}
return 0;
}
return 1;
}
static void
smtp_rcpt (int *pargc, char ***pargv, int *state)
{
nrcpt++;
if (check_address_command("to", pargc, pargv) == 0)
{
char **argv = *pargv;
int rc;
char *sender;
char *addr;
char *host;
if (parse_email_addr (argv[2], &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, argv + 2);
free (sender);
free (addr);
free (host);
if (process_gacopyz_reply ("to", argv[2], rc, state))
return;
}
else
{
free (sender);
free (addr);
free (host);
}
smtp_reply (250, "Recipient OK");
*state = STATE_RCPT;
}
else
smtp_reply (501, "Syntax error");
}
int
process_header (struct obstack *stk, size_t header_size,
int *state, char **reply)
{
int status = 0;
char *hn, *hv;
obstack_1grow (stk, 0);
hn = obstack_finish (stk);
hv = strchr (hn, ':');
if (hv)
{
int rc, len;
*hv++ = 0;
while (*hv && c_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);
}
obstack_free (stk, 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, 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];
struct obstack stk;
int stk_init = 0;
size_t header_size = 0;
char *datareply = NULL;
struct body_buf body_buf;
body_buf.bufptr = xmalloc (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);
for (state = STATE_INIT; state != STATE_QUIT;)
{
int argc;
char **argv;
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)
{
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
len--;
buf[len] = 0;
argcv_split (buf, &argc, &argv);
if (argc == 0)
continue;
if (argv[0][0] == '\\' && milter_port)
{
shell (argc, argv);
argcv_free (argc, argv);
continue;
}
kw = smtp_kw (argv[0]);
switch (kw)
{
case KW_QUIT:
state = STATE_QUIT;
smtp_reply (221, "Done");
argcv_free (argc, argv);
continue;
case KW_HELP:
smtp_help ();
continue;
case KW_RSET:
state = STATE_INIT;
free (nullmailer);
nullmailer = NULL;
tempfail = 0;
nrcpt = 0;
nbadrcpts = 0;
discard = 0;
header_size = 0;
free (datareply);
datareply = NULL;
if (stk_init)
obstack_free (&stk, NULL);
stk_init = 0;
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 (argc == 2)
{
smtp_ehlo (kw == KW_EHLO, argv[1]);
state = STATE_EHLO;
}
else
smtp_reply (501, "%s requires domain address", argv[0]);
break;
default:
smtp_reply (503, "Polite people say HELO first");
break;
}
break;
case STATE_EHLO:
switch (kw) {
case KW_EHLO:
if (argc == 2)
{
smtp_ehlo (1, argv[1]);
}
else
smtp_reply (501, "%s requires domain address", argv[0]);
break;
case KW_MAIL:
if (check_address_command("from", &argc, &argv) == 0)
{
int rc;
char *sender;
char *addr;
char *host;
if (parse_email_addr (argv[2], &sender, &addr, &host))
{
smtp_reply (553, "5.0.0 sender address syntactically incorrect");
break;
}
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, argv + 2);
free (sender);
free (addr);
free (host);
if (process_gacopyz_reply ("from", argv[2], rc, &state))
continue;
}
else
{
free (sender);
free (addr);
free (host);
}
smtp_reply (250, "Sender OK");
state = STATE_MAIL;
}
else
smtp_reply (501, "Syntax error");
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 (&argc, &argv, &state);
break;
default:
smtp_reply (503, "Need RCPT command");
}
break;
case STATE_RCPT:
switch (kw) {
case KW_RCPT:
smtp_rcpt (&argc, &argv, &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 (!stk_init)
{
obstack_init (&stk);
stk_init = 1;
header_size = 0;
}
state = STATE_HEADERS;
break;
default:
smtp_reply (501, "Syntax error");
}
break;
case STATE_HEADERS:
if (strcmp (buf, "\n") == 0)
{
if (gsrv)
{
int rc;
if (header_size)
{
rc = process_header (&stk, header_size, &state,
&datareply);
header_size = 0;
if (rc)
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)
{
obstack_grow (&stk, buf, len - 1);
obstack_grow (&stk, "\r\n", 2);
header_size += len + 1;
}
}
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 (header_size)
{
int rc = process_header (&stk, header_size, &state,
&datareply);
header_size = 0;
if (rc)
continue;
}
obstack_grow (&stk, buf, len - 1);
obstack_grow (&stk, "\r\n", 2);
header_size += len + 1;
}
break;
case STATE_DATA:
if (strcmp (buf, ".\n") == 0)
{
if (gsrv)
{
int rc;
rc = gacopyz_srv_eom (gsrv, 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 (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)
{
int 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, len, status;
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 = (void *) fd;
smtp ();
break;
}
return 0;
}
int
mta_stdio ()
{
in = (void *) fileno (stdin);
out = (void *) fileno (stdout);
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);
}
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",
};
#define hcount (sizeof (hstr) / sizeof (hstr[0]))
int i;
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 '?':
shell_help ();
break;
default:
smtp_reply (502, "Unknown administrative command");
break;
}
}
/*
Local Variables:
c-file-style: "gnu"
End:
*/
/* EOF */