root/usr/src/cmd/sendmail/src/usersmtp.c
/*
 * Copyright (c) 1998-2006, 2008, 2009 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#include <sendmail.h>

SM_RCSID("@(#)$Id: usersmtp.c,v 8.473 2009/06/17 17:26:51 ca Exp $")

#include <sysexits.h>


static void     esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static void     helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
static int      smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *));

#if SASL
extern void     *sm_sasl_malloc __P((unsigned long));
extern void     sm_sasl_free __P((void *));
#endif /* SASL */

/*
**  USERSMTP -- run SMTP protocol from the user end.
**
**      This protocol is described in RFC821.
*/

#define REPLYCLASS(r)   (((r) / 10) % 10)       /* second digit of reply code */
#define SMTPCLOSING     421                     /* "Service Shutting Down" */

#define ENHSCN(e, d)    ((e) == NULL ? (d) : (e))

#define ENHSCN_RPOOL(e, d, rpool) \
        ((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e))

static char     SmtpMsgBuffer[MAXLINE];         /* buffer for commands */
static char     SmtpReplyBuffer[MAXLINE];       /* buffer for replies */
static bool     SmtpNeedIntro;          /* need "while talking" in transcript */
/*
**  SMTPINIT -- initialize SMTP.
**
**      Opens the connection and sends the initial protocol.
**
**      Parameters:
**              m -- mailer to create connection to.
**              mci -- the mailer connection info.
**              e -- the envelope.
**              onlyhelo -- send only helo command?
**
**      Returns:
**              none.
**
**      Side Effects:
**              creates connection and sends initial protocol.
*/

void
smtpinit(m, mci, e, onlyhelo)
        MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
        bool onlyhelo;
{
        register int r;
        int state;
        register char *p;
        register char *hn;
        char *enhsc;

        enhsc = NULL;
        if (tTd(18, 1))
        {
                sm_dprintf("smtpinit ");
                mci_dump(sm_debug_file(), mci, false);
        }

        /*
        **  Open the connection to the mailer.
        */

        SmtpError[0] = '\0';
        SmtpMsgBuffer[0] = '\0';
        CurHostName = mci->mci_host;            /* XXX UGLY XXX */
        if (CurHostName == NULL)
                CurHostName = MyHostName;
        SmtpNeedIntro = true;
        state = mci->mci_state;
        switch (state)
        {
          case MCIS_MAIL:
          case MCIS_RCPT:
          case MCIS_DATA:
                /* need to clear old information */
                smtprset(m, mci, e);
                /* FALLTHROUGH */

          case MCIS_OPEN:
                if (!onlyhelo)
                        return;
                break;

          case MCIS_ERROR:
          case MCIS_QUITING:
          case MCIS_SSD:
                /* shouldn't happen */
                smtpquit(m, mci, e);
                /* FALLTHROUGH */

          case MCIS_CLOSED:
                syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state);
                return;

          case MCIS_OPENING:
                break;
        }
        if (onlyhelo)
                goto helo;

        mci->mci_state = MCIS_OPENING;
        clrsessenvelope(e);

        /*
        **  Get the greeting message.
        **      This should appear spontaneously.  Give it five minutes to
        **      happen.
        */

        SmtpPhase = mci->mci_phase = "client greeting";
        sm_setproctitle(true, e, "%s %s: %s",
                        qid_printname(e), CurHostName, mci->mci_phase);
        r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL,
                XS_DEFAULT);
        if (r < 0)
                goto tempfail1;
        if (REPLYTYPE(r) == 4)
                goto tempfail2;
        if (REPLYTYPE(r) != 2)
                goto unavailable;

        /*
        **  Send the HELO command.
        **      My mother taught me to always introduce myself.
        */

helo:
        if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
                mci->mci_flags |= MCIF_ESMTP;
        hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;

tryhelo:
#if _FFR_IGNORE_EXT_ON_HELO
        mci->mci_flags &= ~MCIF_HELO;
#endif /* _FFR_IGNORE_EXT_ON_HELO */
        if (bitnset(M_LMTP, m->m_flags))
        {
                smtpmessage("LHLO %s", m, mci, hn);
                SmtpPhase = mci->mci_phase = "client LHLO";
        }
        else if (bitset(MCIF_ESMTP, mci->mci_flags) &&
                 !bitnset(M_FSMTP, m->m_flags))
        {
                smtpmessage("EHLO %s", m, mci, hn);
                SmtpPhase = mci->mci_phase = "client EHLO";
        }
        else
        {
                smtpmessage("HELO %s", m, mci, hn);
                SmtpPhase = mci->mci_phase = "client HELO";
#if _FFR_IGNORE_EXT_ON_HELO
                mci->mci_flags |= MCIF_HELO;
#endif /* _FFR_IGNORE_EXT_ON_HELO */
        }
        sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
                        CurHostName, mci->mci_phase);
        r = reply(m, mci, e,
                  bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo
                                              : TimeOuts.to_helo,
                  helo_options, NULL, XS_DEFAULT);
        if (r < 0)
                goto tempfail1;
        else if (REPLYTYPE(r) == 5)
        {
                if (bitset(MCIF_ESMTP, mci->mci_flags) &&
                    !bitnset(M_LMTP, m->m_flags))
                {
                        /* try old SMTP instead */
                        mci->mci_flags &= ~MCIF_ESMTP;
                        goto tryhelo;
                }
                goto unavailable;
        }
        else if (REPLYTYPE(r) != 2)
                goto tempfail2;

        /*
        **  Check to see if we actually ended up talking to ourself.
        **  This means we didn't know about an alias or MX, or we managed
        **  to connect to an echo server.
        */

        p = strchr(&SmtpReplyBuffer[4], ' ');
        if (p != NULL)
                *p = '\0';
        if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
            !bitnset(M_LMTP, m->m_flags) &&
            sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
        {
                syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
                        CurHostName);
                mci_setstat(mci, EX_CONFIG, "5.3.5",
                            "553 5.3.5 system config error");
                mci->mci_errno = 0;
                smtpquit(m, mci, e);
                return;
        }

        /*
        **  If this is expected to be another sendmail, send some internal
        **  commands.
        **  If we're running as MSP, "propagate" -v flag if possible.
        */

        if ((UseMSP && Verbose && bitset(MCIF_VERB, mci->mci_flags))
# if !_FFR_DEPRECATE_MAILER_FLAG_I
            || bitnset(M_INTERNAL, m->m_flags)
# endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */
           )
        {
                /* tell it to be verbose */
                smtpmessage("VERB", m, mci);
                r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc,
                        XS_DEFAULT);
                if (r < 0)
                        goto tempfail1;
        }

        if (mci->mci_state != MCIS_CLOSED)
        {
                mci->mci_state = MCIS_OPEN;
                return;
        }

        /* got a 421 error code during startup */

  tempfail1:
        mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
        if (mci->mci_state != MCIS_CLOSED)
                smtpquit(m, mci, e);
        return;

  tempfail2:
        /* XXX should use code from other end iff ENHANCEDSTATUSCODES */
        mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
                    SmtpReplyBuffer);
        if (mci->mci_state != MCIS_CLOSED)
                smtpquit(m, mci, e);
        return;

  unavailable:
        mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
        smtpquit(m, mci, e);
        return;
}
/*
**  ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
**
**      Parameters:
**              line -- the response line.
**              firstline -- set if this is the first line of the reply.
**              m -- the mailer.
**              mci -- the mailer connection info.
**              e -- the envelope.
**
**      Returns:
**              none.
*/

static void
esmtp_check(line, firstline, m, mci, e)
        char *line;
        bool firstline;
        MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
{
        if (strstr(line, "ESMTP") != NULL)
                mci->mci_flags |= MCIF_ESMTP;

        /*
        **  Dirty hack below. Quoting the author:
        **  This was a response to people who wanted SMTP transmission to be
        **  just-send-8 by default.  Essentially, you could put this tag into
        **  your greeting message to behave as though the F=8 flag was set on
        **  the mailer.
        */

        if (strstr(line, "8BIT-OK") != NULL)
                mci->mci_flags |= MCIF_8BITOK;
}

#if SASL
/* specify prototype so compiler can check calls */
static char *str_union __P((char *, char *, SM_RPOOL_T *));

/*
**  STR_UNION -- create the union of two lists
**
**      Parameters:
**              s1, s2 -- lists of items (separated by single blanks).
**              rpool -- resource pool from which result is allocated.
**
**      Returns:
**              the union of both lists.
*/

static char *
str_union(s1, s2, rpool)
        char *s1, *s2;
        SM_RPOOL_T *rpool;
{
        char *hr, *h1, *h, *res;
        int l1, l2, rl;

        if (s1 == NULL || *s1 == '\0')
                return s2;
        if (s2 == NULL || *s2 == '\0')
                return s1;
        l1 = strlen(s1);
        l2 = strlen(s2);
        rl = l1 + l2;
        res = (char *) sm_rpool_malloc(rpool, rl + 2);
        if (res == NULL)
        {
                if (l1 > l2)
                        return s1;
                return s2;
        }
        (void) sm_strlcpy(res, s1, rl);
        hr = res + l1;
        h1 = s2;
        h = s2;

        /* walk through s2 */
        while (h != NULL && *h1 != '\0')
        {
                /* is there something after the current word? */
                if ((h = strchr(h1, ' ')) != NULL)
                        *h = '\0';
                l1 = strlen(h1);

                /* does the current word appear in s1 ? */
                if (iteminlist(h1, s1, " ") == NULL)
                {
                        /* add space as delimiter */
                        *hr++ = ' ';

                        /* copy the item */
                        memcpy(hr, h1, l1);

                        /* advance pointer in result list */
                        hr += l1;
                        *hr = '\0';
                }
                if (h != NULL)
                {
                        /* there are more items */
                        *h = ' ';
                        h1 = h + 1;
                }
        }
        return res;
}
#endif /* SASL */

/*
**  HELO_OPTIONS -- process the options on a HELO line.
**
**      Parameters:
**              line -- the response line.
**              firstline -- set if this is the first line of the reply.
**              m -- the mailer.
**              mci -- the mailer connection info.
**              e -- the envelope (unused).
**
**      Returns:
**              none.
*/

static void
helo_options(line, firstline, m, mci, e)
        char *line;
        bool firstline;
        MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
{
        register char *p;
#if _FFR_IGNORE_EXT_ON_HELO
        static bool logged = false;
#endif /* _FFR_IGNORE_EXT_ON_HELO */

        if (firstline)
        {
#if SASL
                mci->mci_saslcap = NULL;
#endif /* SASL */
#if _FFR_IGNORE_EXT_ON_HELO
                logged = false;
#endif /* _FFR_IGNORE_EXT_ON_HELO */
                return;
        }
#if _FFR_IGNORE_EXT_ON_HELO
        else if (bitset(MCIF_HELO, mci->mci_flags))
        {
                if (LogLevel > 8 && !logged)
                {
                        sm_syslog(LOG_WARNING, NOQID,
                                  "server=%s [%s] returned extensions despite HELO command",
                                  macvalue(macid("{server_name}"), e),
                                  macvalue(macid("{server_addr}"), e));
                        logged = true;
                }
                return;
        }
#endif /* _FFR_IGNORE_EXT_ON_HELO */

        if (strlen(line) < 5)
                return;
        line += 4;
        p = strpbrk(line, " =");
        if (p != NULL)
                *p++ = '\0';
        if (sm_strcasecmp(line, "size") == 0)
        {
                mci->mci_flags |= MCIF_SIZE;
                if (p != NULL)
                        mci->mci_maxsize = atol(p);
        }
        else if (sm_strcasecmp(line, "8bitmime") == 0)
        {
                mci->mci_flags |= MCIF_8BITMIME;
                mci->mci_flags &= ~MCIF_7BIT;
        }
        else if (sm_strcasecmp(line, "expn") == 0)
                mci->mci_flags |= MCIF_EXPN;
        else if (sm_strcasecmp(line, "dsn") == 0)
                mci->mci_flags |= MCIF_DSN;
        else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0)
                mci->mci_flags |= MCIF_ENHSTAT;
        else if (sm_strcasecmp(line, "pipelining") == 0)
                mci->mci_flags |= MCIF_PIPELINED;
        else if (sm_strcasecmp(line, "verb") == 0)
                mci->mci_flags |= MCIF_VERB;
