diff options
Diffstat (limited to 'mda/lmtpd/lmtpd.c')
-rw-r--r-- | mda/lmtpd/lmtpd.c | 902 |
1 files changed, 902 insertions, 0 deletions
diff --git a/mda/lmtpd/lmtpd.c b/mda/lmtpd/lmtpd.c new file mode 100644 index 000000000..2ffdd6ab5 --- /dev/null +++ b/mda/lmtpd/lmtpd.c @@ -0,0 +1,902 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2007-2019 Free Software Foundation, Inc. + + GNU Mailutils 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, or (at your option) + any later version. + + GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */ + +#include "libmda.h" +#include <mailutils/server.h> +#include <mailutils/daemon.h> +#include <tcpwrap.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/un.h> + +static const char *program_version = "lmtpd (" PACKAGE_STRING ")"; + +static mu_m_server_t server; +static int reuse_lmtp_address = 1; +static int mda_transcript; +static mu_list_t lmtp_groups; + +static int +cb2_group (const char *gname, void *data) +{ + mu_list_t list = data; + struct group *group; + + group = getgrnam (gname); + if (!group) + mu_error (_("unknown group: %s"), gname); + else + mu_list_append (list, (void*) (intptr_t) group->gr_gid); + return 0; +} + +static int +cb_group (void *data, mu_config_value_t *arg) +{ + mu_list_t *plist = data; + + if (!*plist) + mu_list_create (plist); + return mu_cfg_string_value_cb (arg, cb2_group, *plist); +} + +static int +cb_listen (void *data, mu_config_value_t *val) +{ + struct mu_sockaddr *s; + + if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) + return 1; + if (mu_m_server_parse_url (server, val->v.string, &s)) + return 1; + mu_m_server_listen (server, s, MU_IP_TCP); + return 0; +} + +static void +set_foreground (struct mu_parseopt *po, struct mu_option *opt, + char const *arg) +{ + mu_m_server_set_foreground (server, 1); +} + +static void +set_stderr (struct mu_parseopt *po, struct mu_option *opt, char const *arg) +{ + mu_log_syslog = 0; +} + +static void +set_inetd_mode (struct mu_parseopt *po, struct mu_option *opt, + char const *arg) +{ + mu_m_server_set_mode (server, MODE_INTERACTIVE); +} + +static void +set_daemon_mode (struct mu_parseopt *po, struct mu_option *opt, + char const *arg) +{ + mu_m_server_set_mode (server, MODE_DAEMON); + if (arg) + { + size_t max_children; + char *errmsg; + int rc = mu_str_to_c (arg, mu_c_size, &max_children, &errmsg); + if (rc) + { + mu_parseopt_error (po, _("%s: bad argument"), arg); + exit (po->po_exit_error); + } + mu_m_server_set_max_children (server, max_children); + } +} + +static struct mu_option lmtpd_options[] = { + MU_OPTION_GROUP (N_("General options")), + { "foreground", 0, NULL, MU_OPTION_DEFAULT, + N_("remain in foreground"), + mu_c_bool, NULL, set_foreground }, + { "inetd", 'i', NULL, MU_OPTION_DEFAULT, + N_("run in inetd mode"), + mu_c_bool, NULL, set_inetd_mode }, + { "daemon", 'd', N_("NUMBER"), MU_OPTION_ARG_OPTIONAL, + N_("runs in daemon mode with a maximum of NUMBER children"), + mu_c_string, NULL, set_daemon_mode }, + { "stderr", 0, NULL, MU_OPTION_DEFAULT, + N_("log to standard error"), + mu_c_string, NULL, set_stderr }, + { "transcript", 0, NULL, MU_OPTION_DEFAULT, + N_("enable session transcript"), + mu_c_bool, &mda_transcript }, + MU_OPTION_END +}, *options[] = { lmtpd_options, NULL }; + +struct mu_cfg_param lmtp_cfg_param[] = { + { "group", mu_cfg_callback, &lmtp_groups, 0, cb_group, + N_("In LMTP mode, retain these supplementary groups."), + N_("groups: list of string") }, + { "listen", mu_cfg_callback, NULL, 0, cb_listen, + N_("In LMTP mode, listen on the given URL. Valid URLs are:\n" + " tcp://<address: string>:<port: number> (note that port is " + "mandatory)\n" + " file://<socket-file-name>\n" + "or socket://<socket-file-name>"), + N_("url: string") }, + { "reuse-address", mu_c_bool, &reuse_lmtp_address, 0, NULL, + N_("Reuse existing address (LMTP mode). Default is \"yes\".") }, + { "filter", mu_cfg_section, NULL, 0, NULL, + N_("Add a message filter") }, + { ".server", mu_cfg_section, NULL, 0, NULL, + N_("LMTP server configuration.") }, + TCP_WRAPPERS_CONFIG + { NULL } +}; + +static char *capa[] = { + "auth", + "debug", + "logging", + "mailbox", + "locking", + "mailer", + "sieve", + "deliver", + "forward", + "quota", + "script", + NULL +}; + +static struct mu_cli_setup cli = { + options, + lmtp_cfg_param, + N_("GNU lmtpd -- local mail transfer protocol daemon."), +}; + +static int lmtp_connection (int fd, struct sockaddr *sa, int salen, + struct mu_srv_config *pconf, + void *data); +static int lmtp_server (void); + + +int +main (int argc, char **argv) +{ + umask (0077); + + /* Native Language Support */ + MU_APP_INIT_NLS (); + + /* Default locker settings */ + mu_locker_set_default_flags (MU_LOCKER_PID|MU_LOCKER_RETRY, + mu_locker_assign); + mu_locker_set_default_retry_timeout (1); + mu_locker_set_default_retry_count (300); + + /* Register needed modules */ + MU_AUTH_REGISTER_ALL_MODULES (); + + /* Register all supported mailbox and mailer formats */ + mu_register_all_formats (); + mu_registrar_record (mu_smtp_record); + + mda_filter_cfg_init (); + mu_tcpwrapper_cfg_init (); + mu_acl_cfg_init (); + mda_cli_capa_init (); + + mu_m_server_create (&server, program_version); + mu_m_server_set_conn (server, lmtp_connection); + mu_m_server_set_prefork (server, mu_tcp_wrapper_prefork); + mu_m_server_set_mode (server, MODE_INTERACTIVE); + mu_m_server_set_max_children (server, 20); + mu_m_server_set_timeout (server, 600); + mu_m_server_cfg_init (server, NULL); + + /* Parse command line */ + mu_cli (argc, argv, &cli, capa, server, &argc, &argv); + + mu_stdstream_strerr_setup (mu_log_syslog ? + MU_STRERR_SYSLOG : MU_STRERR_STDERR); + + if (argc) + { + mu_error (_("too many arguments")); + return EX_USAGE; + } + + return lmtp_server (); +} + +static mu_stream_t +lmtp_transcript (mu_stream_t iostream) +{ + int rc; + mu_stream_t dstr, xstr; + + rc = mu_dbgstream_create (&dstr, MU_DIAG_DEBUG); + if (rc) + mu_error (_("cannot create debug stream; transcript disabled: %s"), + mu_strerror (rc)); + else + { + rc = mu_xscript_stream_create (&xstr, iostream, dstr, NULL); + if (rc) + mu_error (_("cannot create transcript stream: %s"), + mu_strerror (rc)); + else + { + /* FIXME: Would do a mu_stream_unref (iostream) here, + however mu_xscript_stream_create *may* steal the reference. + This should be fixed in mu_xscript_stream_create. */ + iostream = xstr; + } + } + return iostream; +} + +static void +lmtp_reply (mu_stream_t iostr, char *code, char *enh, char *fmt, ...) +{ + va_list ap; + char *str = NULL; + size_t size = 0; + + va_start (ap, fmt); + mu_vasnprintf (&str, &size, fmt, ap); + va_end (ap); + + if (!str) + { + mu_error (_("not enough memory")); + exit (EX_TEMPFAIL); + } + + while (*str) + { + char *end = strchr (str, '\n'); + + if (end) + { + size_t len = end - str; + mu_stream_printf (iostr, "%s-", code); + if (enh) + mu_stream_printf (iostr, "%s ", enh); + mu_stream_printf (iostr, "%.*s\r\n", (int) len, str); + for (str = end; *str && *str == '\n'; str++); + } + else + { + mu_stream_printf (iostr, "%s ", code); + if (enh) + mu_stream_printf (iostr, "%s ", enh); + mu_stream_printf (iostr, "%s\r\n", str); + str += strlen (str); + } + } +} + +enum lmtp_state + { + state_none, + + state_init, + state_lhlo, + state_mail, + state_rcpt, + state_data, + state_quit, + state_dot, + + state_end + }; + +#define NSTATE ((int) state_end + 1) + +enum lmtp_command + { + cmd_unknown, + cmd_lhlo, + cmd_mail, + cmd_rcpt, + cmd_data, + cmd_quit, + cmd_rset, + cmd_help, + cmd_dot + }; + +#define NCMD ((int)cmd_dot + 1) + +#define SNO state_none +#define SIN state_init +#define SHL state_lhlo +#define SML state_mail +#define SRC state_rcpt +#define SDA state_data +#define SQT state_quit +#define SDT state_dot +#define SEN state_end + +static int transtab[NCMD][NSTATE] = { +/* state_ SNO SIN SHL SML SRC SDA SQT SDT SEN */ +/* unkn */ { SNO, SNO, SNO, SNO, SNO, SNO, SNO, SNO, SEN }, +/* lhlo */ { SNO, SHL, SNO, SNO, SNO, SNO, SNO, SNO, SNO }, +/* mail */ { SNO, SNO, SML, SNO, SNO, SNO, SNO, SNO, SNO }, +/* rcpt */ { SNO, SNO, SNO, SRC, SRC, SNO, SNO, SNO, SNO }, +/* data */ { SNO, SNO, SNO, SNO, SDA, SNO, SNO, SNO, SNO }, +/* quit */ { SNO, SEN, SEN, SEN, SEN, SEN, SEN, SEN, SEN }, +/* rset */ { SNO, SIN, SIN, SIN, SIN, SIN, SIN, SIN, SNO }, +/* help */ { SNO, SIN, SHL, SML, SRC, SDT, SQT, SDT, SEN }, +/* dot */ { SNO, SNO, SNO, SNO, SNO, SQT, SNO, SNO, SNO }, +}; + + +/* Delivery data */ +static char *lhlo_domain; /* Sender domain */ +static char *mail_from; /* Sender address */ +static mu_list_t rcpt_list; /* Recipient addresses */ +static mu_message_t mesg; /* Collected message */ + + +static int +cfun_unknown (mu_stream_t iostr, char *arg) +{ + lmtp_reply (iostr, "500", "5.5.1", "Command unrecognized"); + return 0; +} + + +static void +add_default_domain (char *str, int len, char **pret) +{ + *pret = malloc (len + 1 + strlen (lhlo_domain) + 1); + if (!*pret) + { + mu_error (_("not enough memory")); + exit (EX_SOFTWARE); + } + memcpy (*pret, str, len); + (*pret)[len] = '@'; + strcpy (*pret + len + 1, lhlo_domain); +} + +#define MAILER_DAEMON "MAILER-DAEMON" + +static int +check_address (char *arg, int with_domain, char **pret) +{ + if (strchr (arg, '@') == 0) + { + char *addr = NULL; + size_t addrlen = 0; + + if (*arg == '<') + { + size_t len = strlen (arg); + if (arg[len - 1] == '>') + { + if (len == 2) /* null address */ + { + if (!with_domain) + /* Null address is only legal in mail from */ + return 1; + addr = MAILER_DAEMON; + addrlen = sizeof MAILER_DAEMON - 1; + } + else + { + addr = arg + 1; + addrlen = len - 2; + } + } + else + return 1; + } + else + { + addr = arg; + addrlen = strlen (arg); + } + + if (with_domain) + add_default_domain (addr, addrlen, pret); + else + { + *pret = malloc (addrlen + 1); + memcpy (*pret, addr, addrlen); + (*pret)[addrlen] = 0; + } + } + else + { + mu_address_t addr; + char *s; + int rc = mu_address_create (&addr, arg); + if (rc) + return 1; + if (with_domain) + rc = mu_address_aget_email (addr, 1, &s); + else + rc = mu_address_aget_local_part (addr, 1, &s); + mu_address_destroy (&addr); + if (rc || !s) + return 1; + *pret = s; + } + return 0; +} + +static int +cfun_mail_from (mu_stream_t iostr, char *arg) +{ + if (*arg == 0) + { + lmtp_reply (iostr, "501", "5.5.2", "Syntax error"); + return 1; + } + + if (check_address (arg, 1, &mail_from)) + { + lmtp_reply (iostr, "553", "5.1.8", "Address format error"); + return 1; + } + lmtp_reply (iostr, "250", "2.1.0", "Go ahead"); + return 0; +} + +static int +cfun_rcpt_to (mu_stream_t iostr, char *arg) +{ + char *user; + struct mu_auth_data *auth; + + if (*arg == 0) + { + lmtp_reply (iostr, "501", "5.5.2", "Syntax error"); + return 1; + } + + /* FIXME: Check if domain is OK */ + if (check_address (arg, 0, &user)) + { + lmtp_reply (iostr, "553", "5.1.8", "Address format error"); + return 1; + } + auth = mu_get_auth_by_name (user); + if (!auth) + { + lmtp_reply (iostr, "550", "5.1.1", "User unknown"); + free (user); + return 1; + } + mu_auth_data_free (auth); + if (!rcpt_list) + { + mu_list_create (&rcpt_list); + mu_list_set_destroy_item (rcpt_list, mu_list_free_item); + } + mu_list_append (rcpt_list, user); + lmtp_reply (iostr, "250", "2.1.5", "Go ahead"); + return 0; +} + +static int +dot_temp_fail (void *item, void *cbdata) +{ + char *name = item; + mu_stream_t iostr = cbdata; + lmtp_reply (iostr, "450", "4.1.0", "%s: temporary failure", name); + return 0; +} + +static int +dot_deliver (void *item, void *cbdata) +{ + char *name = item; + mu_stream_t iostr = cbdata; + char *errp = NULL; + + switch (mda_deliver_to_user (mesg, name, &errp)) + { + case 0: + lmtp_reply (iostr, "250", "2.0.0", "%s: delivered", name); + break; + + case EX_UNAVAILABLE: + if (errp) + lmtp_reply (iostr, "553", "5.1.8", "%s", errp); + else + lmtp_reply (iostr, "553", "5.1.8", "%s: delivery failed", name); + break; + + default: + if (errp) + lmtp_reply (iostr, "450", "4.1.0", "%s", errp); + else + lmtp_reply (iostr, "450", "4.1.0", + "%s: temporary failure, try again later", + name); + break; + } + free (errp); + return 0; +} + +static int +cfun_data (mu_stream_t iostr, char *arg) +{ + int rc; + mu_stream_t flt, tempstr; + time_t t; + struct tm *tm; + int xlev = MU_XSCRIPT_PAYLOAD, xlev_switch = 0; + + if (*arg) + { + lmtp_reply (iostr, "501", "5.5.2", "Syntax error"); + return 1; + } + + rc = mu_filter_create (&flt, iostr, "CRLFDOT", MU_FILTER_DECODE, + MU_STREAM_READ|MU_STREAM_WRTHRU); + if (rc) + { + mda_error (_("unable to open filter: %s"), + mu_strerror (rc)); + lmtp_reply (iostr, "450", "4.1.0", "Temporary failure, try again later"); + return 1; + } + + rc = mu_temp_file_stream_create (&tempstr, NULL, 0); + if (rc) + { + mda_error (_("unable to open temporary file: %s"), mu_strerror (rc)); + mu_stream_destroy (&flt); + return 1; + } + + /* Write out envelope */ + time (&t); + tm = gmtime (&t); + rc = mu_stream_printf (tempstr, "From %s ", mail_from); + if (rc == 0) + rc = mu_c_streamftime (tempstr, "%c%n", tm, NULL); + if (rc) + { + mda_error (_("copy error: %s"), mu_strerror (rc)); + mu_stream_destroy (&flt); + mu_stream_destroy (&tempstr); + mu_list_foreach (rcpt_list, dot_temp_fail, iostr); + } + + lmtp_reply (iostr, "354", NULL, "Go ahead"); + + if (mu_stream_ioctl (iostr, MU_IOCTL_XSCRIPTSTREAM, + MU_IOCTL_XSCRIPTSTREAM_LEVEL, &xlev) == 0) + xlev_switch = 1; + rc = mu_stream_copy (tempstr, flt, 0, NULL); + mu_stream_destroy (&flt); + if (xlev_switch) + mu_stream_ioctl (iostr, MU_IOCTL_XSCRIPTSTREAM, + MU_IOCTL_XSCRIPTSTREAM_LEVEL, &xlev); + if (rc) + { + mda_error (_("copy error: %s"), mu_strerror (rc)); + mu_list_foreach (rcpt_list, dot_temp_fail, iostr); + } + + rc = mu_stream_to_message (tempstr, &mesg); + mu_stream_unref (tempstr); + if (rc) + { + mda_error (_("error creating temporary message: %s"), + mu_strerror (rc)); + mu_list_foreach (rcpt_list, dot_temp_fail, iostr); + } + + rc = mu_list_foreach (rcpt_list, dot_deliver, iostr); + + mu_message_destroy (&mesg, mu_message_get_owner (mesg)); + if (rc) + mu_list_foreach (rcpt_list, dot_temp_fail, iostr); + + return 0; +} + +static int +cfun_rset (mu_stream_t iostr, char *arg) +{ + free (lhlo_domain); + free (mail_from); + mu_list_destroy (&rcpt_list); + mu_message_destroy (&mesg, mu_message_get_owner (mesg)); + lmtp_reply (iostr, "250", "2.0.0", "OK, forgotten"); + return 0; +} + +static char *capa_str = "ENHANCEDSTATUSCODES\n\ +PIPELINING\n\ +8BITMIME\n\ +HELP"; + +static int +cfun_lhlo (mu_stream_t iostr, char *arg) +{ + if (*arg == 0) + { + lmtp_reply (iostr, "501", "5.0.0", "Syntax error"); + return 1; + } + lhlo_domain = strdup (arg); + if (!lhlo_domain) + { + lmtp_reply (iostr, "410", "4.0.0", + "Local error; please try again later"); + return 1; + } + lmtp_reply (iostr, "250", NULL, "Hello\n"); + lmtp_reply (iostr, "250", NULL, capa_str); + return 0; +} + +static int +cfun_quit (mu_stream_t iostr, char *arg) +{ + lmtp_reply (iostr, "221", "2.0.0", "Bye"); + return 0; +} + +static int +cfun_help (mu_stream_t iostr, char *arg) +{ + lmtp_reply (iostr, "200", "2.0.0", "Man, help yourself"); + return 0; +} + +static struct command_tab +{ + char *cmd_verb; + int cmd_len; + enum lmtp_command cmd_code; + int (*cmd_fun) (mu_stream_t, char *); +} command_tab[] = { +#define S(s) #s, (sizeof #s - 1) + { S(lhlo), cmd_lhlo, cfun_lhlo }, + { S(mail from:), cmd_mail, cfun_mail_from }, + { S(rcpt to:), cmd_rcpt, cfun_rcpt_to }, + { S(data), cmd_data, cfun_data }, + { S(quit), cmd_quit, cfun_quit }, + { S(rset), cmd_rset, cfun_rset }, + { S(help), cmd_help, cfun_help }, + { NULL, 0, cmd_unknown, cfun_unknown } +}; + +static struct command_tab * +getcmd (char *buf, char **sp) +{ + struct command_tab *cp; + size_t len = strlen (buf); + for (cp = command_tab; cp->cmd_verb; cp++) + { + if (cp->cmd_len <= len + && mu_c_strncasecmp (cp->cmd_verb, buf, cp->cmd_len) == 0) + { + *sp = buf + cp->cmd_len; + return cp; + } + } + return cp; +} + +static int +to_fgets (mu_stream_t iostr, char **pbuf, size_t *psize, size_t *pnread, + unsigned int timeout) +{ + int rc; + + alarm (timeout); + rc = mu_stream_getline (iostr, pbuf, psize, pnread); + alarm (0); + return rc; +} + +static int +lmtp_loop (mu_stream_t iostr, unsigned int timeout) +{ + size_t size = 0, n; + char *buf = NULL; + enum lmtp_state state = state_init; + + lmtp_reply (iostr, "220", NULL, "At your service"); + while (to_fgets (iostr, &buf, &size, &n, timeout) == 0 && n) + { + char *sp; + struct command_tab *cp = getcmd (buf, &sp); + enum lmtp_command cmd = cp->cmd_code; + enum lmtp_state next_state = transtab[cmd][state]; + + mu_rtrim_class (sp, MU_CTYPE_ENDLN); + + if (next_state != state_none) + { + if (cp->cmd_fun) + { + sp = mu_str_skip_class (sp, MU_CTYPE_SPACE); + if (cp->cmd_fun (iostr, sp)) + continue; + } + state = next_state; + } + else + lmtp_reply (iostr, "503", "5.0.0", "Syntax error"); + + if (state == state_end) + break; + } + return 0; +} + +typedef union +{ + struct sockaddr sa; + struct sockaddr_in s_in; + struct sockaddr_un s_un; +} all_addr_t; + +static int +lmtp_connection (int fd, struct sockaddr *sa, int salen, + struct mu_srv_config *pconf, + void *data) +{ + mu_stream_t str; + int rc; + + rc = mu_fd_stream_create (&str, NULL, fd, MU_STREAM_RDWR); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_fd_stream_create", NULL, rc); + return rc; + } + mu_stream_set_buffer (str, mu_buffer_line, 0); + + if (pconf->transcript || mda_transcript) + str = lmtp_transcript (str); + lmtp_loop (str, pconf->timeout); + mu_stream_destroy (&str); + return 0; +} + +static int +lmtp_set_privs (void) +{ + gid_t gid; + + if (lmtp_groups) + { + gid_t *gidset = NULL; + size_t size = 0; + size_t j = 0; + mu_iterator_t itr; + int rc; + + rc = mu_list_count (lmtp_groups, &size); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_list_count", NULL, rc); + return EX_UNAVAILABLE; + } + if (size == 0) + return 0; /* nothing to do */ + gidset = calloc (size, sizeof (gidset[0])); + if (!gidset) + { + mu_error (_("not enough memory")); + return EX_UNAVAILABLE; + } + if (mu_list_get_iterator (lmtp_groups, &itr) == 0) + { + for (mu_iterator_first (itr); + !mu_iterator_is_done (itr); mu_iterator_next (itr)) + mu_iterator_current (itr, + (void **)(gidset + j++)); + mu_iterator_destroy (&itr); + } + gid = gidset[0]; + rc = setgroups (j, gidset); + free (gidset); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "setgroups", NULL, errno); + return EX_UNAVAILABLE; + } + } + else + { + struct group *gr = getgrnam ("mail"); + if (gr == NULL) + { + mu_diag_funcall (MU_DIAG_ERROR, "getgrnam", "mail", errno); + return EX_UNAVAILABLE; + } + gid = gr->gr_gid; + } + if (setgid (gid) == -1) + { + mu_diag_funcall (MU_DIAG_ERROR, "setgid", "mail", errno); + return EX_UNAVAILABLE; + } + return 0; +} + +static int +lmtp_server (void) +{ + int rc = lmtp_set_privs (); + + if (rc) + return rc; + + if (mu_m_server_mode (server) == MODE_DAEMON) + { + mu_m_server_begin (server); + rc = mu_m_server_run (server); + if (rc) + rc = EX_CONFIG; + mu_m_server_end (server); + mu_m_server_destroy (&server); + } + else + { + mu_stream_t str, istream, ostream; + + rc = mu_stdio_stream_create (&istream, MU_STDIN_FD, MU_STREAM_READ); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", + "MU_STDIN_FD", rc); + return EX_UNAVAILABLE; + } + + rc = mu_stdio_stream_create (&ostream, MU_STDOUT_FD, MU_STREAM_WRITE); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create", + "MU_STDOUT_FD", rc); + return 1; + } + + rc = mu_iostream_create (&str, istream, ostream); + mu_stream_unref (istream); + mu_stream_unref (ostream); + if (rc) + { + mu_diag_funcall (MU_DIAG_ERROR, "mu_iostream_create", NULL, rc); + return 1; + } + + if (mda_transcript) + str = lmtp_transcript (str); + + rc = lmtp_loop (str, 0); + mu_stream_destroy (&str); + } + return rc; +} + + |