/* 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 */