#if STARTTLS
        else if (sm_strcasecmp(line, "starttls") == 0)
                mci->mci_flags |= MCIF_TLS;
#endif /* STARTTLS */
        else if (sm_strcasecmp(line, "deliverby") == 0)
        {
                mci->mci_flags |= MCIF_DLVR_BY;
                if (p != NULL)
                        mci->mci_min_by = atol(p);
        }
#if SASL
        else if (sm_strcasecmp(line, "auth") == 0)
        {
                if (p != NULL && *p != '\0')
                {
                        if (mci->mci_saslcap != NULL)
                        {
                                /*
                                **  Create the union with previous auth
                                **  offerings because we recognize "auth "
                                **  and "auth=" (old format).
                                */

                                mci->mci_saslcap = str_union(mci->mci_saslcap,
                                                             p, mci->mci_rpool);
                                mci->mci_flags |= MCIF_AUTH;
                        }
                        else
                        {
                                int l;

                                l = strlen(p) + 1;
                                mci->mci_saslcap = (char *)
                                        sm_rpool_malloc(mci->mci_rpool, l);
                                if (mci->mci_saslcap != NULL)
                                {
                                        (void) sm_strlcpy(mci->mci_saslcap, p,
                                                          l);
                                        mci->mci_flags |= MCIF_AUTH;
                                }
                        }
                }
        }
#endif /* SASL */
}
#if SASL

static int getsimple    __P((void *, int, const char **, unsigned *));
static int getsecret    __P((sasl_conn_t *, void *, int, sasl_secret_t **));
static int saslgetrealm __P((void *, int, const char **, const char **));
static int readauth     __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *));
static int getauth      __P((MCI *, ENVELOPE *, SASL_AI_T *));
static char *removemech __P((char *, char *, SM_RPOOL_T *));
static int attemptauth  __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *));

static sasl_callback_t callbacks[] =
{
        {       SASL_CB_GETREALM,       &saslgetrealm,  NULL    },
#define CB_GETREALM_IDX 0
        {       SASL_CB_PASS,           &getsecret,     NULL    },
#define CB_PASS_IDX     1
        {       SASL_CB_USER,           &getsimple,     NULL    },
#define CB_USER_IDX     2
        {       SASL_CB_AUTHNAME,       &getsimple,     NULL    },
#define CB_AUTHNAME_IDX 3
        {       SASL_CB_VERIFYFILE,     &safesaslfile,  NULL    },
#define CB_SAFESASL_IDX 4
        {       SASL_CB_LIST_END,       NULL,           NULL    }
};

/*
**  INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL
**
**      Parameters:
**              none.
**
**      Returns:
**              SASL_OK -- if successful.
**              SASL error code -- otherwise.
**
**      Side Effects:
**              checks/sets sasl_clt_init.
**
**      Note:
**      Callbacks are ignored if sasl_client_init() has
**      been called before (by a library such as libnss_ldap)
*/

static bool sasl_clt_init = false;

static int
init_sasl_client()
{
        int result;

        if (sasl_clt_init)
                return SASL_OK;
        result = sasl_client_init(callbacks);

        /* should we retry later again or just remember that it failed? */
        if (result == SASL_OK)
                sasl_clt_init = true;
        return result;
}
/*
**  STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL
**
**      Parameters:
**              none.
**
**      Returns:
**              none.
**
**      Side Effects:
**              checks/sets sasl_clt_init.
*/

void
stop_sasl_client()
{
        if (!sasl_clt_init)
                return;
        sasl_clt_init = false;
        sasl_done();
}
/*
**  GETSASLDATA -- process the challenges from the SASL protocol
**
**      This gets the relevant sasl response data out of the reply
**      from the server.
**
**      Parameters:
**              line -- the response line.
**              firstline -- set if this is the first line of the reply.
**              m -- the mailer.
**              mci -- the mailer connection info.
**              e -- the envelope (unused).
**
**      Returns:
**              none.
*/

static void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *));

static void
getsasldata(line, firstline, m, mci, e)
        char *line;
        bool firstline;
        MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
{
        int len;
        int result;
# if SASL < 20000
        char *out;
# endif /* SASL < 20000 */

        /* if not a continue we don't care about it */
        len = strlen(line);
        if ((len <= 4) ||
            (line[0] != '3') ||
             !isascii(line[1]) || !isdigit(line[1]) ||
             !isascii(line[2]) || !isdigit(line[2]))
        {
                SM_FREE_CLR(mci->mci_sasl_string);
                return;
        }

        /* forget about "334 " */
        line += 4;
        len -= 4;
# if SASL >= 20000
        /* XXX put this into a macro/function? It's duplicated below */
        if (mci->mci_sasl_string != NULL)
        {
                if (mci->mci_sasl_string_len <= len)
                {
                        sm_free(mci->mci_sasl_string); /* XXX */
                        mci->mci_sasl_string = xalloc(len + 1);
                }
        }
        else
                mci->mci_sasl_string = xalloc(len + 1);

        result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1,
                               (unsigned int *) &mci->mci_sasl_string_len);
        if (result != SASL_OK)
        {
                mci->mci_sasl_string_len = 0;
                *mci->mci_sasl_string = '\0';
        }
# else /* SASL >= 20000 */
        out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1);
        result = sasl_decode64(line, len, out, (unsigned int *) &len);
        if (result != SASL_OK)
        {
                len = 0;
                *out = '\0';
        }

        /*
        **  mci_sasl_string is "shared" with Cyrus-SASL library; hence
        **      it can't be in an rpool unless we use the same memory
        **      management mechanism (with same rpool!) for Cyrus SASL.
        */

        if (mci->mci_sasl_string != NULL)
        {
                if (mci->mci_sasl_string_len <= len)
                {
                        sm_free(mci->mci_sasl_string); /* XXX */
                        mci->mci_sasl_string = xalloc(len + 1);
                }
        }
        else
                mci->mci_sasl_string = xalloc(len + 1);

        memcpy(mci->mci_sasl_string, out, len);
        mci->mci_sasl_string[len] = '\0';
        mci->mci_sasl_string_len = len;
# endif /* SASL >= 20000 */
        return;
}
/*
**  READAUTH -- read auth values from a file
**
**      Parameters:
**              filename -- name of file to read.
**              safe -- if set, this is a safe read.
**              sai -- where to store auth_info.
**              rpool -- resource pool for sai.
**
**      Returns:
**              EX_OK -- data succesfully read.
**              EX_UNAVAILABLE -- no valid filename.
**              EX_TEMPFAIL -- temporary failure.
*/

static char *sasl_info_name[] =
{
        "user id",
        "authentication id",
        "password",
        "realm",
        "mechlist"
};
static int
readauth(filename, safe, sai, rpool)
        char *filename;
        bool safe;
        SASL_AI_T *sai;
        SM_RPOOL_T *rpool;
{
        SM_FILE_T *f;
        long sff;
        pid_t pid;
        int lc;
        char *s;
        char buf[MAXLINE];

        if (filename == NULL || filename[0] == '\0')
                return EX_UNAVAILABLE;

#if !_FFR_ALLOW_SASLINFO
        /*
        **  make sure we don't use a program that is not
        **  accesible to the user who specified a different authinfo file.
        **  However, currently we don't pass this info (authinfo file
        **  specified by user) around, so we just turn off program access.
        */

        if (filename[0] == '|')
        {
                auto int fd;
                int i;
                char *p;
                char *argv[MAXPV + 1];

                i = 0;
                for (p = strtok(&filename[1], " \t"); p != NULL;
                     p = strtok(NULL, " \t"))
                {
                        if (i >= MAXPV)
                                break;
                        argv[i++] = p;
                }
                argv[i] = NULL;
                pid = prog_open(argv, &fd, CurEnv);
                if (pid < 0)
                        f = NULL;
                else
                        f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
                                       (void *) &fd, SM_IO_RDONLY, NULL);
        }
        else
#endif /* !_FFR_ALLOW_SASLINFO */
        {
                pid = -1;
                sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK
                      |SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES;
# if _FFR_GROUPREADABLEAUTHINFOFILE
                if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail))
# endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
                        sff |= SFF_NOGRFILES;
                if (DontLockReadFiles)
                        sff |= SFF_NOLOCK;

#if _FFR_ALLOW_SASLINFO
                /*
                **  XXX: make sure we don't read or open files that are not
                **  accesible to the user who specified a different authinfo
                **  file.
                */

                sff |= SFF_MUSTOWN;
#else /* _FFR_ALLOW_SASLINFO */
                if (safe)
                        sff |= SFF_OPENASROOT;
#endif /* _FFR_ALLOW_SASLINFO */

                f = safefopen(filename, O_RDONLY, 0, sff);
        }
        if (f == NULL)
        {
                if (LogLevel > 5)
                        sm_syslog(LOG_ERR, NOQID,
                                  "AUTH=client, error: can't open %s: %s",
                                  filename, sm_errstring(errno));
                return EX_TEMPFAIL;
        }

        lc = 0;
        while (lc <= SASL_MECHLIST &&
                sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
        {
                if (buf[0] != '#')
                {
                        (*sai)[lc] = sm_rpool_strdup_x(rpool, buf);
                        if ((s = strchr((*sai)[lc], '\n')) != NULL)
                                *s = '\0';
                        lc++;
                }
        }

        (void) sm_io_close(f, SM_TIME_DEFAULT);
        if (pid > 0)
                (void) waitfor(pid);
        if (lc < SASL_PASSWORD)
        {
                if (LogLevel > 8)
                        sm_syslog(LOG_ERR, NOQID,
                                  "AUTH=client, error: can't read %s from %s",
                                  sasl_info_name[lc + 1], filename);
                return EX_TEMPFAIL;
        }
        return EX_OK;
}

/*
**  GETAUTH -- get authinfo from ruleset call
**
**      {server_name}, {server_addr} must be set
**
**      Parameters:
**              mci -- the mailer connection structure.
**              e -- the envelope (including the sender to specify).
**              sai -- pointer to authinfo (result).
**
**      Returns:
**              EX_OK -- ruleset was succesfully called, data may not
**                      be available, sai must be checked.
**              EX_UNAVAILABLE -- ruleset unavailable (or failed).
**              EX_TEMPFAIL -- temporary failure (from ruleset).
**
**      Side Effects:
**              Fills in sai if successful.
*/

static int
getauth(mci, e, sai)
        MCI *mci;
        ENVELOPE *e;
        SASL_AI_T *sai;
{
        int i, r, l, got, ret;
        char **pvp;
        char pvpbuf[PSBUFSIZE];

        r = rscap("authinfo", macvalue(macid("{server_name}"), e),
                   macvalue(macid("{server_addr}"), e), e,
                   &pvp, pvpbuf, sizeof(pvpbuf));

        if (r != EX_OK)
                return EX_UNAVAILABLE;

        /* other than expected return value: ok (i.e., no auth) */
        if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
                return EX_OK;
        if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
                return EX_TEMPFAIL;

        /*
        **  parse the data, put it into sai
        **  format: "TDstring" (including the '"' !)
        **  where T is a tag: 'U', ...
        **  D is a delimiter: ':' or '='
        */

        ret = EX_OK;    /* default return value */
        i = 0;
        got = 0;
        while (i < SASL_ENTRIES)
        {
                if (pvp[i + 1] == NULL)
                        break;
                if (pvp[i + 1][0] != '"')
                        break;
                switch (pvp[i + 1][1])
                {
                  case 'U':
                  case 'u':
                        r = SASL_USER;
                        break;
                  case 'I':
                  case 'i':
                        r = SASL_AUTHID;
                        break;
                  case 'P':
                  case 'p':
                        r = SASL_PASSWORD;
                        break;
                  case 'R':
                  case 'r':
                        r = SASL_DEFREALM;
                        break;
                  case 'M':
                  case 'm':
                        r = SASL_MECHLIST;
                        break;
                  default:
                        goto fail;
                }
                l = strlen(pvp[i + 1]);

                /* check syntax */
                if (l <= 3 || pvp[i + 1][l - 1] != '"')
                        goto fail;

                /* remove closing quote */
                pvp[i + 1][l - 1] = '\0';

                /* remove "TD and " */
                l -= 4;
                (*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1);
                if ((*sai)[r] == NULL)
                        goto tempfail;
                if (pvp[i + 1][2] == ':')
                {
                        /* ':text' (just copy) */
                        (void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1);
                        got |= 1 << r;
                }
                else if (pvp[i + 1][2] == '=')
                {
                        unsigned int len;

                        /* '=base64' (decode) */
# if SASL >= 20000
                        ret = sasl_decode64(pvp[i + 1] + 3,
                                          (unsigned int) l, (*sai)[r],
                                          (unsigned int) l + 1, &len);
# else /* SASL >= 20000 */
                        ret = sasl_decode64(pvp[i + 1] + 3,
                                          (unsigned int) l, (*sai)[r], &len);
# endif /* SASL >= 20000 */
                        if (ret != SASL_OK)
                                goto fail;
                        got |= 1 << r;
                }
                else
                        goto fail;
                if (tTd(95, 5))
                        sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
                                  sasl_info_name[r], (*sai)[r]);
                ++i;
        }

        /* did we get the expected data? */
        /* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */
        if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) &&
              bitset(SASL_PASSWORD_BIT, got)))
                goto fail;

        /* no authid? copy uid */
        if (!bitset(SASL_AUTHID_BIT, got))
        {
                l = strlen((*sai)[SASL_USER]) + 1;
                (*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool,
                                                               l + 1);
                if ((*sai)[SASL_AUTHID] == NULL)
                        goto tempfail;
                (void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l);
        }

        /* no uid? copy authid */
        if (!bitset(SASL_USER_BIT, got))
        {
                l = strlen((*sai)[SASL_AUTHID]) + 1;
                (*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool,
                                                             l + 1);
                if ((*sai)[SASL_USER] == NULL)
                        goto tempfail;
                (void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l);
        }
        return EX_OK;

  tempfail:
        ret = EX_TEMPFAIL;
  fail:
        if (LogLevel > 8)
                sm_syslog(LOG_WARNING, NOQID,
                          "AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
                          macvalue(macid("{server_name}"), e),
                          macvalue(macid("{server_addr}"), e),
                          ret == EX_TEMPFAIL ? "temp" : "");
        for (i = 0; i <= SASL_MECHLIST; i++)
                (*sai)[i] = NULL;       /* just clear; rpool */
        return ret;
}

# if SASL >= 20000
/*
**  GETSIMPLE -- callback to get userid or authid
**
**      Parameters:
**              context -- sai
**              id -- what to do
**              result -- (pointer to) result
**              len -- (pointer to) length of result
**
**      Returns:
**              OK/failure values
*/

static int
getsimple(context, id, result, len)
        void *context;
        int id;
        const char **result;
        unsigned *len;
{
        SASL_AI_T *sai;

        if (result == NULL || context == NULL)
                return SASL_BADPARAM;
        sai = (SASL_AI_T *) context;

        switch (id)
        {
          case SASL_CB_USER:
                *result = (*sai)[SASL_USER];
                if (tTd(95, 5))
                        sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
                                  *result);
                if (len != NULL)
                        *len = *result != NULL ? strlen(*result) : 0;
                break;

          case SASL_CB_AUTHNAME:
                *result = (*sai)[SASL_AUTHID];
                if (tTd(95, 5))
                        sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
                                  *result);
                if (len != NULL)
                        *len = *result != NULL ? strlen(*result) : 0;
                break;

          case SASL_CB_LANGUAGE:
                *result = NULL;
                if (len != NULL)
                        *len = 0;
                break;

          default:
                return SASL_BADPARAM;
        }
        return SASL_OK;
}
/*
**  GETSECRET -- callback to get password
**
**      Parameters:
**              conn -- connection information
**              context -- sai
**              id -- what to do
**              psecret -- (pointer to) result
**
**      Returns:
**              OK/failure values
*/

static int
getsecret(conn, context, id, psecret)
        sasl_conn_t *conn;
        SM_UNUSED(void *context);
        int id;
        sasl_secret_t **psecret;
{
        int len;
        char *authpass;
        MCI *mci;

        if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
                return SASL_BADPARAM;

        mci = (MCI *) context;
        authpass = mci->mci_sai[SASL_PASSWORD];
        len = strlen(authpass);

        /*
        **  use an rpool because we are responsible for free()ing the secret,
        **  but we can't free() it until after the auth completes
        */

        *psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool,
                                                     sizeof(sasl_secret_t) +
                                                     len + 1);
        if (*psecret == NULL)
                return SASL_FAIL;
        (void) sm_strlcpy((char *) (*psecret)->data, authpass, len + 1);
        (*psecret)->len = (unsigned long) len;
        return SASL_OK;
}
# else /* SASL >= 20000 */
/*
**  GETSIMPLE -- callback to get userid or authid
**
**      Parameters:
**              context -- sai
**              id -- what to do
**              result -- (pointer to) result
**              len -- (pointer to) length of result
**
**      Returns:
**              OK/failure values
*/

static int
getsimple(context, id, result, len)
        void *context;
        int id;
        const char **result;
        unsigned *len;
{
        char *h, *s;
# if SASL > 10509
        bool addrealm;
# endif /* SASL > 10509 */
        size_t l;
        SASL_AI_T *sai;
        char *authid = NULL;

        if (result == NULL || context == NULL)
                return SASL_BADPARAM;
        sai = (SASL_AI_T *) context;

        /*
        **  Unfortunately it is not clear whether this routine should
        **  return a copy of a string or just a pointer to a string.
        **  The Cyrus-SASL plugins treat these return values differently, e.g.,
        **  plugins/cram.c free()s authid, plugings/digestmd5.c does not.
        **  The best solution to this problem is to fix Cyrus-SASL, but it
        **  seems there is nobody who creates patches... Hello CMU!?
        **  The second best solution is to have flags that tell this routine
        **  whether to return an malloc()ed copy.
        **  The next best solution is to always return an malloc()ed copy,
        **  and suffer from some memory leak, which is ugly for persistent
        **  queue runners.
        **  For now we go with the last solution...
        **  We can't use rpools (which would avoid this particular problem)
        **  as explained in sasl.c.
        */

        switch (id)
        {
          case SASL_CB_USER:
                l = strlen((*sai)[SASL_USER]) + 1;
                s = sm_sasl_malloc(l);
                if (s == NULL)
                {
                        if (len != NULL)
                                *len = 0;
                        *result = NULL;
                        return SASL_NOMEM;
                }
                (void) sm_strlcpy(s, (*sai)[SASL_USER], l);
                *result = s;
                if (tTd(95, 5))
                        sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
                                  *result);
                if (len != NULL)
                        *len = *result != NULL ? strlen(*result) : 0;
                break;

          case SASL_CB_AUTHNAME:
                h = (*sai)[SASL_AUTHID];
# if SASL > 10509
                /* XXX maybe other mechanisms too?! */
                addrealm = (*sai)[SASL_MECH] != NULL &&
                           sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0;

                /*
                **  Add realm to authentication id unless authid contains
                **  '@' (i.e., a realm) or the default realm is empty.
                */

                if (addrealm && h != NULL && strchr(h, '@') == NULL)
                {
                        /* has this been done before? */
                        if ((*sai)[SASL_ID_REALM] == NULL)
                        {
                                char *realm;

                                realm = (*sai)[SASL_DEFREALM];

                                /* do not add an empty realm */
                                if (*realm == '\0')
                                {
                                        authid = h;
                                        (*sai)[SASL_ID_REALM] = NULL;
                                }
                                else
                                {
                                        l = strlen(h) + strlen(realm) + 2;

                                        /* should use rpool, but from where? */
                                        authid = sm_sasl_malloc(l);
                                        if (authid != NULL)
                                        {
                                                (void) sm_snprintf(authid, l,
                                                                  "%s@%s",
                                                                   h, realm);
                                                (*sai)[SASL_ID_REALM] = authid;
                                        }
                                        else
                                        {
                                                authid = h;
                                                (*sai)[SASL_ID_REALM] = NULL;
                                        }
                                }
                        }
                        else
                                authid = (*sai)[SASL_ID_REALM];
                }
                else
# endif /* SASL > 10509 */
                        authid = h;
                l = strlen(authid) + 1;
                s = sm_sasl_malloc(l);
                if (s == NULL)
                {
                        if (len != NULL)
                                *len = 0;
                        *result = NULL;
                        return SASL_NOMEM;
                }
                (void) sm_strlcpy(s, authid, l);
                *result = s;
                if (tTd(95, 5))
                        sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
                                  *result);
                if (len != NULL)
                        *len = authid ? strlen(authid) : 0;
                break;

          case SASL_CB_LANGUAGE:
                *result = NULL;
                if (len != NULL)
                        *len = 0;
                break;

          default:
                return SASL_BADPARAM;
        }
        return SASL_OK;
}
/*
**  GETSECRET -- callback to get password
**
**      Parameters:
**              conn -- connection information
**              context -- sai
**              id -- what to do
**              psecret -- (pointer to) result
**
**      Returns:
**              OK/failure values
*/

static int
getsecret(conn, context, id, psecret)
        sasl_conn_t *conn;
        SM_UNUSED(void *context);
        int id;
        sasl_secret_t **psecret;
{
        int len;
        char *authpass;
        SASL_AI_T *sai;

        if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
                return SASL_BADPARAM;

        sai = (SASL_AI_T *) context;
        authpass = (*sai)[SASL_PASSWORD];
        len = strlen(authpass);
        *psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) +
                                                    len + 1);
        if (*psecret == NULL)
                return SASL_FAIL;
        (void) sm_strlcpy((*psecret)->data, authpass, len + 1);
        (*psecret)->len = (unsigned long) len;
        return SASL_OK;
}
# endif /* SASL >= 20000 */

/*
**  SAFESASLFILE -- callback for sasl: is file safe?
**
**      Parameters:
**              context -- pointer to context between invocations (unused)
**              file -- name of file to check
**              type -- type of file to check
**
**      Returns:
**              SASL_OK -- file can be used
**              SASL_CONTINUE -- don't use file
**              SASL_FAIL -- failure (not used here)
**
*/

int
#if SASL > 10515
safesaslfile(context, file, type)
#else /* SASL > 10515 */
safesaslfile(context, file)
#endif /* SASL > 10515 */
        void *context;
# if SASL >= 20000
        const char *file;
# else /* SASL >= 20000 */
        char *file;
# endif /* SASL >= 20000 */
#if SASL > 10515
# if SASL >= 20000
        sasl_verify_type_t type;
# else /* SASL >= 20000 */
        int type;
# endif /* SASL >= 20000 */
#endif /* SASL > 10515 */
{
        long sff;
        int r;
#if SASL <= 10515
        size_t len;
#endif /* SASL <= 10515 */
        char *p;

        if (file == NULL || *file == '\0')
                return SASL_OK;
        sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK;
#if SASL <= 10515
        if ((p = strrchr(file, '/')) == NULL)
                p = file;
        else
                ++p;

        /* everything beside libs and .conf files must not be readable */
        len = strlen(p);
        if ((len <= 3 || strncmp(p, "lib", 3) != 0) &&
            (len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0))
        {
                if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
                        sff |= SFF_NORFILES;
                if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
                        sff |= SFF_NOGWFILES;
        }
#else /* SASL <= 10515 */
        /* files containing passwords should be not readable */
        if (type == SASL_VRFY_PASSWD)
        {
                if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
                        sff |= SFF_NOWRFILES;
                else
                        sff |= SFF_NORFILES;
                if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
                        sff |= SFF_NOGWFILES;
        }
#endif /* SASL <= 10515 */

        p = (char *) file;
        if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff,
                          S_IRUSR, NULL)) == 0)
                return SASL_OK;
        if (LogLevel > (r != ENOENT ? 8 : 10))
                sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
                          p, sm_errstring(r));
        return SASL_CONTINUE;
}

/*
**  SASLGETREALM -- return the realm for SASL
**
**      return the realm for the client
**
**      Parameters:
**              context -- context shared between invocations
**              availrealms -- list of available realms
**                      {realm, realm, ...}
**              result -- pointer to result
**
**      Returns:
**              failure/success
*/

static int
saslgetrealm(context, id, availrealms, result)
        void *context;
        int id;
        const char **availrealms;
        const char **result;
{
        char *r;
        SASL_AI_T *sai;

        sai = (SASL_AI_T *) context;
        if (sai == NULL)
                return SASL_FAIL;
        r = (*sai)[SASL_DEFREALM];

        if (LogLevel > 12)
                sm_syslog(LOG_INFO, NOQID,
                          "AUTH=client, realm=%s, available realms=%s",
                          r == NULL ? "<No Realm>" : r,
                          (availrealms == NULL || *availrealms == NULL)
                                ? "<No Realms>" : *availrealms);

        /* check whether context is in list */
        if (availrealms != NULL && *availrealms != NULL)
        {
                if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
                    NULL)
                {
                        if (LogLevel > 8)
                                sm_syslog(LOG_ERR, NOQID,
                                          "AUTH=client, realm=%s not in list=%s",
                                          r, *availrealms);
                        return SASL_FAIL;
                }
        }
        *result = r;
        return SASL_OK;
}
/*
**  ITEMINLIST -- does item appear in list?
**
**      Check whether item appears in list (which must be separated by a
**      character in delim) as a "word", i.e. it must appear at the begin
**      of the list or after a space, and it must end with a space or the
**      end of the list.
**
**      Parameters:
**              item -- item to search.
**              list -- list of items.
**              delim -- list of delimiters.
**
**      Returns:
**              pointer to occurrence (NULL if not found).
*/

char *
iteminlist(item, list, delim)
        char *item;
        char *list;
        char *delim;
{
        char *s;
        int len;

        if (list == NULL || *list == '\0')
                return NULL;
        if (item == NULL || *item == '\0')
                return NULL;
        s = list;
        len = strlen(item);
        while (s != NULL && *s != '\0')
        {
                if (sm_strncasecmp(s, item, len) == 0 &&
                    (s[len] == '\0' || strchr(delim, s[len]) != NULL))
                        return s;
                s = strpbrk(s, delim);
                if (s != NULL)
                        while (*++s == ' ')
                                continue;
        }
        return NULL;
}
/*
**  REMOVEMECH -- remove item [rem] from list [list]
**
**      Parameters:
**              rem -- item to remove
**              list -- list of items
**              rpool -- resource pool from which result is allocated.
**
**      Returns:
**              pointer to new list (NULL in case of error).
*/

static char *
removemech(rem, list, rpool)
        char *rem;
        char *list;
        SM_RPOOL_T *rpool;
{
        char *ret;
        char *needle;
        int len;

        if (list == NULL)
                return NULL;
        if (rem == NULL || *rem == '\0')
        {
                /* take out what? */
                return NULL;
        }

        /* find the item in the list */
        if ((needle = iteminlist(rem, list, " ")) == NULL)
        {
                /* not in there: return original */
                return list;
        }

        /* length of string without rem */
        len = strlen(list) - strlen(rem);
        if (len <= 0)
        {
                ret = (char *) sm_rpool_malloc_x(rpool, 1);
                *ret = '\0';
                return ret;
        }
        ret = (char *) sm_rpool_malloc_x(rpool, len);
        memset(ret, '\0', len);

        /* copy from start to removed item */
        memcpy(ret, list, needle - list);

        /* length of rest of string past removed item */
        len = strlen(needle) - strlen(rem) - 1;
        if (len > 0)
        {
                /* not last item -- copy into string */
                memcpy(ret + (needle - list),
                       list + (needle - list) + strlen(rem) + 1,
                       len);
        }
        else
                ret[(needle - list) - 1] = '\0';
        return ret;
}
/*
**  ATTEMPTAUTH -- try to AUTHenticate using one mechanism
**
**      Parameters:
**              m -- the mailer.
**              mci -- the mailer connection structure.
**              e -- the envelope (including the sender to specify).
**              sai - sasl authinfo
**
**      Returns:
**              EX_OK -- authentication was successful.
**              EX_NOPERM -- authentication failed.
**              EX_IOERR -- authentication dialogue failed (I/O problem?).
**              EX_TEMPFAIL -- temporary failure.
**
*/

static int
attemptauth(m, mci, e, sai)
        MAILER *m;
        MCI *mci;
        ENVELOPE *e;
        SASL_AI_T *sai;
{
        int saslresult, smtpresult;
# if SASL >= 20000
        sasl_ssf_t ssf;
        const char *auth_id;
        const char *out;
# else /* SASL >= 20000 */
        sasl_external_properties_t ssf;
        char *out;
# endif /* SASL >= 20000 */
        unsigned int outlen;
        sasl_interact_t *client_interact = NULL;
        char *mechusing;
        sasl_security_properties_t ssp;

        /* MUST NOT be a multiple of 4: bug in some sasl_encode64() versions */
        char in64[MAXOUTLEN + 1];
#if NETINET || (NETINET6 && SASL >= 20000)
        extern SOCKADDR CurHostAddr;
#endif /* NETINET || (NETINET6 && SASL >= 20000) */

        /* no mechanism selected (yet) */
        (*sai)[SASL_MECH] = NULL;

        /* dispose old connection */
        if (mci->mci_conn != NULL)
                sasl_dispose(&(mci->mci_conn));

        /* make a new client sasl connection */
# if SASL >= 20000
        /*
        **  We provide the callbacks again because global callbacks in
        **  sasl_client_init() are ignored if SASL has been initialized
        **  before, for example, by a library such as libnss-ldap.
        */

        saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
                                                                 : "smtp",
                                     CurHostName, NULL, NULL, callbacks, 0,
                                     &mci->mci_conn);
# else /* SASL >= 20000 */
        saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
                                                                 : "smtp",
                                     CurHostName, NULL, 0, &mci->mci_conn);
# endif /* SASL >= 20000 */
        if (saslresult != SASL_OK)
                return EX_TEMPFAIL;

        /* set properties */
        (void) memset(&ssp, '\0', sizeof(ssp));

        /* XXX should these be options settable via .cf ? */
        {
                ssp.max_ssf = MaxSLBits;
                ssp.maxbufsize = MAXOUTLEN;
#  if 0
                ssp.security_flags = SASL_SEC_NOPLAINTEXT;
#  endif /* 0 */
        }
        saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
        if (saslresult != SASL_OK)
                return EX_TEMPFAIL;

# if SASL >= 20000
        /* external security strength factor, authentication id */
        ssf = 0;
        auth_id = NULL;
#  if STARTTLS
        out = macvalue(macid("{cert_subject}"), e);
        if (out != NULL && *out != '\0')
                auth_id = out;
        out = macvalue(macid("{cipher_bits}"), e);
        if (out != NULL && *out != '\0')
                ssf = atoi(out);
#  endif /* STARTTLS */
        saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
        if (saslresult != SASL_OK)
                return EX_TEMPFAIL;
        saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id);
        if (saslresult != SASL_OK)
                return EX_TEMPFAIL;

#  if NETINET || NETINET6
        /* set local/remote ipv4 addresses */
        if (mci->mci_out != NULL && (
#   if NETINET6
                CurHostAddr.sa.sa_family == AF_INET6 ||
#   endif /* NETINET6 */
                CurHostAddr.sa.sa_family == AF_INET))
        {
                SOCKADDR_LEN_T addrsize;
                SOCKADDR saddr_l;
                char localip[60], remoteip[60];

                switch (CurHostAddr.sa.sa_family)
                {
                  case AF_INET:
                        addrsize = sizeof(struct sockaddr_in);
                        break;
#   if NETINET6
                  case AF_INET6:
                        addrsize = sizeof(struct sockaddr_in6);
                        break;
#   endif /* NETINET6 */
                  default:
                        break;
                }
                if (iptostring(&CurHostAddr, addrsize,
                               remoteip, sizeof(remoteip)))
                {
                        if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT,
                                         remoteip) != SASL_OK)
                                return EX_TEMPFAIL;
                }
                addrsize = sizeof(saddr_l);
                if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
                                              NULL),
                                (struct sockaddr *) &saddr_l, &addrsize) == 0)
                {
                        if (iptostring(&saddr_l, addrsize,
                                       localip, sizeof(localip)))
                        {
                                if (sasl_setprop(mci->mci_conn,
                                                 SASL_IPLOCALPORT,
                                                 localip) != SASL_OK)
                                        return EX_TEMPFAIL;
                        }
                }
        }
#  endif /* NETINET || NETINET6 */

        /* start client side of sasl */
        saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
                                       &client_interact,
                                       &out, &outlen,
                                       (const char **) &mechusing);
# else /* SASL >= 20000 */
        /* external security strength factor, authentication id */
        ssf.ssf = 0;
        ssf.auth_id = NULL;
#  if STARTTLS
        out = macvalue(macid("{cert_subject}"), e);
        if (out != NULL && *out != '\0')
                ssf.auth_id = out;
        out = macvalue(macid("{cipher_bits}"), e);
        if (out != NULL && *out != '\0')
                ssf.ssf = atoi(out);
#  endif /* STARTTLS */
        saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
        if (saslresult != SASL_OK)
                return EX_TEMPFAIL;

#  if NETINET
        /* set local/remote ipv4 addresses */
        if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
        {
                SOCKADDR_LEN_T addrsize;
                struct sockaddr_in saddr_l;

                if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
                                 (struct sockaddr_in *) &CurHostAddr)
                    != SASL_OK)
                        return EX_TEMPFAIL;
                addrsize = sizeof(struct sockaddr_in);
                if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
                                              NULL),
                                (struct sockaddr *) &saddr_l, &addrsize) == 0)
                {
                        if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
                                         &saddr_l) != SASL_OK)
                                return EX_TEMPFAIL;
                }
        }
#  endif /* NETINET */

        /* start client side of sasl */
        saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
                                       NULL, &client_interact,
                                       &out, &outlen,
                                       (const char **) &mechusing);
# endif /* SASL >= 20000 */

        if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
        {
                if (saslresult == SASL_NOMECH && LogLevel > 8)
                {
                        sm_syslog(LOG_NOTICE, e->e_id,
                                  "AUTH=client, available mechanisms do not fulfill requirements");
                }
                return EX_TEMPFAIL;
        }

        /* just point current mechanism to the data in the sasl library */
        (*sai)[SASL_MECH] = mechusing;

        /* send the info across the wire */
        if (out == NULL
                /* login and digest-md5 up to 1.5.28 set out="" */
            || (outlen == 0 &&
                (sm_strcasecmp(mechusing, "LOGIN") == 0 ||
                 sm_strcasecmp(mechusing, "DIGEST-MD5") == 0))
           )
        {
                /* no initial response */
                smtpmessage("AUTH %s", m, mci, mechusing);
        }
        else if (outlen == 0)
        {
                /*
                **  zero-length initial response, per RFC 2554 4.:
                **  "Unlike a zero-length client answer to a 334 reply, a zero-
                **  length initial response is sent as a single equals sign"
                */

                smtpmessage("AUTH %s =", m, mci, mechusing);
        }
        else
        {
                saslresult = sasl_encode64(out, outlen, in64, sizeof(in64),
                                           NULL);
                if (saslresult != SASL_OK) /* internal error */
                {
                        if (LogLevel > 8)
                                sm_syslog(LOG_ERR, e->e_id,
                                        "encode64 for AUTH failed");
                        return EX_TEMPFAIL;
                }
                smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
        }
# if SASL < 20000
        sm_sasl_free(out); /* XXX only if no rpool is used */
# endif /* SASL < 20000 */

        /* get the reply */
        smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL,
                        XS_AUTH);

        for (;;)
        {
                /* check return code from server */
                if (smtpresult == 235)
                {
                        macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"),
                                  mechusing);
                        return EX_OK;
                }
                if (smtpresult == -1)
                        return EX_IOERR;
                if (REPLYTYPE(smtpresult) == 5)
                        return EX_NOPERM;       /* ugly, but ... */
                if (REPLYTYPE(smtpresult) != 3)
                {
                        /* should we fail deliberately, see RFC 2554 4. ? */
                        /* smtpmessage("*", m, mci); */
                        return EX_TEMPFAIL;
                }

                saslresult = sasl_client_step(mci->mci_conn,
                                              mci->mci_sasl_string,
                                              mci->mci_sasl_string_len,
                                              &client_interact,
                                              &out, &outlen);

                if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
                {
                        if (tTd(95, 5))
                                sm_dprintf("AUTH FAIL=%s (%d)\n",
                                        sasl_errstring(saslresult, NULL, NULL),
                                        saslresult);

                        /* fail deliberately, see RFC 2554 4. */
                        smtpmessage("*", m, mci);

                        /*
                        **  but we should only fail for this authentication
                        **  mechanism; how to do that?
                        */

                        smtpresult = reply(m, mci, e, TimeOuts.to_auth,
                                           getsasldata, NULL, XS_AUTH);
                        return EX_NOPERM;
                }

                if (outlen > 0)
                {
                        saslresult = sasl_encode64(out, outlen, in64,
                                                   sizeof(in64), NULL);
                        if (saslresult != SASL_OK)
                        {
                                /* give an error reply to the other side! */
                                smtpmessage("*", m, mci);
                                return EX_TEMPFAIL;
                        }
                }
                else
                        in64[0] = '\0';
# if SASL < 20000
                sm_sasl_free(out); /* XXX only if no rpool is used */
# endif /* SASL < 20000 */
                smtpmessage("%s", m, mci, in64);
                smtpresult = reply(m, mci, e, TimeOuts.to_auth,
                                   getsasldata, NULL, XS_AUTH);
        }
        /* NOTREACHED */
}
/*
**  SMTPAUTH -- try to AUTHenticate
**
**      This will try mechanisms in the order the sasl library decided until:
**      - there are no more mechanisms
**      - a mechanism succeeds
**      - the sasl library fails initializing
**
**      Parameters:
**              m -- the mailer.
**              mci -- the mailer connection info.
**              e -- the envelope.
**
**      Returns:
**              EX_OK -- authentication was successful
**              EX_UNAVAILABLE -- authentication not possible, e.g.,
**                      no data available.
**              EX_NOPERM -- authentication failed.
**              EX_TEMPFAIL -- temporary failure.
**
**      Notice: AuthInfo is used for all connections, hence we must
**              return EX_TEMPFAIL only if we really want to retry, i.e.,
**              iff getauth() tempfailed or getauth() was used and
**              authentication tempfailed.
*/

int
smtpauth(m, mci, e)
        MAILER *m;
        MCI *mci;
        ENVELOPE *e;
{
        int result;
        int i;
        bool usedgetauth;

        mci->mci_sasl_auth = false;
        for (i = 0; i < SASL_MECH ; i++)
                mci->mci_sai[i] = NULL;

        result = getauth(mci, e, &(mci->mci_sai));
        if (result == EX_TEMPFAIL)
                return result;
        usedgetauth = true;

        /* no data available: don't try to authenticate */
        if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL)
                return result;
        if (result != EX_OK)
        {
                if (SASLInfo == NULL)
                        return EX_UNAVAILABLE;

                /* read authinfo from file */
                result = readauth(SASLInfo, true, &(mci->mci_sai),
                                  mci->mci_rpool);
                if (result != EX_OK)
                        return result;
                usedgetauth = false;
        }

        /* check whether sufficient data is available */
        if (mci->mci_sai[SASL_PASSWORD] == NULL ||
            *(mci->mci_sai)[SASL_PASSWORD] == '\0')
                return EX_UNAVAILABLE;
        if ((mci->mci_sai[SASL_AUTHID] == NULL ||
             *(mci->mci_sai)[SASL_AUTHID] == '\0') &&
            (mci->mci_sai[SASL_USER] == NULL ||
             *(mci->mci_sai)[SASL_USER] == '\0'))
                return EX_UNAVAILABLE;

        /* set the context for the callback function to sai */
# if SASL >= 20000
        callbacks[CB_PASS_IDX].context = (void *) mci;
# else /* SASL >= 20000 */
        callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai;
# endif /* SASL >= 20000 */
        callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai;
        callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai;
        callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai;
#if 0
        callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai;
#endif /* 0 */

        /* set default value for realm */
        if ((mci->mci_sai)[SASL_DEFREALM] == NULL)
                (mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool,
                                                        macvalue('j', CurEnv));

        /* set default value for list of mechanism to use */
        if ((mci->mci_sai)[SASL_MECHLIST] == NULL ||
            *(mci->mci_sai)[SASL_MECHLIST] == '\0')
                (mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms;

        /* create list of mechanisms to try */
        mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST],
                                     mci->mci_saslcap, mci->mci_rpool);

        /* initialize sasl client library */
        result = init_sasl_client();
        if (result != SASL_OK)
                return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE;
        do
        {
                result = attemptauth(m, mci, e, &(mci->mci_sai));
                if (result == EX_OK)
                        mci->mci_sasl_auth = true;
                else if (result == EX_TEMPFAIL || result == EX_NOPERM)
                {
                        mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH],
                                                      mci->mci_saslcap,
                                                      mci->mci_rpool);
                        if (mci->mci_saslcap == NULL ||
                            *(mci->mci_saslcap) == '\0')
                                return usedgetauth ? result
                                                   : EX_UNAVAILABLE;
                }
                else
                        return result;
        } while (result != EX_OK);
        return result;
}
#endif /* SASL */

/*
**  SMTPMAILFROM -- send MAIL command
**
**      Parameters:
**              m -- the mailer.
**              mci -- the mailer connection structure.
**              e -- the envelope (including the sender to specify).
*/

int
smtpmailfrom(m, mci, e)
        MAILER *m;
        MCI *mci;
        ENVELOPE *e;
{
        int r;
        char *bufp;
        char *bodytype;
        char *enhsc;
        char buf[MAXNAME + 1];
        char optbuf[MAXLINE];

        if (tTd(18, 2))
                sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
        enhsc = NULL;

        /*
        **  Check if connection is gone, if so
        **  it's a tempfail and we use mci_errno
        **  for the reason.
        */

        if (mci->mci_state == MCIS_CLOSED)
        {
                errno = mci->mci_errno;
                return EX_TEMPFAIL;
        }

        /* set up appropriate options to include */
        if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
        {
                (void) sm_snprintf(optbuf, sizeof(optbuf), " SIZE=%ld",
                        e->e_msgsize);
                bufp = &optbuf[strlen(optbuf)];
        }
        else
        {
                optbuf[0] = '\0';
                bufp = optbuf;
        }

        bodytype = e->e_bodytype;
        if (bitset(MCIF_8BITMIME, mci->mci_flags))
        {
                if (bodytype == NULL &&
                    bitset(MM_MIME8BIT, MimeMode) &&
                    bitset(EF_HAS8BIT, e->e_flags) &&
                    !bitset(EF_DONT_MIME, e->e_flags) &&
                    !bitnset(M_8BITS, m->m_flags))
                        bodytype = "8BITMIME";
                if (bodytype != NULL &&
                    SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
                {
                        (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                                 " BODY=%s", bodytype);
                        bufp += strlen(bufp);
                }
        }
        else if (bitnset(M_8BITS, m->m_flags) ||
                 !bitset(EF_HAS8BIT, e->e_flags) ||
                 bitset(MCIF_8BITOK, mci->mci_flags))
        {
                /* EMPTY */
                /* just pass it through */
        }
#if MIME8TO7
        else if (bitset(MM_CVTMIME, MimeMode) &&
                 !bitset(EF_DONT_MIME, e->e_flags) &&
                 (!bitset(MM_PASS8BIT, MimeMode) ||
                  bitset(EF_IS_MIME, e->e_flags)))
        {
                /* must convert from 8bit MIME format to 7bit encoded */
                mci->mci_flags |= MCIF_CVT8TO7;
        }
#endif /* MIME8TO7 */
        else if (!bitset(MM_PASS8BIT, MimeMode))
        {
                /* cannot just send a 8-bit version */
                extern char MsgBuf[];

                usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
                mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
                return EX_DATAERR;
        }

        if (bitset(MCIF_DSN, mci->mci_flags))
        {
                if (e->e_envid != NULL &&
                    SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
                {
                        (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                                 " ENVID=%s", e->e_envid);
                        bufp += strlen(bufp);
                }

                /* RET= parameter */
                if (bitset(EF_RET_PARAM, e->e_flags) &&
                    SPACELEFT(optbuf, bufp) > 9)
                {
                        (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                                 " RET=%s",
                                 bitset(EF_NO_BODY_RETN, e->e_flags) ?
                                        "HDRS" : "FULL");
                        bufp += strlen(bufp);
                }
        }

        if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
            SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
#if SASL
             && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
#endif /* SASL */
            )
        {
                (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                         " AUTH=%s", e->e_auth_param);
                bufp += strlen(bufp);
        }

        /*
        **  17 is the max length required, we could use log() to compute
        **  the exact length (and check IS_DLVR_TRACE())
        */

        if (bitset(MCIF_DLVR_BY, mci->mci_flags) &&
            IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17)
        {
                long dby;

                /*
                **  Avoid problems with delays (for R) since the check
                **  in deliver() whether min-deliver-time is sufficient.
                **  Alternatively we could pass the computed time to this
                **  function.
                */

                dby = e->e_deliver_by - (curtime() - e->e_ctime);
                if (dby <= 0 && IS_DLVR_RETURN(e))
                        dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by;
                (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                        " BY=%ld;%c%s",
                        dby,
                        IS_DLVR_RETURN(e) ? 'R' : 'N',
                        IS_DLVR_TRACE(e) ? "T" : "");
                bufp += strlen(bufp);
        }

        /*
        **  Send the MAIL command.
        **      Designates the sender.
        */

        mci->mci_state = MCIS_MAIL;

        if (bitset(EF_RESPONSE, e->e_flags) &&
            !bitnset(M_NO_NULL_FROM, m->m_flags))
                buf[0] = '\0';
        else
                expand("\201g", buf, sizeof(buf), e);
        if (buf[0] == '<')
        {
                /* strip off <angle brackets> (put back on below) */
                bufp = &buf[strlen(buf) - 1];
                if (*bufp == '>')
                        *bufp = '\0';
                bufp = &buf[1];
        }
        else
                bufp = buf;
        if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
            !bitnset(M_FROMPATH, m->m_flags))
        {
                smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
        }
        else
        {
                smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
                            *bufp == '@' ? ',' : ':', bufp, optbuf);
        }
        SmtpPhase = mci->mci_phase = "client MAIL";
        sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
                        CurHostName, mci->mci_phase);
        r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc, XS_DEFAULT);
        if (r < 0)
        {
                /* communications failure */
                mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
                return EX_TEMPFAIL;
        }
        else if (r == SMTPCLOSING)
        {
                /* service shutting down: handled by reply() */
                return EX_TEMPFAIL;
        }
        else if (REPLYTYPE(r) == 4)
        {
                mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
                            SmtpReplyBuffer);
                return EX_TEMPFAIL;
        }
        else if (REPLYTYPE(r) == 2)
        {
                return EX_OK;
        }
        else if (r == 501)
        {
                /* syntax error in arguments */
                mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
                            SmtpReplyBuffer);
                return EX_DATAERR;
        }
        else if (r == 553)
        {
                /* mailbox name not allowed */
                mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
                            SmtpReplyBuffer);
                return EX_DATAERR;
        }
        else if (r == 552)
        {
                /* exceeded storage allocation */
                mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
                            SmtpReplyBuffer);
                if (bitset(MCIF_SIZE, mci->mci_flags))
                        e->e_flags |= EF_NO_BODY_RETN;
                return EX_UNAVAILABLE;
        }
        else if (REPLYTYPE(r) == 5)
        {
                /* unknown error */
                mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
                            SmtpReplyBuffer);
                return EX_UNAVAILABLE;
        }

        if (LogLevel > 1)
        {
                sm_syslog(LOG_CRIT, e->e_id,
                          "%.100s: SMTP MAIL protocol error: %s",
                          CurHostName,
                          shortenstring(SmtpReplyBuffer, 403));
        }

        /* protocol error -- close up */
        mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
                    SmtpReplyBuffer);
        smtpquit(m, mci, e);
        return EX_PROTOCOL;
}
/*
**  SMTPRCPT -- designate recipient.
**
**      Parameters:
**              to -- address of recipient.
**              m -- the mailer we are sending to.
**              mci -- the connection info for this transaction.
**              e -- the envelope for this transaction.
**
**      Returns:
**              exit status corresponding to recipient status.
**
**      Side Effects:
**              Sends the mail via SMTP.
*/

int
smtprcpt(to, m, mci, e, ctladdr, xstart)
        ADDRESS *to;
        register MAILER *m;
        MCI *mci;
        ENVELOPE *e;
        ADDRESS *ctladdr;
        time_t xstart;
{
        char *bufp;
        char optbuf[MAXLINE];

#if PIPELINING
        /*
        **  If there is status waiting from the other end, read it.
        **  This should normally happen because of SMTP pipelining.
        */

        while (mci->mci_nextaddr != NULL &&
               sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
        {
                int r;

                r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
                if (r != EX_OK)
                {
                        markfailure(e, mci->mci_nextaddr, mci, r, false);
                        giveresponse(r, mci->mci_nextaddr->q_status,  m, mci,
                                     ctladdr, xstart, e, to);
                }
                mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
        }
#endif /* PIPELINING */

        /*
        **  Check if connection is gone, if so
        **  it's a tempfail and we use mci_errno
        **  for the reason.
        */

        if (mci->mci_state == MCIS_CLOSED)
        {
                errno = mci->mci_errno;
                return EX_TEMPFAIL;
        }

        optbuf[0] = '\0';
        bufp = optbuf;

        /*
        **  Warning: in the following it is assumed that the free space
        **  in bufp is sizeof(optbuf)
        */

        if (bitset(MCIF_DSN, mci->mci_flags))
        {
                if (IS_DLVR_NOTIFY(e) &&
                    !bitset(MCIF_DLVR_BY, mci->mci_flags))
                {
                        /* RFC 2852: 4.1.4.2 */
                        if (!bitset(QHASNOTIFY, to->q_flags))
                                to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY;
                        else if (bitset(QPINGONSUCCESS, to->q_flags) ||
                                 bitset(QPINGONFAILURE, to->q_flags) ||
                                 bitset(QPINGONDELAY, to->q_flags))
                                to->q_flags |= QPINGONDELAY;
                }

                /* NOTIFY= parameter */
                if (bitset(QHASNOTIFY, to->q_flags) &&
                    bitset(QPRIMARY, to->q_flags) &&
                    !bitnset(M_LOCALMAILER, m->m_flags))
                {
                        bool firstone = true;

                        (void) sm_strlcat(bufp, " NOTIFY=", sizeof(optbuf));
                        if (bitset(QPINGONSUCCESS, to->q_flags))
                        {
                                (void) sm_strlcat(bufp, "SUCCESS", sizeof(optbuf));
                                firstone = false;
                        }
                        if (bitset(QPINGONFAILURE, to->q_flags))
                        {
                                if (!firstone)
                                        (void) sm_strlcat(bufp, ",",
                                                       sizeof(optbuf));
                                (void) sm_strlcat(bufp, "FAILURE", sizeof(optbuf));
                                firstone = false;
                        }
                        if (bitset(QPINGONDELAY, to->q_flags))
                        {
                                if (!firstone)
                                        (void) sm_strlcat(bufp, ",",
                                                       sizeof(optbuf));
                                (void) sm_strlcat(bufp, "DELAY", sizeof(optbuf));
                                firstone = false;
                        }
                        if (firstone)
                                (void) sm_strlcat(bufp, "NEVER", sizeof(optbuf));
                        bufp += strlen(bufp);
                }

                /* ORCPT= parameter */
                if (to->q_orcpt != NULL &&
                    SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
                {
                        (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
                                 " ORCPT=%s", to->q_orcpt);
                        bufp += strlen(bufp);
                }
        }

        smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
        mci->mci_state = MCIS_RCPT;

        SmtpPhase = mci->mci_phase = "client RCPT";
        sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
                        CurHostName, mci->mci_phase);

#if PIPELINING
        /*
        **  If running SMTP pipelining, we will pick up status later
        */

        if (bitset(MCIF_PIPELINED, mci->mci_flags))
                return EX_OK;
#endif /* PIPELINING */

        return smtprcptstat(to, m, mci, e);
}
/*
**  SMTPRCPTSTAT -- get recipient status
**
**      This is only called during SMTP pipelining
**
**      Parameters:
**              to -- address of recipient.
**              m -- mailer being sent to.
**              mci -- the mailer connection information.
**              e -- the envelope for this message.
**
**      Returns:
**              EX_* -- protocol status
*/

static int
smtprcptstat(to, m, mci, e)
        ADDRESS *to;
        MAILER *m;
        register MCI *mci;
        register ENVELOPE *e;
{
        int r;
        int save_errno;
        char *enhsc;

        /*
        **  Check if connection is gone, if so
        **  it's a tempfail and we use mci_errno
        **  for the reason.
        */

        if (mci->mci_state == MCIS_CLOSED)
        {
                errno = mci->mci_errno;
                return EX_TEMPFAIL;
        }

        enhsc = NULL;
        r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc, XS_DEFAULT);
        save_errno = errno;
        to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer);
        to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool);
        if (!bitnset(M_LMTP, m->m_flags))
                to->q_statmta = mci->mci_host;
        if (r < 0 || REPLYTYPE(r) == 4)
        {
                mci->mci_retryrcpt = true;
                errno = save_errno;
                return EX_TEMPFAIL;
        }
        else if (REPLYTYPE(r) == 2)
        {
                char *t;

                if ((t = mci->mci_tolist) != NULL)
                {
                        char *p;

                        *t++ = ',';
                        for (p = to->q_paddr; *p != '\0'; *t++ = *p++)
                                continue;
                        *t = '\0';
                        mci->mci_tolist = t;
                }
#if PIPELINING
                mci->mci_okrcpts++;
#endif /* PIPELINING */
                return EX_OK;
        }
        else if (r == 550)
        {
                to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool);
                return EX_NOUSER;
        }
        else if (r == 551)
        {
                to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool);
                return EX_NOUSER;
        }
        else if (r == 553)
        {
                to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool);
                return EX_NOUSER;
        }
        else if (REPLYTYPE(r) == 5)
        {
                return EX_UNAVAILABLE;
        }

        if (LogLevel > 1)
        {
                sm_syslog(LOG_CRIT, e->e_id,
                          "%.100s: SMTP RCPT protocol error: %s",
                          CurHostName,
                          shortenstring(SmtpReplyBuffer, 403));
        }

        mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
                    SmtpReplyBuffer);
        return EX_PROTOCOL;
}
/*
**  SMTPDATA -- send the data and clean up the transaction.
**
**      Parameters:
**              m -- mailer being sent to.
**              mci -- the mailer connection information.
**              e -- the envelope for this message.
**
**      Returns:
**              exit status corresponding to DATA command.
*/

int
smtpdata(m, mci, e, ctladdr, xstart)
        MAILER *m;
        register MCI *mci;
        register ENVELOPE *e;
        ADDRESS *ctladdr;
        time_t xstart;
{
        register int r;
        int rstat;
        int xstat;
        int timeout;
        char *enhsc;

        /*
        **  Check if connection is gone, if so
        **  it's a tempfail and we use mci_errno
        **  for the reason.
        */

        if (mci->mci_state == MCIS_CLOSED)
        {
                errno = mci->mci_errno;
                return EX_TEMPFAIL;
        }

        enhsc = NULL;

        /*
        **  Send the data.
        **      First send the command and check that it is ok.
        **      Then send the data (if there are valid recipients).
        **      Follow it up with a dot to terminate.
        **      Finally get the results of the transaction.
        */

        /* send the command and check ok to proceed */
        smtpmessage("DATA", m, mci);

#if PIPELINING
        if (mci->mci_nextaddr != NULL)
        {
                char *oldto = e->e_to;

                /* pick up any pending RCPT responses for SMTP pipelining */
                while (mci->mci_nextaddr != NULL)
                {
                        int r;

                        e->e_to = mci->mci_nextaddr->q_paddr;
                        r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
                        if (r != EX_OK)
                        {
                                markfailure(e, mci->mci_nextaddr, mci, r,
                                            false);
                                giveresponse(r, mci->mci_nextaddr->q_status, m,
                                             mci, ctladdr, xstart, e,
                                             mci->mci_nextaddr);
                                if (r == EX_TEMPFAIL)
                                        mci->mci_nextaddr->q_state = QS_RETRY;
                        }
                        mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
                }
                e->e_to = oldto;

                /*
                **  Connection might be closed in response to a RCPT command,
                **  i.e., the server responded with 421. In that case (at
                **  least) one RCPT has a temporary failure, hence we don't
                **  need to check mci_okrcpts (as it is done below) to figure
                **  out which error to return.
                */

                if (mci->mci_state == MCIS_CLOSED)
                {
                        errno = mci->mci_errno;
                        return EX_TEMPFAIL;
                }
        }
#endif /* PIPELINING */

        /* now proceed with DATA phase */
        SmtpPhase = mci->mci_phase = "client DATA 354";
        mci->mci_state = MCIS_DATA;
        sm_setproctitle(true, e, "%s %s: %s",
                        qid_printname(e), CurHostName, mci->mci_phase);
        r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc, XS_DEFAULT);
        if (r < 0 || REPLYTYPE(r) == 4)
        {
                if (r >= 0)
                        smtpquit(m, mci, e);
                errno = mci->mci_errno;
                return EX_TEMPFAIL;
        }
        else if (REPLYTYPE(r) == 5)
        {
                smtprset(m, mci, e);
#if PIPELINING
                if (mci->mci_okrcpts <= 0)
                        return mci->mci_retryrcpt ? EX_TEMPFAIL
                                                  : EX_UNAVAILABLE;
#endif /* PIPELINING */
                return EX_UNAVAILABLE;
        }
        else if (REPLYTYPE(r) != 3)
        {
                if (LogLevel > 1)
                {
                        sm_syslog(LOG_CRIT, e->e_id,
                                  "%.100s: SMTP DATA-1 protocol error: %s",
                                  CurHostName,
                                  shortenstring(SmtpReplyBuffer, 403));
                }
                smtprset(m, mci, e);
                mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
                            SmtpReplyBuffer);
#if PIPELINING
                if (mci->mci_okrcpts <= 0)
                        return mci->mci_retryrcpt ? EX_TEMPFAIL
                                                  : EX_PROTOCOL;
#endif /* PIPELINING */
                return EX_PROTOCOL;
        }

#if PIPELINING
        if (mci->mci_okrcpts > 0)
        {
#endif /* PIPELINING */

        /*
        **  Set timeout around data writes.  Make it at least large
        **  enough for DNS timeouts on all recipients plus some fudge
        **  factor.  The main thing is that it should not be infinite.
        */

        if (tTd(18, 101))
        {
                /* simulate a DATA timeout */
                timeout = 10;
        }
        else
                timeout = DATA_PROGRESS_TIMEOUT * 1000;
        sm_io_setinfo(mci->mci_out, SM_IO_WHAT_TIMEOUT, &timeout);


        /*
        **  Output the actual message.
        */

        if (!(*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER))
                goto writeerr;

        if (tTd(18, 101))
        {
                /* simulate a DATA timeout */
                (void) sleep(2);
        }

        if (!(*e->e_putbody)(mci, e, NULL))
                goto writeerr;

        /*
        **  Cleanup after sending message.
        */


#if PIPELINING
        }
#endif /* PIPELINING */

#if _FFR_CATCH_BROKEN_MTAS
        if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
        {
                /* terminate the message */
                (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s",
                                     m->m_eol);
                if (TrafficLogFile != NULL)
                        (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
                                             "%05d >>> .\n", (int) CurrentPid);
                if (Verbose)
                        nmessage(">>> .");

                sm_syslog(LOG_CRIT, e->e_id,
                          "%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
                          CurHostName);
                mci->mci_errno = EIO;
                mci->mci_state = MCIS_ERROR;
                mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
                smtpquit(m, mci, e);
                return EX_PROTOCOL;
        }
#endif /* _FFR_CATCH_BROKEN_MTAS */

        if (sm_io_error(mci->mci_out))
        {
                /* error during processing -- don't send the dot */
                mci->mci_errno = EIO;
                mci->mci_state = MCIS_ERROR;
                mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
                smtpquit(m, mci, e);
                return EX_IOERR;
        }

        /* terminate the message */
        if (sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s.%s",
                        bitset(MCIF_INLONGLINE, mci->mci_flags) ? m->m_eol : "",
                        m->m_eol) == SM_IO_EOF)
                goto writeerr;
        if (TrafficLogFile != NULL)
                (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
                                     "%05d >>> .\n", (int) CurrentPid);
        if (Verbose)
                nmessage(">>> .");

        /* check for the results of the transaction */
        SmtpPhase = mci->mci_phase = "client DATA status";
        sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
                        CurHostName, mci->mci_phase);
        if (bitnset(M_LMTP, m->m_flags))
                return EX_OK;
        r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DEFAULT);
        if (r < 0)
                return EX_TEMPFAIL;
        if (mci->mci_state == MCIS_DATA)
                mci->mci_state = MCIS_OPEN;
        xstat = EX_NOTSTICKY;
        if (r == 452)
                rstat = EX_TEMPFAIL;
        else if (REPLYTYPE(r) == 4)
                rstat = xstat = EX_TEMPFAIL;
        else if (REPLYTYPE(r) == 2)
                rstat = xstat = EX_OK;
        else if (REPLYCLASS(r) != 5)
                rstat = xstat = EX_PROTOCOL;
        else if (REPLYTYPE(r) == 5)
                rstat = EX_UNAVAILABLE;
        else
                rstat = EX_PROTOCOL;
        mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
                    SmtpReplyBuffer);
        if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
            (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
                r += 5;
        else
                r = 4;
        e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
        SmtpPhase = mci->mci_phase = "idle";
        sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase);
        if (rstat != EX_PROTOCOL)
                return rstat;
        if (LogLevel > 1)
        {
                sm_syslog(LOG_CRIT, e->e_id,
                          "%.100s: SMTP DATA-2 protocol error: %s",
                          CurHostName,
                          shortenstring(SmtpReplyBuffer, 403));
        }
        return rstat;

  writeerr:
        mci->mci_errno = errno;
        mci->mci_state = MCIS_ERROR;
        mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);

        /*
        **  If putbody() couldn't finish due to a timeout,
        **  rewind it here in the timeout handler.  See
        **  comments at the end of putbody() for reasoning.
        */

        if (e->e_dfp != NULL)
                (void) bfrewind(e->e_dfp);

        errno = mci->mci_errno;
        syserr("451 4.4.1 timeout writing message to %s", CurHostName);
        smtpquit(m, mci, e);
        return EX_TEMPFAIL;
}

/*
**  SMTPGETSTAT -- get status code from DATA in LMTP
**
**      Parameters:
**              m -- the mailer to which we are sending the message.
**              mci -- the mailer connection structure.
**              e -- the current envelope.
**
**      Returns:
**              The exit status corresponding to the reply code.
*/

int
smtpgetstat(m, mci, e)
        MAILER *m;
        MCI *mci;
        ENVELOPE *e;
{
        int r;
        int off;
        int status, xstat;
        char *enhsc;

        enhsc = NULL;

        /* check for the results of the transaction */
        r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DEFAULT);
        if (r < 0)
                return EX_TEMPFAIL;
        xstat = EX_NOTSTICKY;
        if (REPLYTYPE(r) == 4)
                status = EX_TEMPFAIL;
        else if (REPLYTYPE(r) == 2)
                status = xstat = EX_OK;
        else if (REPLYCLASS(r) != 5)
                status = xstat = EX_PROTOCOL;
        else if (REPLYTYPE(r) == 5)
                status = EX_UNAVAILABLE;
        else
                status = EX_PROTOCOL;
        if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
            (off = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
                off += 5;
        else
                off = 4;
        e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[off]);
        mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)), SmtpReplyBuffer);
        if (LogLevel > 1 && status == EX_PROTOCOL)
        {
                sm_syslog(LOG_CRIT, e->e_id,
                          "%.100s: SMTP DATA-3 protocol error: %s",
                          CurHostName,
                          shortenstring(SmtpReplyBuffer, 403));
        }
        return status;
}
/*
**  SMTPQUIT -- close the SMTP connection.
**
**      Parameters:
**              m -- a pointer to the mailer.
**              mci -- the mailer connection information.
**              e -- the current envelope.
**
**      Returns:
**              none.
**
**      Side Effects:
**              sends the final protocol and closes the connection.
*/

void
smtpquit(m, mci, e)
        register MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
{
        bool oldSuprErrs = SuprErrs;
        int rcode;
        char *oldcurhost;

        if (mci->mci_state == MCIS_CLOSED)
        {
                mci_close(mci, "smtpquit:1");
                return;
        }

        oldcurhost = CurHostName;
        CurHostName = mci->mci_host;            /* XXX UGLY XXX */
        if (CurHostName == NULL)
                CurHostName = MyHostName;

#if PIPELINING
        mci->mci_okrcpts = 0;
#endif /* PIPELINING */

        /*
        **      Suppress errors here -- we may be processing a different
        **      job when we do the quit connection, and we don't want the
        **      new job to be penalized for something that isn't it's
        **      problem.
        */

        SuprErrs = true;

        /* send the quit message if we haven't gotten I/O error */
        if (mci->mci_state != MCIS_ERROR &&
            mci->mci_state != MCIS_QUITING)
        {
                SmtpPhase = "client QUIT";
                mci->mci_state = MCIS_QUITING;
                smtpmessage("QUIT", m, mci);
                (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL,
                                XS_DEFAULT);
                SuprErrs = oldSuprErrs;
                if (mci->mci_state == MCIS_CLOSED)
                        goto end;
        }

        /* now actually close the connection and pick up the zombie */
        rcode = endmailer(mci, e, NULL);
        if (rcode != EX_OK)
        {
                char *mailer = NULL;

                if (mci->mci_mailer != NULL &&
                    mci->mci_mailer->m_name != NULL)
                        mailer = mci->mci_mailer->m_name;

                /* look for naughty mailers */
                sm_syslog(LOG_ERR, e->e_id,
                          "smtpquit: mailer%s%s exited with exit value %d",
                          mailer == NULL ? "" : " ",
                          mailer == NULL ? "" : mailer,
                          rcode);
        }

        SuprErrs = oldSuprErrs;

  end:
        CurHostName = oldcurhost;
        return;
}
/*
**  SMTPRSET -- send a RSET (reset) command
**
**      Parameters:
**              m -- a pointer to the mailer.
**              mci -- the mailer connection information.
**              e -- the current envelope.
**
**      Returns:
**              none.
**
**      Side Effects:
**              closes the connection if there is no reply to RSET.
*/

void
smtprset(m, mci, e)
        register MAILER *m;
        register MCI *mci;
        ENVELOPE *e;
{
        int r;

        CurHostName = mci->mci_host;            /* XXX UGLY XXX */
        if (CurHostName == NULL)
                CurHostName = MyHostName;

#if PIPELINING
        mci->mci_okrcpts = 0;
#endif /* PIPELINING */

        /*
        **  Check if connection is gone, if so
        **  it's a tempfail and we use mci_errno
        **  for the reason.
        */

        if (mci->mci_state == MCIS_CLOSED)
        {
                errno = mci->mci_errno;
                return;
        }

        SmtpPhase = "client RSET";
        smtpmessage("RSET", m, mci);
        r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL, XS_DEFAULT);
        if (r < 0)
                return;

        /*
        **  Any response is deemed to be acceptable.
        **  The standard does not state the proper action
        **  to take when a value other than 250 is received.
        **
        **  However, if 421 is returned for the RSET, leave
        **  mci_state alone (MCIS_SSD can be set in reply()
        **  and MCIS_CLOSED can be set in smtpquit() if
        **  reply() gets a 421 and calls smtpquit()).
        */

        if (mci->mci_state != MCIS_SSD && mci->mci_state != MCIS_CLOSED)
                mci->mci_state = MCIS_OPEN;
        else if (mci->mci_exitstat == EX_OK)
                mci_setstat(mci, EX_TEMPFAIL, "4.5.0", NULL);
}
/*
**  SMTPPROBE -- check the connection state
**
**      Parameters:
**              mci -- the mailer connection information.
**
**      Returns:
**              none.
**
**      Side Effects:
**              closes the connection if there is no reply to RSET.
*/

int
smtpprobe(mci)
        register MCI *mci;
{
        int r;
        MAILER *m = mci->mci_mailer;
        ENVELOPE *e;
        extern ENVELOPE BlankEnvelope;

        CurHostName = mci->mci_host;            /* XXX UGLY XXX */
        if (CurHostName == NULL)
                CurHostName = MyHostName;

        e = &BlankEnvelope;
        SmtpPhase = "client probe";
        smtpmessage("RSET", m, mci);
        r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL, XS_DEFAULT);
        if (REPLYTYPE(r) != 2)
                smtpquit(m, mci, e);
        return r;
}
/*
**  REPLY -- read arpanet reply
**
**      Parameters:
**              m -- the mailer we are reading the reply from.
**              mci -- the mailer connection info structure.
**              e -- the current envelope.
**              timeout -- the timeout for reads.
**              pfunc -- processing function called on each line of response.
**                      If null, no special processing is done.
**              enhstat -- optional, returns enhanced error code string (if set)
**              rtype -- type of SmtpMsgBuffer: does it contains secret data?
**
**      Returns:
**              reply code it reads.
**
**      Side Effects:
**              flushes the mail file.
*/

int
reply(m, mci, e, timeout, pfunc, enhstat, rtype)
        MAILER *m;
        MCI *mci;
        ENVELOPE *e;
        time_t timeout;
        void (*pfunc) __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
        char **enhstat;
        int rtype;
{
        register char *bufp;
        register int r;
        bool firstline = true;
        char junkbuf[MAXLINE];
        static char enhstatcode[ENHSCLEN];
        int save_errno;

        /*
        **  Flush the output before reading response.
        **
        **      For SMTP pipelining, it would be better if we didn't do
        **      this if there was already data waiting to be read.  But
        **      to do it properly means pushing it to the I/O library,
        **      since it really needs to be done below the buffer layer.
        */

        if (mci->mci_out != NULL)
                (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);

        if (tTd(18, 1))
                sm_dprintf("reply\n");

        /*
        **  Read the input line, being careful not to hang.
        */

        bufp = SmtpReplyBuffer;
        set_tls_rd_tmo(timeout);
        for (;;)
        {
                register char *p;

                /* actually do the read */
                if (e->e_xfp != NULL)   /* for debugging */
                        (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);

                /* if we are in the process of closing just give the code */
                if (mci->mci_state == MCIS_CLOSED)
                        return SMTPCLOSING;

                /* don't try to read from a non-existent fd */
                if (mci->mci_in == NULL)
                {
                        if (mci->mci_errno == 0)
                                mci->mci_errno = EBADF;

                        /* errors on QUIT should be ignored */
                        if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
                        {
                                errno = mci->mci_errno;
                                mci_close(mci, "reply:1");
                                return -1;
                        }
                        mci->mci_state = MCIS_ERROR;
                        smtpquit(m, mci, e);
                        errno = mci->mci_errno;
                        return -1;
                }

                if (mci->mci_out != NULL)
                        (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);

                /* get the line from the other side */
                p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
                save_errno = errno;
                mci->mci_lastuse = curtime();

                if (p == NULL)
                {
                        bool oldholderrs;
                        extern char MsgBuf[];

                        /* errors on QUIT should be ignored */
                        if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
                        {
                                mci_close(mci, "reply:2");
                                return -1;
                        }

                        /* if the remote end closed early, fake an error */
                        errno = save_errno;
                        if (errno == 0)
                        {
                                (void) sm_snprintf(SmtpReplyBuffer,
                                                   sizeof(SmtpReplyBuffer),
                                                   "421 4.4.1 Connection reset by %s",
                                                   CURHOSTNAME);
#ifdef ECONNRESET
                                errno = ECONNRESET;
#else /* ECONNRESET */
                                errno = EPIPE;
#endif /* ECONNRESET */
                        }

                        mci->mci_errno = errno;
                        oldholderrs = HoldErrs;
                        HoldErrs = true;
                        usrerr("451 4.4.1 reply: read error from %s",
                               CURHOSTNAME);
                        mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);

                        /* if debugging, pause so we can see state */
                        if (tTd(18, 100))
                                (void) pause();
                        mci->mci_state = MCIS_ERROR;
                        smtpquit(m, mci, e);
#if XDEBUG
                        {
                                char wbuf[MAXLINE];

                                p = wbuf;
                                if (e->e_to != NULL)
                                {
                                        (void) sm_snprintf(p,
                                                           SPACELEFT(wbuf, p),
                                                           "%s... ",
                                                           shortenstring(e->e_to, MAXSHORTSTR));
                                        p += strlen(p);
                                }
                                (void) sm_snprintf(p, SPACELEFT(wbuf, p),
                                                   "reply(%.100s) during %s",
                                                   CURHOSTNAME, SmtpPhase);
                                checkfd012(wbuf);
                        }
#endif /* XDEBUG */
                        HoldErrs = oldholderrs;
                        errno = save_errno;
                        return -1;
                }
                fixcrlf(bufp, true);

                /* EHLO failure is not a real error */
                if (e->e_xfp != NULL && (bufp[0] == '4' ||
                    (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
                {
                        /* serious error -- log the previous command */
                        if (SmtpNeedIntro)
                        {
                                /* inform user who we are chatting with */
                                (void) sm_io_fprintf(CurEnv->e_xfp,
                                                     SM_TIME_DEFAULT,
                                                     "... while talking to %s:\n",
                                                     CURHOSTNAME);
                                SmtpNeedIntro = false;
                        }
                        if (SmtpMsgBuffer[0] != '\0')
                        {
                                (void) sm_io_fprintf(e->e_xfp,
                                        SM_TIME_DEFAULT,
                                        ">>> %s\n",
                                        (rtype == XS_STARTTLS)
                                        ? "STARTTLS dialogue"
                                        : ((rtype == XS_AUTH)
                                           ? "AUTH dialogue"
                                           : SmtpMsgBuffer));
                                SmtpMsgBuffer[0] = '\0';
                        }

                        /* now log the message as from the other side */
                        (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
                                             "<<< %s\n", bufp);
                }

                /* display the input for verbose mode */
                if (Verbose)
                        nmessage("050 %s", bufp);

                /* ignore improperly formatted input */
                if (!ISSMTPREPLY(bufp))
                        continue;

                if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
                    enhstat != NULL &&
                    extenhsc(bufp + 4, ' ', enhstatcode) > 0)
                        *enhstat = enhstatcode;

                /* process the line */
                if (pfunc != NULL)
                        (*pfunc)(bufp, firstline, m, mci, e);

                firstline = false;

                /* decode the reply code */
                r = atoi(bufp);

                /* extra semantics: 0xx codes are "informational" */
                if (r < 100)
                        continue;

                /* if no continuation lines, return this line */
                if (bufp[3] != '-')
                        break;

                /* first line of real reply -- ignore rest */
                bufp = junkbuf;
        }

        /*
        **  Now look at SmtpReplyBuffer -- only care about the first
        **  line of the response from here on out.
        */

        /* save temporary failure messages for posterity */
        if (SmtpReplyBuffer[0] == '4')
                (void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof(SmtpError));

        /* reply code 421 is "Service Shutting Down" */
        if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD &&
            mci->mci_state != MCIS_QUITING)
        {
                /* send the quit protocol */
                mci->mci_state = MCIS_SSD;
                smtpquit(m, mci, e);
        }

        return r;
}
/*
**  SMTPMESSAGE -- send message to server
**
**      Parameters:
**              f -- format
**              m -- the mailer to control formatting.
**              a, b, c -- parameters
**
**      Returns:
**              none.
**
**      Side Effects:
**              writes message to mci->mci_out.
*/

/*VARARGS1*/
void
#ifdef __STDC__
smtpmessage(char *f, MAILER *m, MCI *mci, ...)
#else /* __STDC__ */
smtpmessage(f, m, mci, va_alist)
        char *f;
        MAILER *m;
        MCI *mci;
        va_dcl
#endif /* __STDC__ */
{
        SM_VA_LOCAL_DECL

        SM_VA_START(ap, mci);
        (void) sm_vsnprintf(SmtpMsgBuffer, sizeof(SmtpMsgBuffer), f, ap);
        SM_VA_END(ap);

        if (tTd(18, 1) || Verbose)
                nmessage(">>> %s", SmtpMsgBuffer);
        if (TrafficLogFile != NULL)
                (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
                                     "%05d >>> %s\n", (int) CurrentPid,
                                     SmtpMsgBuffer);
        if (mci->mci_out != NULL)
        {
                (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s",
                                     SmtpMsgBuffer, m == NULL ? "\r\n"
                                                              : m->m_eol);
        }
        else if (tTd(18, 1))
        {
                sm_dprintf("smtpmessage: NULL mci_out\n");
        }
}