1N/A/*
1N/A * Copyright (c) 1998-2006, 2008-2010 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
1N/A * Copyright (c) 1988, 1993
1N/A * The Regents of the University of California. All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A *
1N/A */
1N/A
1N/A#include <sendmail.h>
1N/A
1N/ASM_RCSID("@(#)$Id: usersmtp.c,v 8.485 2010/07/23 21:09:38 ca Exp $")
1N/A
1N/A#include <sysexits.h>
1N/A
1N/A
1N/Astatic void esmtp_check __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
1N/Astatic void helo_options __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
1N/Astatic int smtprcptstat __P((ADDRESS *, MAILER *, MCI *, ENVELOPE *));
1N/A
1N/A#if SASL
1N/Aextern void *sm_sasl_malloc __P((unsigned long));
1N/Aextern void sm_sasl_free __P((void *));
1N/A#endif /* SASL */
1N/A
1N/A/*
1N/A** USERSMTP -- run SMTP protocol from the user end.
1N/A**
1N/A** This protocol is described in RFC821.
1N/A*/
1N/A
1N/A#define SMTPCLOSING 421 /* "Service Shutting Down" */
1N/A
1N/A#define ENHSCN(e, d) ((e) == NULL ? (d) : (e))
1N/A
1N/A#define ENHSCN_RPOOL(e, d, rpool) \
1N/A ((e) == NULL ? (d) : sm_rpool_strdup_x(rpool, e))
1N/A
1N/Astatic char SmtpMsgBuffer[MAXLINE]; /* buffer for commands */
1N/Astatic char SmtpReplyBuffer[MAXLINE]; /* buffer for replies */
1N/Astatic bool SmtpNeedIntro; /* need "while talking" in transcript */
1N/A/*
1N/A** SMTPINIT -- initialize SMTP.
1N/A**
1N/A** Opens the connection and sends the initial protocol.
1N/A**
1N/A** Parameters:
1N/A** m -- mailer to create connection to.
1N/A** mci -- the mailer connection info.
1N/A** e -- the envelope.
1N/A** onlyhelo -- send only helo command?
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** creates connection and sends initial protocol.
1N/A*/
1N/A
1N/Avoid
1N/Asmtpinit(m, mci, e, onlyhelo)
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A bool onlyhelo;
1N/A{
1N/A register int r;
1N/A int state;
1N/A register char *p;
1N/A register char *hn;
1N/A char *enhsc;
1N/A
1N/A enhsc = NULL;
1N/A if (tTd(18, 1))
1N/A {
1N/A sm_dprintf("smtpinit ");
1N/A mci_dump(sm_debug_file(), mci, false);
1N/A }
1N/A
1N/A /*
1N/A ** Open the connection to the mailer.
1N/A */
1N/A
1N/A SmtpError[0] = '\0';
1N/A SmtpMsgBuffer[0] = '\0';
1N/A CurHostName = mci->mci_host; /* XXX UGLY XXX */
1N/A if (CurHostName == NULL)
1N/A CurHostName = MyHostName;
1N/A SmtpNeedIntro = true;
1N/A state = mci->mci_state;
1N/A switch (state)
1N/A {
1N/A case MCIS_MAIL:
1N/A case MCIS_RCPT:
1N/A case MCIS_DATA:
1N/A /* need to clear old information */
1N/A smtprset(m, mci, e);
1N/A /* FALLTHROUGH */
1N/A
1N/A case MCIS_OPEN:
1N/A if (!onlyhelo)
1N/A return;
1N/A break;
1N/A
1N/A case MCIS_ERROR:
1N/A case MCIS_QUITING:
1N/A case MCIS_SSD:
1N/A /* shouldn't happen */
1N/A smtpquit(m, mci, e);
1N/A /* FALLTHROUGH */
1N/A
1N/A case MCIS_CLOSED:
1N/A syserr("451 4.4.0 smtpinit: state CLOSED (was %d)", state);
1N/A return;
1N/A
1N/A case MCIS_OPENING:
1N/A break;
1N/A }
1N/A if (onlyhelo)
1N/A goto helo;
1N/A
1N/A mci->mci_state = MCIS_OPENING;
1N/A clrsessenvelope(e);
1N/A
1N/A /*
1N/A ** Get the greeting message.
1N/A ** This should appear spontaneously. Give it five minutes to
1N/A ** happen.
1N/A */
1N/A
1N/A SmtpPhase = mci->mci_phase = "client greeting";
1N/A sm_setproctitle(true, e, "%s %s: %s",
1N/A qid_printname(e), CurHostName, mci->mci_phase);
1N/A r = reply(m, mci, e, TimeOuts.to_initial, esmtp_check, NULL, XS_GREET);
1N/A if (r < 0)
1N/A goto tempfail1;
1N/A if (REPLYTYPE(r) == 4)
1N/A goto tempfail2;
1N/A if (REPLYTYPE(r) != 2)
1N/A goto unavailable;
1N/A
1N/A /*
1N/A ** Send the HELO command.
1N/A ** My mother taught me to always introduce myself.
1N/A */
1N/A
1N/Ahelo:
1N/A if (bitnset(M_ESMTP, m->m_flags) || bitnset(M_LMTP, m->m_flags))
1N/A mci->mci_flags |= MCIF_ESMTP;
1N/A hn = mci->mci_heloname ? mci->mci_heloname : MyHostName;
1N/A
1N/Atryhelo:
1N/A#if _FFR_IGNORE_EXT_ON_HELO
1N/A mci->mci_flags &= ~MCIF_HELO;
1N/A#endif /* _FFR_IGNORE_EXT_ON_HELO */
1N/A if (bitnset(M_LMTP, m->m_flags))
1N/A {
1N/A smtpmessage("LHLO %s", m, mci, hn);
1N/A SmtpPhase = mci->mci_phase = "client LHLO";
1N/A }
1N/A else if (bitset(MCIF_ESMTP, mci->mci_flags) &&
1N/A !bitnset(M_FSMTP, m->m_flags))
1N/A {
1N/A smtpmessage("EHLO %s", m, mci, hn);
1N/A SmtpPhase = mci->mci_phase = "client EHLO";
1N/A }
1N/A else
1N/A {
1N/A smtpmessage("HELO %s", m, mci, hn);
1N/A SmtpPhase = mci->mci_phase = "client HELO";
1N/A#if _FFR_IGNORE_EXT_ON_HELO
1N/A mci->mci_flags |= MCIF_HELO;
1N/A#endif /* _FFR_IGNORE_EXT_ON_HELO */
1N/A }
1N/A sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
1N/A CurHostName, mci->mci_phase);
1N/A r = reply(m, mci, e,
1N/A bitnset(M_LMTP, m->m_flags) ? TimeOuts.to_lhlo
1N/A : TimeOuts.to_helo,
1N/A helo_options, NULL, XS_EHLO);
1N/A if (r < 0)
1N/A goto tempfail1;
1N/A else if (REPLYTYPE(r) == 5)
1N/A {
1N/A if (bitset(MCIF_ESMTP, mci->mci_flags) &&
1N/A !bitnset(M_LMTP, m->m_flags))
1N/A {
1N/A /* try old SMTP instead */
1N/A mci->mci_flags &= ~MCIF_ESMTP;
1N/A goto tryhelo;
1N/A }
1N/A goto unavailable;
1N/A }
1N/A else if (REPLYTYPE(r) != 2)
1N/A goto tempfail2;
1N/A
1N/A /*
1N/A ** Check to see if we actually ended up talking to ourself.
1N/A ** This means we didn't know about an alias or MX, or we managed
1N/A ** to connect to an echo server.
1N/A */
1N/A
1N/A p = strchr(&SmtpReplyBuffer[4], ' ');
1N/A if (p != NULL)
1N/A *p = '\0';
1N/A if (!bitnset(M_NOLOOPCHECK, m->m_flags) &&
1N/A !bitnset(M_LMTP, m->m_flags) &&
1N/A sm_strcasecmp(&SmtpReplyBuffer[4], MyHostName) == 0)
1N/A {
1N/A syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
1N/A CurHostName);
1N/A mci_setstat(mci, EX_CONFIG, "5.3.5",
1N/A "553 5.3.5 system config error");
1N/A mci->mci_errno = 0;
1N/A smtpquit(m, mci, e);
1N/A return;
1N/A }
1N/A
1N/A /*
1N/A ** If this is expected to be another sendmail, send some internal
1N/A ** commands.
1N/A ** If we're running as MSP, "propagate" -v flag if possible.
1N/A */
1N/A
1N/A if ((UseMSP && Verbose && bitset(MCIF_VERB, mci->mci_flags))
1N/A# if !_FFR_DEPRECATE_MAILER_FLAG_I
1N/A || bitnset(M_INTERNAL, m->m_flags)
1N/A# endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */
1N/A )
1N/A {
1N/A /* tell it to be verbose */
1N/A smtpmessage("VERB", m, mci);
1N/A r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, &enhsc,
1N/A XS_DEFAULT);
1N/A if (r < 0)
1N/A goto tempfail1;
1N/A }
1N/A
1N/A if (mci->mci_state != MCIS_CLOSED)
1N/A {
1N/A mci->mci_state = MCIS_OPEN;
1N/A return;
1N/A }
1N/A
1N/A /* got a 421 error code during startup */
1N/A
1N/A tempfail1:
1N/A mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.4.2"), NULL);
1N/A if (mci->mci_state != MCIS_CLOSED)
1N/A smtpquit(m, mci, e);
1N/A return;
1N/A
1N/A tempfail2:
1N/A /* XXX should use code from other end iff ENHANCEDSTATUSCODES */
1N/A mci_setstat(mci, EX_TEMPFAIL, ENHSCN(enhsc, "4.5.0"),
1N/A SmtpReplyBuffer);
1N/A if (mci->mci_state != MCIS_CLOSED)
1N/A smtpquit(m, mci, e);
1N/A return;
1N/A
1N/A unavailable:
1N/A mci_setstat(mci, EX_UNAVAILABLE, "5.5.0", SmtpReplyBuffer);
1N/A smtpquit(m, mci, e);
1N/A return;
1N/A}
1N/A/*
1N/A** ESMTP_CHECK -- check to see if this implementation likes ESMTP protocol
1N/A**
1N/A** Parameters:
1N/A** line -- the response line.
1N/A** firstline -- set if this is the first line of the reply.
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection info.
1N/A** e -- the envelope.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Astatic void
1N/Aesmtp_check(line, firstline, m, mci, e)
1N/A char *line;
1N/A bool firstline;
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A if (strstr(line, "ESMTP") != NULL)
1N/A mci->mci_flags |= MCIF_ESMTP;
1N/A
1N/A /*
1N/A ** Dirty hack below. Quoting the author:
1N/A ** This was a response to people who wanted SMTP transmission to be
1N/A ** just-send-8 by default. Essentially, you could put this tag into
1N/A ** your greeting message to behave as though the F=8 flag was set on
1N/A ** the mailer.
1N/A */
1N/A
1N/A if (strstr(line, "8BIT-OK") != NULL)
1N/A mci->mci_flags |= MCIF_8BITOK;
1N/A}
1N/A
1N/A#if SASL
1N/A/* specify prototype so compiler can check calls */
1N/Astatic char *str_union __P((char *, char *, SM_RPOOL_T *));
1N/A
1N/A/*
1N/A** STR_UNION -- create the union of two lists
1N/A**
1N/A** Parameters:
1N/A** s1, s2 -- lists of items (separated by single blanks).
1N/A** rpool -- resource pool from which result is allocated.
1N/A**
1N/A** Returns:
1N/A** the union of both lists.
1N/A*/
1N/A
1N/Astatic char *
1N/Astr_union(s1, s2, rpool)
1N/A char *s1, *s2;
1N/A SM_RPOOL_T *rpool;
1N/A{
1N/A char *hr, *h1, *h, *res;
1N/A int l1, l2, rl;
1N/A
1N/A if (s1 == NULL || *s1 == '\0')
1N/A return s2;
1N/A if (s2 == NULL || *s2 == '\0')
1N/A return s1;
1N/A l1 = strlen(s1);
1N/A l2 = strlen(s2);
1N/A rl = l1 + l2;
1N/A if (rl <= 0)
1N/A {
1N/A sm_syslog(LOG_WARNING, NOQID,
1N/A "str_union: stringlen1=%d, stringlen2=%d, sum=%d, status=overflow",
1N/A l1, l2, rl);
1N/A res = NULL;
1N/A }
1N/A else
1N/A res = (char *) sm_rpool_malloc(rpool, rl + 2);
1N/A if (res == NULL)
1N/A {
1N/A if (l1 > l2)
1N/A return s1;
1N/A return s2;
1N/A }
1N/A (void) sm_strlcpy(res, s1, rl);
1N/A hr = res + l1;
1N/A h1 = s2;
1N/A h = s2;
1N/A
1N/A /* walk through s2 */
1N/A while (h != NULL && *h1 != '\0')
1N/A {
1N/A /* is there something after the current word? */
1N/A if ((h = strchr(h1, ' ')) != NULL)
1N/A *h = '\0';
1N/A l1 = strlen(h1);
1N/A
1N/A /* does the current word appear in s1 ? */
1N/A if (iteminlist(h1, s1, " ") == NULL)
1N/A {
1N/A /* add space as delimiter */
1N/A *hr++ = ' ';
1N/A
1N/A /* copy the item */
1N/A memcpy(hr, h1, l1);
1N/A
1N/A /* advance pointer in result list */
1N/A hr += l1;
1N/A *hr = '\0';
1N/A }
1N/A if (h != NULL)
1N/A {
1N/A /* there are more items */
1N/A *h = ' ';
1N/A h1 = h + 1;
1N/A }
1N/A }
1N/A return res;
1N/A}
1N/A#endif /* SASL */
1N/A
1N/A/*
1N/A** HELO_OPTIONS -- process the options on a HELO line.
1N/A**
1N/A** Parameters:
1N/A** line -- the response line.
1N/A** firstline -- set if this is the first line of the reply.
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection info.
1N/A** e -- the envelope (unused).
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Astatic void
1N/Ahelo_options(line, firstline, m, mci, e)
1N/A char *line;
1N/A bool firstline;
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A register char *p;
1N/A#if _FFR_IGNORE_EXT_ON_HELO
1N/A static bool logged = false;
1N/A#endif /* _FFR_IGNORE_EXT_ON_HELO */
1N/A
1N/A if (firstline)
1N/A {
1N/A mci_clr_extensions(mci);
1N/A#if _FFR_IGNORE_EXT_ON_HELO
1N/A logged = false;
1N/A#endif /* _FFR_IGNORE_EXT_ON_HELO */
1N/A return;
1N/A }
1N/A#if _FFR_IGNORE_EXT_ON_HELO
1N/A else if (bitset(MCIF_HELO, mci->mci_flags))
1N/A {
1N/A if (LogLevel > 8 && !logged)
1N/A {
1N/A sm_syslog(LOG_WARNING, NOQID,
1N/A "server=%s [%s] returned extensions despite HELO command",
1N/A macvalue(macid("{server_name}"), e),
1N/A macvalue(macid("{server_addr}"), e));
1N/A logged = true;
1N/A }
1N/A return;
1N/A }
1N/A#endif /* _FFR_IGNORE_EXT_ON_HELO */
1N/A
1N/A if (strlen(line) < 5)
1N/A return;
1N/A line += 4;
1N/A p = strpbrk(line, " =");
1N/A if (p != NULL)
1N/A *p++ = '\0';
1N/A if (sm_strcasecmp(line, "size") == 0)
1N/A {
1N/A mci->mci_flags |= MCIF_SIZE;
1N/A if (p != NULL)
1N/A mci->mci_maxsize = atol(p);
1N/A }
1N/A else if (sm_strcasecmp(line, "8bitmime") == 0)
1N/A {
1N/A mci->mci_flags |= MCIF_8BITMIME;
1N/A mci->mci_flags &= ~MCIF_7BIT;
1N/A }
1N/A else if (sm_strcasecmp(line, "expn") == 0)
1N/A mci->mci_flags |= MCIF_EXPN;
1N/A else if (sm_strcasecmp(line, "dsn") == 0)
1N/A mci->mci_flags |= MCIF_DSN;
1N/A else if (sm_strcasecmp(line, "enhancedstatuscodes") == 0)
1N/A mci->mci_flags |= MCIF_ENHSTAT;
1N/A else if (sm_strcasecmp(line, "pipelining") == 0)
1N/A mci->mci_flags |= MCIF_PIPELINED;
1N/A else if (sm_strcasecmp(line, "verb") == 0)
1N/A mci->mci_flags |= MCIF_VERB;
1N/A#if STARTTLS
1N/A else if (sm_strcasecmp(line, "starttls") == 0)
1N/A mci->mci_flags |= MCIF_TLS;
1N/A#endif /* STARTTLS */
1N/A else if (sm_strcasecmp(line, "deliverby") == 0)
1N/A {
1N/A mci->mci_flags |= MCIF_DLVR_BY;
1N/A if (p != NULL)
1N/A mci->mci_min_by = atol(p);
1N/A }
1N/A#if SASL
1N/A else if (sm_strcasecmp(line, "auth") == 0)
1N/A {
1N/A if (p != NULL && *p != '\0' &&
1N/A !bitset(MCIF_AUTH2, mci->mci_flags))
1N/A {
1N/A if (mci->mci_saslcap != NULL)
1N/A {
1N/A /*
1N/A ** Create the union with previous auth
1N/A ** offerings because we recognize "auth "
1N/A ** and "auth=" (old format).
1N/A */
1N/A
1N/A mci->mci_saslcap = str_union(mci->mci_saslcap,
1N/A p, mci->mci_rpool);
1N/A mci->mci_flags |= MCIF_AUTH2;
1N/A }
1N/A else
1N/A {
1N/A int l;
1N/A
1N/A l = strlen(p) + 1;
1N/A mci->mci_saslcap = (char *)
1N/A sm_rpool_malloc(mci->mci_rpool, l);
1N/A if (mci->mci_saslcap != NULL)
1N/A {
1N/A (void) sm_strlcpy(mci->mci_saslcap, p,
1N/A l);
1N/A mci->mci_flags |= MCIF_AUTH;
1N/A }
1N/A }
1N/A }
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "AUTH flags=%lx, mechs=%s",
1N/A mci->mci_flags, mci->mci_saslcap);
1N/A }
1N/A#endif /* SASL */
1N/A}
1N/A#if SASL
1N/A
1N/Astatic int getsimple __P((void *, int, const char **, unsigned *));
1N/Astatic int getsecret __P((sasl_conn_t *, void *, int, sasl_secret_t **));
1N/Astatic int saslgetrealm __P((void *, int, const char **, const char **));
1N/Astatic int readauth __P((char *, bool, SASL_AI_T *m, SM_RPOOL_T *));
1N/Astatic int getauth __P((MCI *, ENVELOPE *, SASL_AI_T *));
1N/Astatic char *removemech __P((char *, char *, SM_RPOOL_T *));
1N/Astatic int attemptauth __P((MAILER *, MCI *, ENVELOPE *, SASL_AI_T *));
1N/A
1N/Astatic sasl_callback_t callbacks[] =
1N/A{
1N/A { SASL_CB_GETREALM, &saslgetrealm, NULL },
1N/A#define CB_GETREALM_IDX 0
1N/A { SASL_CB_PASS, &getsecret, NULL },
1N/A#define CB_PASS_IDX 1
1N/A { SASL_CB_USER, &getsimple, NULL },
1N/A#define CB_USER_IDX 2
1N/A { SASL_CB_AUTHNAME, &getsimple, NULL },
1N/A#define CB_AUTHNAME_IDX 3
1N/A { SASL_CB_VERIFYFILE, &safesaslfile, NULL },
1N/A#define CB_SAFESASL_IDX 4
1N/A { SASL_CB_LIST_END, NULL, NULL }
1N/A};
1N/A
1N/A/*
1N/A** INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** SASL_OK -- if successful.
1N/A** SASL error code -- otherwise.
1N/A**
1N/A** Side Effects:
1N/A** checks/sets sasl_clt_init.
1N/A**
1N/A** Note:
1N/A** Callbacks are ignored if sasl_client_init() has
1N/A** been called before (by a library such as libnss_ldap)
1N/A*/
1N/A
1N/Astatic bool sasl_clt_init = false;
1N/A
1N/Astatic int
1N/Ainit_sasl_client()
1N/A{
1N/A int result;
1N/A
1N/A if (sasl_clt_init)
1N/A return SASL_OK;
1N/A result = sasl_client_init(callbacks);
1N/A
1N/A /* should we retry later again or just remember that it failed? */
1N/A if (result == SASL_OK)
1N/A sasl_clt_init = true;
1N/A return result;
1N/A}
1N/A/*
1N/A** STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** checks/sets sasl_clt_init.
1N/A*/
1N/A
1N/Avoid
1N/Astop_sasl_client()
1N/A{
1N/A if (!sasl_clt_init)
1N/A return;
1N/A sasl_clt_init = false;
1N/A sasl_done();
1N/A}
1N/A/*
1N/A** GETSASLDATA -- process the challenges from the SASL protocol
1N/A**
1N/A** This gets the relevant sasl response data out of the reply
1N/A** from the server.
1N/A**
1N/A** Parameters:
1N/A** line -- the response line.
1N/A** firstline -- set if this is the first line of the reply.
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection info.
1N/A** e -- the envelope (unused).
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Astatic void getsasldata __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
1N/A
1N/Astatic void
1N/Agetsasldata(line, firstline, m, mci, e)
1N/A char *line;
1N/A bool firstline;
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A int len;
1N/A int result;
1N/A# if SASL < 20000
1N/A char *out;
1N/A# endif /* SASL < 20000 */
1N/A
1N/A /* if not a continue we don't care about it */
1N/A len = strlen(line);
1N/A if ((len <= 4) ||
1N/A (line[0] != '3') ||
1N/A !isascii(line[1]) || !isdigit(line[1]) ||
1N/A !isascii(line[2]) || !isdigit(line[2]))
1N/A {
1N/A SM_FREE_CLR(mci->mci_sasl_string);
1N/A return;
1N/A }
1N/A
1N/A /* forget about "334 " */
1N/A line += 4;
1N/A len -= 4;
1N/A# if SASL >= 20000
1N/A /* XXX put this into a macro/function? It's duplicated below */
1N/A if (mci->mci_sasl_string != NULL)
1N/A {
1N/A if (mci->mci_sasl_string_len <= len)
1N/A {
1N/A sm_free(mci->mci_sasl_string); /* XXX */
1N/A mci->mci_sasl_string = xalloc(len + 1);
1N/A }
1N/A }
1N/A else
1N/A mci->mci_sasl_string = xalloc(len + 1);
1N/A
1N/A result = sasl_decode64(line, len, mci->mci_sasl_string, len + 1,
1N/A (unsigned int *) &mci->mci_sasl_string_len);
1N/A if (result != SASL_OK)
1N/A {
1N/A mci->mci_sasl_string_len = 0;
1N/A *mci->mci_sasl_string = '\0';
1N/A }
1N/A# else /* SASL >= 20000 */
1N/A out = (char *) sm_rpool_malloc_x(mci->mci_rpool, len + 1);
1N/A result = sasl_decode64(line, len, out, (unsigned int *) &len);
1N/A if (result != SASL_OK)
1N/A {
1N/A len = 0;
1N/A *out = '\0';
1N/A }
1N/A
1N/A /*
1N/A ** mci_sasl_string is "shared" with Cyrus-SASL library; hence
1N/A ** it can't be in an rpool unless we use the same memory
1N/A ** management mechanism (with same rpool!) for Cyrus SASL.
1N/A */
1N/A
1N/A if (mci->mci_sasl_string != NULL)
1N/A {
1N/A if (mci->mci_sasl_string_len <= len)
1N/A {
1N/A sm_free(mci->mci_sasl_string); /* XXX */
1N/A mci->mci_sasl_string = xalloc(len + 1);
1N/A }
1N/A }
1N/A else
1N/A mci->mci_sasl_string = xalloc(len + 1);
1N/A
1N/A memcpy(mci->mci_sasl_string, out, len);
1N/A mci->mci_sasl_string[len] = '\0';
1N/A mci->mci_sasl_string_len = len;
1N/A# endif /* SASL >= 20000 */
1N/A return;
1N/A}
1N/A/*
1N/A** READAUTH -- read auth values from a file
1N/A**
1N/A** Parameters:
1N/A** filename -- name of file to read.
1N/A** safe -- if set, this is a safe read.
1N/A** sai -- where to store auth_info.
1N/A** rpool -- resource pool for sai.
1N/A**
1N/A** Returns:
1N/A** EX_OK -- data succesfully read.
1N/A** EX_UNAVAILABLE -- no valid filename.
1N/A** EX_TEMPFAIL -- temporary failure.
1N/A*/
1N/A
1N/Astatic char *sasl_info_name[] =
1N/A{
1N/A "user id",
1N/A "authentication id",
1N/A "password",
1N/A "realm",
1N/A "mechlist"
1N/A};
1N/Astatic int
1N/Areadauth(filename, safe, sai, rpool)
1N/A char *filename;
1N/A bool safe;
1N/A SASL_AI_T *sai;
1N/A SM_RPOOL_T *rpool;
1N/A{
1N/A SM_FILE_T *f;
1N/A long sff;
1N/A pid_t pid;
1N/A int lc;
1N/A char *s;
1N/A char buf[MAXLINE];
1N/A
1N/A if (filename == NULL || filename[0] == '\0')
1N/A return EX_UNAVAILABLE;
1N/A
1N/A#if !_FFR_ALLOW_SASLINFO
1N/A /*
1N/A ** make sure we don't use a program that is not
1N/A ** accesible to the user who specified a different authinfo file.
1N/A ** However, currently we don't pass this info (authinfo file
1N/A ** specified by user) around, so we just turn off program access.
1N/A */
1N/A
1N/A if (filename[0] == '|')
1N/A {
1N/A auto int fd;
1N/A int i;
1N/A char *p;
1N/A char *argv[MAXPV + 1];
1N/A
1N/A i = 0;
1N/A for (p = strtok(&filename[1], " \t"); p != NULL;
1N/A p = strtok(NULL, " \t"))
1N/A {
1N/A if (i >= MAXPV)
1N/A break;
1N/A argv[i++] = p;
1N/A }
1N/A argv[i] = NULL;
1N/A pid = prog_open(argv, &fd, CurEnv);
1N/A if (pid < 0)
1N/A f = NULL;
1N/A else
1N/A f = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
1N/A (void *) &fd, SM_IO_RDONLY, NULL);
1N/A }
1N/A else
1N/A#endif /* !_FFR_ALLOW_SASLINFO */
1N/A {
1N/A pid = -1;
1N/A sff = SFF_REGONLY|SFF_SAFEDIRPATH|SFF_NOWLINK
1N/A |SFF_NOGWFILES|SFF_NOWWFILES|SFF_NOWRFILES;
1N/A# if _FFR_GROUPREADABLEAUTHINFOFILE
1N/A if (!bitnset(DBS_GROUPREADABLEAUTHINFOFILE, DontBlameSendmail))
1N/A# endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
1N/A sff |= SFF_NOGRFILES;
1N/A if (DontLockReadFiles)
1N/A sff |= SFF_NOLOCK;
1N/A
1N/A#if _FFR_ALLOW_SASLINFO
1N/A /*
1N/A ** XXX: make sure we don't read or open files that are not
1N/A ** accesible to the user who specified a different authinfo
1N/A ** file.
1N/A */
1N/A
1N/A sff |= SFF_MUSTOWN;
1N/A#else /* _FFR_ALLOW_SASLINFO */
1N/A if (safe)
1N/A sff |= SFF_OPENASROOT;
1N/A#endif /* _FFR_ALLOW_SASLINFO */
1N/A
1N/A f = safefopen(filename, O_RDONLY, 0, sff);
1N/A }
1N/A if (f == NULL)
1N/A {
1N/A if (LogLevel > 5)
1N/A sm_syslog(LOG_ERR, NOQID,
1N/A "AUTH=client, error: can't open %s: %s",
1N/A filename, sm_errstring(errno));
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A lc = 0;
1N/A while (lc <= SASL_MECHLIST &&
1N/A sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
1N/A {
1N/A if (buf[0] != '#')
1N/A {
1N/A (*sai)[lc] = sm_rpool_strdup_x(rpool, buf);
1N/A if ((s = strchr((*sai)[lc], '\n')) != NULL)
1N/A *s = '\0';
1N/A lc++;
1N/A }
1N/A }
1N/A
1N/A (void) sm_io_close(f, SM_TIME_DEFAULT);
1N/A if (pid > 0)
1N/A (void) waitfor(pid);
1N/A if (lc < SASL_PASSWORD)
1N/A {
1N/A if (LogLevel > 8)
1N/A sm_syslog(LOG_ERR, NOQID,
1N/A "AUTH=client, error: can't read %s from %s",
1N/A sasl_info_name[lc + 1], filename);
1N/A return EX_TEMPFAIL;
1N/A }
1N/A return EX_OK;
1N/A}
1N/A
1N/A/*
1N/A** GETAUTH -- get authinfo from ruleset call
1N/A**
1N/A** {server_name}, {server_addr} must be set
1N/A**
1N/A** Parameters:
1N/A** mci -- the mailer connection structure.
1N/A** e -- the envelope (including the sender to specify).
1N/A** sai -- pointer to authinfo (result).
1N/A**
1N/A** Returns:
1N/A** EX_OK -- ruleset was succesfully called, data may not
1N/A** be available, sai must be checked.
1N/A** EX_UNAVAILABLE -- ruleset unavailable (or failed).
1N/A** EX_TEMPFAIL -- temporary failure (from ruleset).
1N/A**
1N/A** Side Effects:
1N/A** Fills in sai if successful.
1N/A*/
1N/A
1N/Astatic int
1N/Agetauth(mci, e, sai)
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A SASL_AI_T *sai;
1N/A{
1N/A int i, r, l, got, ret;
1N/A char **pvp;
1N/A char pvpbuf[PSBUFSIZE];
1N/A
1N/A r = rscap("authinfo", macvalue(macid("{server_name}"), e),
1N/A macvalue(macid("{server_addr}"), e), e,
1N/A &pvp, pvpbuf, sizeof(pvpbuf));
1N/A
1N/A if (r != EX_OK)
1N/A return EX_UNAVAILABLE;
1N/A
1N/A /* other than expected return value: ok (i.e., no auth) */
1N/A if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
1N/A return EX_OK;
1N/A if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
1N/A return EX_TEMPFAIL;
1N/A
1N/A /*
1N/A ** parse the data, put it into sai
1N/A ** format: "TDstring" (including the '"' !)
1N/A ** where T is a tag: 'U', ...
1N/A ** D is a delimiter: ':' or '='
1N/A */
1N/A
1N/A ret = EX_OK; /* default return value */
1N/A i = 0;
1N/A got = 0;
1N/A while (i < SASL_ENTRIES)
1N/A {
1N/A if (pvp[i + 1] == NULL)
1N/A break;
1N/A if (pvp[i + 1][0] != '"')
1N/A break;
1N/A switch (pvp[i + 1][1])
1N/A {
1N/A case 'U':
1N/A case 'u':
1N/A r = SASL_USER;
1N/A break;
1N/A case 'I':
1N/A case 'i':
1N/A r = SASL_AUTHID;
1N/A break;
1N/A case 'P':
1N/A case 'p':
1N/A r = SASL_PASSWORD;
1N/A break;
1N/A case 'R':
1N/A case 'r':
1N/A r = SASL_DEFREALM;
1N/A break;
1N/A case 'M':
1N/A case 'm':
1N/A r = SASL_MECHLIST;
1N/A break;
1N/A default:
1N/A goto fail;
1N/A }
1N/A l = strlen(pvp[i + 1]);
1N/A
1N/A /* check syntax */
1N/A if (l <= 3 || pvp[i + 1][l - 1] != '"')
1N/A goto fail;
1N/A
1N/A /* remove closing quote */
1N/A pvp[i + 1][l - 1] = '\0';
1N/A
1N/A /* remove "TD and " */
1N/A l -= 4;
1N/A (*sai)[r] = (char *) sm_rpool_malloc(mci->mci_rpool, l + 1);
1N/A if ((*sai)[r] == NULL)
1N/A goto tempfail;
1N/A if (pvp[i + 1][2] == ':')
1N/A {
1N/A /* ':text' (just copy) */
1N/A (void) sm_strlcpy((*sai)[r], pvp[i + 1] + 3, l + 1);
1N/A got |= 1 << r;
1N/A }
1N/A else if (pvp[i + 1][2] == '=')
1N/A {
1N/A unsigned int len;
1N/A
1N/A /* '=base64' (decode) */
1N/A# if SASL >= 20000
1N/A ret = sasl_decode64(pvp[i + 1] + 3,
1N/A (unsigned int) l, (*sai)[r],
1N/A (unsigned int) l + 1, &len);
1N/A# else /* SASL >= 20000 */
1N/A ret = sasl_decode64(pvp[i + 1] + 3,
1N/A (unsigned int) l, (*sai)[r], &len);
1N/A# endif /* SASL >= 20000 */
1N/A if (ret != SASL_OK)
1N/A goto fail;
1N/A got |= 1 << r;
1N/A }
1N/A else
1N/A goto fail;
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
1N/A sasl_info_name[r], (*sai)[r]);
1N/A ++i;
1N/A }
1N/A
1N/A /* did we get the expected data? */
1N/A /* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */
1N/A if (!(bitset(SASL_USER_BIT|SASL_AUTHID_BIT, got) &&
1N/A bitset(SASL_PASSWORD_BIT, got)))
1N/A goto fail;
1N/A
1N/A /* no authid? copy uid */
1N/A if (!bitset(SASL_AUTHID_BIT, got))
1N/A {
1N/A l = strlen((*sai)[SASL_USER]) + 1;
1N/A (*sai)[SASL_AUTHID] = (char *) sm_rpool_malloc(mci->mci_rpool,
1N/A l + 1);
1N/A if ((*sai)[SASL_AUTHID] == NULL)
1N/A goto tempfail;
1N/A (void) sm_strlcpy((*sai)[SASL_AUTHID], (*sai)[SASL_USER], l);
1N/A }
1N/A
1N/A /* no uid? copy authid */
1N/A if (!bitset(SASL_USER_BIT, got))
1N/A {
1N/A l = strlen((*sai)[SASL_AUTHID]) + 1;
1N/A (*sai)[SASL_USER] = (char *) sm_rpool_malloc(mci->mci_rpool,
1N/A l + 1);
1N/A if ((*sai)[SASL_USER] == NULL)
1N/A goto tempfail;
1N/A (void) sm_strlcpy((*sai)[SASL_USER], (*sai)[SASL_AUTHID], l);
1N/A }
1N/A return EX_OK;
1N/A
1N/A tempfail:
1N/A ret = EX_TEMPFAIL;
1N/A fail:
1N/A if (LogLevel > 8)
1N/A sm_syslog(LOG_WARNING, NOQID,
1N/A "AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
1N/A macvalue(macid("{server_name}"), e),
1N/A macvalue(macid("{server_addr}"), e),
1N/A ret == EX_TEMPFAIL ? "temp" : "");
1N/A for (i = 0; i <= SASL_MECHLIST; i++)
1N/A (*sai)[i] = NULL; /* just clear; rpool */
1N/A return ret;
1N/A}
1N/A
1N/A# if SASL >= 20000
1N/A/*
1N/A** GETSIMPLE -- callback to get userid or authid
1N/A**
1N/A** Parameters:
1N/A** context -- sai
1N/A** id -- what to do
1N/A** result -- (pointer to) result
1N/A** len -- (pointer to) length of result
1N/A**
1N/A** Returns:
1N/A** OK/failure values
1N/A*/
1N/A
1N/Astatic int
1N/Agetsimple(context, id, result, len)
1N/A void *context;
1N/A int id;
1N/A const char **result;
1N/A unsigned *len;
1N/A{
1N/A SASL_AI_T *sai;
1N/A
1N/A if (result == NULL || context == NULL)
1N/A return SASL_BADPARAM;
1N/A sai = (SASL_AI_T *) context;
1N/A
1N/A switch (id)
1N/A {
1N/A case SASL_CB_USER:
1N/A *result = (*sai)[SASL_USER];
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
1N/A *result);
1N/A if (len != NULL)
1N/A *len = *result != NULL ? strlen(*result) : 0;
1N/A break;
1N/A
1N/A case SASL_CB_AUTHNAME:
1N/A *result = (*sai)[SASL_AUTHID];
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
1N/A *result);
1N/A if (len != NULL)
1N/A *len = *result != NULL ? strlen(*result) : 0;
1N/A break;
1N/A
1N/A case SASL_CB_LANGUAGE:
1N/A *result = NULL;
1N/A if (len != NULL)
1N/A *len = 0;
1N/A break;
1N/A
1N/A default:
1N/A return SASL_BADPARAM;
1N/A }
1N/A return SASL_OK;
1N/A}
1N/A/*
1N/A** GETSECRET -- callback to get password
1N/A**
1N/A** Parameters:
1N/A** conn -- connection information
1N/A** context -- sai
1N/A** id -- what to do
1N/A** psecret -- (pointer to) result
1N/A**
1N/A** Returns:
1N/A** OK/failure values
1N/A*/
1N/A
1N/Astatic int
1N/Agetsecret(conn, context, id, psecret)
1N/A sasl_conn_t *conn;
1N/A SM_UNUSED(void *context);
1N/A int id;
1N/A sasl_secret_t **psecret;
1N/A{
1N/A int len;
1N/A char *authpass;
1N/A MCI *mci;
1N/A
1N/A if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
1N/A return SASL_BADPARAM;
1N/A
1N/A mci = (MCI *) context;
1N/A authpass = mci->mci_sai[SASL_PASSWORD];
1N/A len = strlen(authpass);
1N/A
1N/A /*
1N/A ** use an rpool because we are responsible for free()ing the secret,
1N/A ** but we can't free() it until after the auth completes
1N/A */
1N/A
1N/A *psecret = (sasl_secret_t *) sm_rpool_malloc(mci->mci_rpool,
1N/A sizeof(sasl_secret_t) +
1N/A len + 1);
1N/A if (*psecret == NULL)
1N/A return SASL_FAIL;
1N/A (void) sm_strlcpy((char *) (*psecret)->data, authpass, len + 1);
1N/A (*psecret)->len = (unsigned long) len;
1N/A return SASL_OK;
1N/A}
1N/A# else /* SASL >= 20000 */
1N/A/*
1N/A** GETSIMPLE -- callback to get userid or authid
1N/A**
1N/A** Parameters:
1N/A** context -- sai
1N/A** id -- what to do
1N/A** result -- (pointer to) result
1N/A** len -- (pointer to) length of result
1N/A**
1N/A** Returns:
1N/A** OK/failure values
1N/A*/
1N/A
1N/Astatic int
1N/Agetsimple(context, id, result, len)
1N/A void *context;
1N/A int id;
1N/A const char **result;
1N/A unsigned *len;
1N/A{
1N/A char *h, *s;
1N/A# if SASL > 10509
1N/A bool addrealm;
1N/A# endif /* SASL > 10509 */
1N/A size_t l;
1N/A SASL_AI_T *sai;
1N/A char *authid = NULL;
1N/A
1N/A if (result == NULL || context == NULL)
1N/A return SASL_BADPARAM;
1N/A sai = (SASL_AI_T *) context;
1N/A
1N/A /*
1N/A ** Unfortunately it is not clear whether this routine should
1N/A ** return a copy of a string or just a pointer to a string.
1N/A ** The Cyrus-SASL plugins treat these return values differently, e.g.,
1N/A ** plugins/cram.c free()s authid, plugings/digestmd5.c does not.
1N/A ** The best solution to this problem is to fix Cyrus-SASL, but it
1N/A ** seems there is nobody who creates patches... Hello CMU!?
1N/A ** The second best solution is to have flags that tell this routine
1N/A ** whether to return an malloc()ed copy.
1N/A ** The next best solution is to always return an malloc()ed copy,
1N/A ** and suffer from some memory leak, which is ugly for persistent
1N/A ** queue runners.
1N/A ** For now we go with the last solution...
1N/A ** We can't use rpools (which would avoid this particular problem)
1N/A ** as explained in sasl.c.
1N/A */
1N/A
1N/A switch (id)
1N/A {
1N/A case SASL_CB_USER:
1N/A l = strlen((*sai)[SASL_USER]) + 1;
1N/A s = sm_sasl_malloc(l);
1N/A if (s == NULL)
1N/A {
1N/A if (len != NULL)
1N/A *len = 0;
1N/A *result = NULL;
1N/A return SASL_NOMEM;
1N/A }
1N/A (void) sm_strlcpy(s, (*sai)[SASL_USER], l);
1N/A *result = s;
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "AUTH username '%s'",
1N/A *result);
1N/A if (len != NULL)
1N/A *len = *result != NULL ? strlen(*result) : 0;
1N/A break;
1N/A
1N/A case SASL_CB_AUTHNAME:
1N/A h = (*sai)[SASL_AUTHID];
1N/A# if SASL > 10509
1N/A /* XXX maybe other mechanisms too?! */
1N/A addrealm = (*sai)[SASL_MECH] != NULL &&
1N/A sm_strcasecmp((*sai)[SASL_MECH], "CRAM-MD5") == 0;
1N/A
1N/A /*
1N/A ** Add realm to authentication id unless authid contains
1N/A ** '@' (i.e., a realm) or the default realm is empty.
1N/A */
1N/A
1N/A if (addrealm && h != NULL && strchr(h, '@') == NULL)
1N/A {
1N/A /* has this been done before? */
1N/A if ((*sai)[SASL_ID_REALM] == NULL)
1N/A {
1N/A char *realm;
1N/A
1N/A realm = (*sai)[SASL_DEFREALM];
1N/A
1N/A /* do not add an empty realm */
1N/A if (*realm == '\0')
1N/A {
1N/A authid = h;
1N/A (*sai)[SASL_ID_REALM] = NULL;
1N/A }
1N/A else
1N/A {
1N/A l = strlen(h) + strlen(realm) + 2;
1N/A
1N/A /* should use rpool, but from where? */
1N/A authid = sm_sasl_malloc(l);
1N/A if (authid != NULL)
1N/A {
1N/A (void) sm_snprintf(authid, l,
1N/A "%s@%s",
1N/A h, realm);
1N/A (*sai)[SASL_ID_REALM] = authid;
1N/A }
1N/A else
1N/A {
1N/A authid = h;
1N/A (*sai)[SASL_ID_REALM] = NULL;
1N/A }
1N/A }
1N/A }
1N/A else
1N/A authid = (*sai)[SASL_ID_REALM];
1N/A }
1N/A else
1N/A# endif /* SASL > 10509 */
1N/A authid = h;
1N/A l = strlen(authid) + 1;
1N/A s = sm_sasl_malloc(l);
1N/A if (s == NULL)
1N/A {
1N/A if (len != NULL)
1N/A *len = 0;
1N/A *result = NULL;
1N/A return SASL_NOMEM;
1N/A }
1N/A (void) sm_strlcpy(s, authid, l);
1N/A *result = s;
1N/A if (tTd(95, 5))
1N/A sm_syslog(LOG_DEBUG, NOQID, "AUTH authid '%s'",
1N/A *result);
1N/A if (len != NULL)
1N/A *len = authid ? strlen(authid) : 0;
1N/A break;
1N/A
1N/A case SASL_CB_LANGUAGE:
1N/A *result = NULL;
1N/A if (len != NULL)
1N/A *len = 0;
1N/A break;
1N/A
1N/A default:
1N/A return SASL_BADPARAM;
1N/A }
1N/A return SASL_OK;
1N/A}
1N/A/*
1N/A** GETSECRET -- callback to get password
1N/A**
1N/A** Parameters:
1N/A** conn -- connection information
1N/A** context -- sai
1N/A** id -- what to do
1N/A** psecret -- (pointer to) result
1N/A**
1N/A** Returns:
1N/A** OK/failure values
1N/A*/
1N/A
1N/Astatic int
1N/Agetsecret(conn, context, id, psecret)
1N/A sasl_conn_t *conn;
1N/A SM_UNUSED(void *context);
1N/A int id;
1N/A sasl_secret_t **psecret;
1N/A{
1N/A int len;
1N/A char *authpass;
1N/A SASL_AI_T *sai;
1N/A
1N/A if (conn == NULL || psecret == NULL || id != SASL_CB_PASS)
1N/A return SASL_BADPARAM;
1N/A
1N/A sai = (SASL_AI_T *) context;
1N/A authpass = (*sai)[SASL_PASSWORD];
1N/A len = strlen(authpass);
1N/A *psecret = (sasl_secret_t *) sm_sasl_malloc(sizeof(sasl_secret_t) +
1N/A len + 1);
1N/A if (*psecret == NULL)
1N/A return SASL_FAIL;
1N/A (void) sm_strlcpy((*psecret)->data, authpass, len + 1);
1N/A (*psecret)->len = (unsigned long) len;
1N/A return SASL_OK;
1N/A}
1N/A# endif /* SASL >= 20000 */
1N/A
1N/A/*
1N/A** SAFESASLFILE -- callback for sasl: is file safe?
1N/A**
1N/A** Parameters:
1N/A** context -- pointer to context between invocations (unused)
1N/A** file -- name of file to check
1N/A** type -- type of file to check
1N/A**
1N/A** Returns:
1N/A** SASL_OK -- file can be used
1N/A** SASL_CONTINUE -- don't use file
1N/A** SASL_FAIL -- failure (not used here)
1N/A**
1N/A*/
1N/A
1N/Aint
1N/A#if SASL > 10515
1N/Asafesaslfile(context, file, type)
1N/A#else /* SASL > 10515 */
1N/Asafesaslfile(context, file)
1N/A#endif /* SASL > 10515 */
1N/A void *context;
1N/A# if SASL >= 20000
1N/A const char *file;
1N/A# else /* SASL >= 20000 */
1N/A char *file;
1N/A# endif /* SASL >= 20000 */
1N/A#if SASL > 10515
1N/A# if SASL >= 20000
1N/A sasl_verify_type_t type;
1N/A# else /* SASL >= 20000 */
1N/A int type;
1N/A# endif /* SASL >= 20000 */
1N/A#endif /* SASL > 10515 */
1N/A{
1N/A long sff;
1N/A int r;
1N/A#if SASL <= 10515
1N/A size_t len;
1N/A#endif /* SASL <= 10515 */
1N/A char *p;
1N/A
1N/A if (file == NULL || *file == '\0')
1N/A return SASL_OK;
1N/A sff = SFF_SAFEDIRPATH|SFF_NOWLINK|SFF_NOWWFILES|SFF_ROOTOK;
1N/A#if SASL <= 10515
1N/A if ((p = strrchr(file, '/')) == NULL)
1N/A p = file;
1N/A else
1N/A ++p;
1N/A
1N/A /* everything beside libs and .conf files must not be readable */
1N/A len = strlen(p);
1N/A if ((len <= 3 || strncmp(p, "lib", 3) != 0) &&
1N/A (len <= 5 || strncmp(p + len - 5, ".conf", 5) != 0))
1N/A {
1N/A if (!bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
1N/A sff |= SFF_NORFILES;
1N/A if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
1N/A sff |= SFF_NOGWFILES;
1N/A }
1N/A#else /* SASL <= 10515 */
1N/A /* files containing passwords should be not readable */
1N/A if (type == SASL_VRFY_PASSWD)
1N/A {
1N/A if (bitnset(DBS_GROUPREADABLESASLDBFILE, DontBlameSendmail))
1N/A sff |= SFF_NOWRFILES;
1N/A else
1N/A sff |= SFF_NORFILES;
1N/A if (!bitnset(DBS_GROUPWRITABLESASLDBFILE, DontBlameSendmail))
1N/A sff |= SFF_NOGWFILES;
1N/A }
1N/A#endif /* SASL <= 10515 */
1N/A
1N/A p = (char *) file;
1N/A if ((r = safefile(p, RunAsUid, RunAsGid, RunAsUserName, sff,
1N/A S_IRUSR, NULL)) == 0)
1N/A return SASL_OK;
1N/A if (LogLevel > (r != ENOENT ? 8 : 10))
1N/A sm_syslog(LOG_WARNING, NOQID, "error: safesasl(%s) failed: %s",
1N/A p, sm_errstring(r));
1N/A return SASL_CONTINUE;
1N/A}
1N/A
1N/A/*
1N/A** SASLGETREALM -- return the realm for SASL
1N/A**
1N/A** return the realm for the client
1N/A**
1N/A** Parameters:
1N/A** context -- context shared between invocations
1N/A** availrealms -- list of available realms
1N/A** {realm, realm, ...}
1N/A** result -- pointer to result
1N/A**
1N/A** Returns:
1N/A** failure/success
1N/A*/
1N/A
1N/Astatic int
1N/Asaslgetrealm(context, id, availrealms, result)
1N/A void *context;
1N/A int id;
1N/A const char **availrealms;
1N/A const char **result;
1N/A{
1N/A char *r;
1N/A SASL_AI_T *sai;
1N/A
1N/A sai = (SASL_AI_T *) context;
1N/A if (sai == NULL)
1N/A return SASL_FAIL;
1N/A r = (*sai)[SASL_DEFREALM];
1N/A
1N/A if (LogLevel > 12)
1N/A sm_syslog(LOG_INFO, NOQID,
1N/A "AUTH=client, realm=%s, available realms=%s",
1N/A r == NULL ? "<No Realm>" : r,
1N/A (availrealms == NULL || *availrealms == NULL)
1N/A ? "<No Realms>" : *availrealms);
1N/A
1N/A /* check whether context is in list */
1N/A if (availrealms != NULL && *availrealms != NULL)
1N/A {
1N/A if (iteminlist(context, (char *)(*availrealms + 1), " ,}") ==
1N/A NULL)
1N/A {
1N/A if (LogLevel > 8)
1N/A sm_syslog(LOG_ERR, NOQID,
1N/A "AUTH=client, realm=%s not in list=%s",
1N/A r, *availrealms);
1N/A return SASL_FAIL;
1N/A }
1N/A }
1N/A *result = r;
1N/A return SASL_OK;
1N/A}
1N/A/*
1N/A** ITEMINLIST -- does item appear in list?
1N/A**
1N/A** Check whether item appears in list (which must be separated by a
1N/A** character in delim) as a "word", i.e. it must appear at the begin
1N/A** of the list or after a space, and it must end with a space or the
1N/A** end of the list.
1N/A**
1N/A** Parameters:
1N/A** item -- item to search.
1N/A** list -- list of items.
1N/A** delim -- list of delimiters.
1N/A**
1N/A** Returns:
1N/A** pointer to occurrence (NULL if not found).
1N/A*/
1N/A
1N/Achar *
1N/Aiteminlist(item, list, delim)
1N/A char *item;
1N/A char *list;
1N/A char *delim;
1N/A{
1N/A char *s;
1N/A int len;
1N/A
1N/A if (list == NULL || *list == '\0')
1N/A return NULL;
1N/A if (item == NULL || *item == '\0')
1N/A return NULL;
1N/A s = list;
1N/A len = strlen(item);
1N/A while (s != NULL && *s != '\0')
1N/A {
1N/A if (sm_strncasecmp(s, item, len) == 0 &&
1N/A (s[len] == '\0' || strchr(delim, s[len]) != NULL))
1N/A return s;
1N/A s = strpbrk(s, delim);
1N/A if (s != NULL)
1N/A while (*++s == ' ')
1N/A continue;
1N/A }
1N/A return NULL;
1N/A}
1N/A/*
1N/A** REMOVEMECH -- remove item [rem] from list [list]
1N/A**
1N/A** Parameters:
1N/A** rem -- item to remove
1N/A** list -- list of items
1N/A** rpool -- resource pool from which result is allocated.
1N/A**
1N/A** Returns:
1N/A** pointer to new list (NULL in case of error).
1N/A*/
1N/A
1N/Astatic char *
1N/Aremovemech(rem, list, rpool)
1N/A char *rem;
1N/A char *list;
1N/A SM_RPOOL_T *rpool;
1N/A{
1N/A char *ret;
1N/A char *needle;
1N/A int len;
1N/A
1N/A if (list == NULL)
1N/A return NULL;
1N/A if (rem == NULL || *rem == '\0')
1N/A {
1N/A /* take out what? */
1N/A return NULL;
1N/A }
1N/A
1N/A /* find the item in the list */
1N/A if ((needle = iteminlist(rem, list, " ")) == NULL)
1N/A {
1N/A /* not in there: return original */
1N/A return list;
1N/A }
1N/A
1N/A /* length of string without rem */
1N/A len = strlen(list) - strlen(rem);
1N/A if (len <= 0)
1N/A {
1N/A ret = (char *) sm_rpool_malloc_x(rpool, 1);
1N/A *ret = '\0';
1N/A return ret;
1N/A }
1N/A ret = (char *) sm_rpool_malloc_x(rpool, len);
1N/A memset(ret, '\0', len);
1N/A
1N/A /* copy from start to removed item */
1N/A memcpy(ret, list, needle - list);
1N/A
1N/A /* length of rest of string past removed item */
1N/A len = strlen(needle) - strlen(rem) - 1;
1N/A if (len > 0)
1N/A {
1N/A /* not last item -- copy into string */
1N/A memcpy(ret + (needle - list),
1N/A list + (needle - list) + strlen(rem) + 1,
1N/A len);
1N/A }
1N/A else
1N/A ret[(needle - list) - 1] = '\0';
1N/A return ret;
1N/A}
1N/A/*
1N/A** ATTEMPTAUTH -- try to AUTHenticate using one mechanism
1N/A**
1N/A** Parameters:
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection structure.
1N/A** e -- the envelope (including the sender to specify).
1N/A** sai - sasl authinfo
1N/A**
1N/A** Returns:
1N/A** EX_OK -- authentication was successful.
1N/A** EX_NOPERM -- authentication failed.
1N/A** EX_IOERR -- authentication dialogue failed (I/O problem?).
1N/A** EX_TEMPFAIL -- temporary failure.
1N/A**
1N/A*/
1N/A
1N/Astatic int
1N/Aattemptauth(m, mci, e, sai)
1N/A MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A SASL_AI_T *sai;
1N/A{
1N/A int saslresult, smtpresult;
1N/A# if SASL >= 20000
1N/A sasl_ssf_t ssf;
1N/A const char *auth_id;
1N/A const char *out;
1N/A# else /* SASL >= 20000 */
1N/A sasl_external_properties_t ssf;
1N/A char *out;
1N/A# endif /* SASL >= 20000 */
1N/A unsigned int outlen;
1N/A sasl_interact_t *client_interact = NULL;
1N/A char *mechusing;
1N/A sasl_security_properties_t ssp;
1N/A
1N/A /* MUST NOT be a multiple of 4: bug in some sasl_encode64() versions */
1N/A char in64[MAXOUTLEN + 1];
1N/A#if NETINET || (NETINET6 && SASL >= 20000)
1N/A extern SOCKADDR CurHostAddr;
1N/A#endif /* NETINET || (NETINET6 && SASL >= 20000) */
1N/A
1N/A /* no mechanism selected (yet) */
1N/A (*sai)[SASL_MECH] = NULL;
1N/A
1N/A /* dispose old connection */
1N/A if (mci->mci_conn != NULL)
1N/A sasl_dispose(&(mci->mci_conn));
1N/A
1N/A /* make a new client sasl connection */
1N/A# if SASL >= 20000
1N/A /*
1N/A ** We provide the callbacks again because global callbacks in
1N/A ** sasl_client_init() are ignored if SASL has been initialized
1N/A ** before, for example, by a library such as libnss-ldap.
1N/A */
1N/A
1N/A saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
1N/A : "smtp",
1N/A CurHostName, NULL, NULL, callbacks, 0,
1N/A &mci->mci_conn);
1N/A# else /* SASL >= 20000 */
1N/A saslresult = sasl_client_new(bitnset(M_LMTP, m->m_flags) ? "lmtp"
1N/A : "smtp",
1N/A CurHostName, NULL, 0, &mci->mci_conn);
1N/A# endif /* SASL >= 20000 */
1N/A if (saslresult != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A
1N/A /* set properties */
1N/A (void) memset(&ssp, '\0', sizeof(ssp));
1N/A
1N/A /* XXX should these be options settable via .cf ? */
1N/A ssp.max_ssf = MaxSLBits;
1N/A ssp.maxbufsize = MAXOUTLEN;
1N/A# if 0
1N/A ssp.security_flags = SASL_SEC_NOPLAINTEXT;
1N/A# endif /* 0 */
1N/A saslresult = sasl_setprop(mci->mci_conn, SASL_SEC_PROPS, &ssp);
1N/A if (saslresult != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A
1N/A# if SASL >= 20000
1N/A /* external security strength factor, authentication id */
1N/A ssf = 0;
1N/A auth_id = NULL;
1N/A# if STARTTLS
1N/A out = macvalue(macid("{cert_subject}"), e);
1N/A if (out != NULL && *out != '\0')
1N/A auth_id = out;
1N/A out = macvalue(macid("{cipher_bits}"), e);
1N/A if (out != NULL && *out != '\0')
1N/A ssf = atoi(out);
1N/A# endif /* STARTTLS */
1N/A saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
1N/A if (saslresult != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A saslresult = sasl_setprop(mci->mci_conn, SASL_AUTH_EXTERNAL, auth_id);
1N/A if (saslresult != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A
1N/A# if NETINET || NETINET6
1N/A /* set local/remote ipv4 addresses */
1N/A if (mci->mci_out != NULL && (
1N/A# if NETINET6
1N/A CurHostAddr.sa.sa_family == AF_INET6 ||
1N/A# endif /* NETINET6 */
1N/A CurHostAddr.sa.sa_family == AF_INET))
1N/A {
1N/A SOCKADDR_LEN_T addrsize;
1N/A SOCKADDR saddr_l;
1N/A char localip[60], remoteip[60];
1N/A
1N/A switch (CurHostAddr.sa.sa_family)
1N/A {
1N/A case AF_INET:
1N/A addrsize = sizeof(struct sockaddr_in);
1N/A break;
1N/A# if NETINET6
1N/A case AF_INET6:
1N/A addrsize = sizeof(struct sockaddr_in6);
1N/A break;
1N/A# endif /* NETINET6 */
1N/A default:
1N/A break;
1N/A }
1N/A if (iptostring(&CurHostAddr, addrsize,
1N/A remoteip, sizeof(remoteip)))
1N/A {
1N/A if (sasl_setprop(mci->mci_conn, SASL_IPREMOTEPORT,
1N/A remoteip) != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A }
1N/A addrsize = sizeof(saddr_l);
1N/A if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
1N/A NULL),
1N/A (struct sockaddr *) &saddr_l, &addrsize) == 0)
1N/A {
1N/A if (iptostring(&saddr_l, addrsize,
1N/A localip, sizeof(localip)))
1N/A {
1N/A if (sasl_setprop(mci->mci_conn,
1N/A SASL_IPLOCALPORT,
1N/A localip) != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A }
1N/A }
1N/A }
1N/A# endif /* NETINET || NETINET6 */
1N/A
1N/A /* start client side of sasl */
1N/A saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
1N/A &client_interact,
1N/A &out, &outlen,
1N/A (const char **) &mechusing);
1N/A# else /* SASL >= 20000 */
1N/A /* external security strength factor, authentication id */
1N/A ssf.ssf = 0;
1N/A ssf.auth_id = NULL;
1N/A# if STARTTLS
1N/A out = macvalue(macid("{cert_subject}"), e);
1N/A if (out != NULL && *out != '\0')
1N/A ssf.auth_id = out;
1N/A out = macvalue(macid("{cipher_bits}"), e);
1N/A if (out != NULL && *out != '\0')
1N/A ssf.ssf = atoi(out);
1N/A# endif /* STARTTLS */
1N/A saslresult = sasl_setprop(mci->mci_conn, SASL_SSF_EXTERNAL, &ssf);
1N/A if (saslresult != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A
1N/A# if NETINET
1N/A /* set local/remote ipv4 addresses */
1N/A if (mci->mci_out != NULL && CurHostAddr.sa.sa_family == AF_INET)
1N/A {
1N/A SOCKADDR_LEN_T addrsize;
1N/A struct sockaddr_in saddr_l;
1N/A
1N/A if (sasl_setprop(mci->mci_conn, SASL_IP_REMOTE,
1N/A (struct sockaddr_in *) &CurHostAddr)
1N/A != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A addrsize = sizeof(struct sockaddr_in);
1N/A if (getsockname(sm_io_getinfo(mci->mci_out, SM_IO_WHAT_FD,
1N/A NULL),
1N/A (struct sockaddr *) &saddr_l, &addrsize) == 0)
1N/A {
1N/A if (sasl_setprop(mci->mci_conn, SASL_IP_LOCAL,
1N/A &saddr_l) != SASL_OK)
1N/A return EX_TEMPFAIL;
1N/A }
1N/A }
1N/A# endif /* NETINET */
1N/A
1N/A /* start client side of sasl */
1N/A saslresult = sasl_client_start(mci->mci_conn, mci->mci_saslcap,
1N/A NULL, &client_interact,
1N/A &out, &outlen,
1N/A (const char **) &mechusing);
1N/A# endif /* SASL >= 20000 */
1N/A
1N/A if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
1N/A {
1N/A if (saslresult == SASL_NOMECH && LogLevel > 8)
1N/A {
1N/A sm_syslog(LOG_NOTICE, e->e_id,
1N/A "AUTH=client, available mechanisms do not fulfill requirements");
1N/A }
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A /* just point current mechanism to the data in the sasl library */
1N/A (*sai)[SASL_MECH] = mechusing;
1N/A
1N/A /* send the info across the wire */
1N/A if (out == NULL
1N/A /* login and digest-md5 up to 1.5.28 set out="" */
1N/A || (outlen == 0 &&
1N/A (sm_strcasecmp(mechusing, "LOGIN") == 0 ||
1N/A sm_strcasecmp(mechusing, "DIGEST-MD5") == 0))
1N/A )
1N/A {
1N/A /* no initial response */
1N/A smtpmessage("AUTH %s", m, mci, mechusing);
1N/A }
1N/A else if (outlen == 0)
1N/A {
1N/A /*
1N/A ** zero-length initial response, per RFC 2554 4.:
1N/A ** "Unlike a zero-length client answer to a 334 reply, a zero-
1N/A ** length initial response is sent as a single equals sign"
1N/A */
1N/A
1N/A smtpmessage("AUTH %s =", m, mci, mechusing);
1N/A }
1N/A else
1N/A {
1N/A saslresult = sasl_encode64(out, outlen, in64, sizeof(in64),
1N/A NULL);
1N/A if (saslresult != SASL_OK) /* internal error */
1N/A {
1N/A if (LogLevel > 8)
1N/A sm_syslog(LOG_ERR, e->e_id,
1N/A "encode64 for AUTH failed");
1N/A return EX_TEMPFAIL;
1N/A }
1N/A smtpmessage("AUTH %s %s", m, mci, mechusing, in64);
1N/A }
1N/A# if SASL < 20000
1N/A sm_sasl_free(out); /* XXX only if no rpool is used */
1N/A# endif /* SASL < 20000 */
1N/A
1N/A /* get the reply */
1N/A smtpresult = reply(m, mci, e, TimeOuts.to_auth, getsasldata, NULL,
1N/A XS_AUTH);
1N/A
1N/A for (;;)
1N/A {
1N/A /* check return code from server */
1N/A if (smtpresult == 235)
1N/A {
1N/A macdefine(&mci->mci_macro, A_TEMP, macid("{auth_type}"),
1N/A mechusing);
1N/A return EX_OK;
1N/A }
1N/A if (smtpresult == -1)
1N/A return EX_IOERR;
1N/A if (REPLYTYPE(smtpresult) == 5)
1N/A return EX_NOPERM; /* ugly, but ... */
1N/A if (REPLYTYPE(smtpresult) != 3)
1N/A {
1N/A /* should we fail deliberately, see RFC 2554 4. ? */
1N/A /* smtpmessage("*", m, mci); */
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A saslresult = sasl_client_step(mci->mci_conn,
1N/A mci->mci_sasl_string,
1N/A mci->mci_sasl_string_len,
1N/A &client_interact,
1N/A &out, &outlen);
1N/A
1N/A if (saslresult != SASL_OK && saslresult != SASL_CONTINUE)
1N/A {
1N/A if (tTd(95, 5))
1N/A sm_dprintf("AUTH FAIL=%s (%d)\n",
1N/A sasl_errstring(saslresult, NULL, NULL),
1N/A saslresult);
1N/A
1N/A /* fail deliberately, see RFC 2554 4. */
1N/A smtpmessage("*", m, mci);
1N/A
1N/A /*
1N/A ** but we should only fail for this authentication
1N/A ** mechanism; how to do that?
1N/A */
1N/A
1N/A smtpresult = reply(m, mci, e, TimeOuts.to_auth,
1N/A getsasldata, NULL, XS_AUTH);
1N/A return EX_NOPERM;
1N/A }
1N/A
1N/A if (outlen > 0)
1N/A {
1N/A saslresult = sasl_encode64(out, outlen, in64,
1N/A sizeof(in64), NULL);
1N/A if (saslresult != SASL_OK)
1N/A {
1N/A /* give an error reply to the other side! */
1N/A smtpmessage("*", m, mci);
1N/A return EX_TEMPFAIL;
1N/A }
1N/A }
1N/A else
1N/A in64[0] = '\0';
1N/A# if SASL < 20000
1N/A sm_sasl_free(out); /* XXX only if no rpool is used */
1N/A# endif /* SASL < 20000 */
1N/A smtpmessage("%s", m, mci, in64);
1N/A smtpresult = reply(m, mci, e, TimeOuts.to_auth,
1N/A getsasldata, NULL, XS_AUTH);
1N/A }
1N/A /* NOTREACHED */
1N/A}
1N/A/*
1N/A** SMTPAUTH -- try to AUTHenticate
1N/A**
1N/A** This will try mechanisms in the order the sasl library decided until:
1N/A** - there are no more mechanisms
1N/A** - a mechanism succeeds
1N/A** - the sasl library fails initializing
1N/A**
1N/A** Parameters:
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection info.
1N/A** e -- the envelope.
1N/A**
1N/A** Returns:
1N/A** EX_OK -- authentication was successful
1N/A** EX_UNAVAILABLE -- authentication not possible, e.g.,
1N/A** no data available.
1N/A** EX_NOPERM -- authentication failed.
1N/A** EX_TEMPFAIL -- temporary failure.
1N/A**
1N/A** Notice: AuthInfo is used for all connections, hence we must
1N/A** return EX_TEMPFAIL only if we really want to retry, i.e.,
1N/A** iff getauth() tempfailed or getauth() was used and
1N/A** authentication tempfailed.
1N/A*/
1N/A
1N/Aint
1N/Asmtpauth(m, mci, e)
1N/A MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A int result;
1N/A int i;
1N/A bool usedgetauth;
1N/A
1N/A mci->mci_sasl_auth = false;
1N/A for (i = 0; i < SASL_MECH ; i++)
1N/A mci->mci_sai[i] = NULL;
1N/A
1N/A result = getauth(mci, e, &(mci->mci_sai));
1N/A if (result == EX_TEMPFAIL)
1N/A return result;
1N/A usedgetauth = true;
1N/A
1N/A /* no data available: don't try to authenticate */
1N/A if (result == EX_OK && mci->mci_sai[SASL_AUTHID] == NULL)
1N/A return result;
1N/A if (result != EX_OK)
1N/A {
1N/A if (SASLInfo == NULL)
1N/A return EX_UNAVAILABLE;
1N/A
1N/A /* read authinfo from file */
1N/A result = readauth(SASLInfo, true, &(mci->mci_sai),
1N/A mci->mci_rpool);
1N/A if (result != EX_OK)
1N/A return result;
1N/A usedgetauth = false;
1N/A }
1N/A
1N/A /* check whether sufficient data is available */
1N/A if (mci->mci_sai[SASL_PASSWORD] == NULL ||
1N/A *(mci->mci_sai)[SASL_PASSWORD] == '\0')
1N/A return EX_UNAVAILABLE;
1N/A if ((mci->mci_sai[SASL_AUTHID] == NULL ||
1N/A *(mci->mci_sai)[SASL_AUTHID] == '\0') &&
1N/A (mci->mci_sai[SASL_USER] == NULL ||
1N/A *(mci->mci_sai)[SASL_USER] == '\0'))
1N/A return EX_UNAVAILABLE;
1N/A
1N/A /* set the context for the callback function to sai */
1N/A# if SASL >= 20000
1N/A callbacks[CB_PASS_IDX].context = (void *) mci;
1N/A# else /* SASL >= 20000 */
1N/A callbacks[CB_PASS_IDX].context = (void *) &mci->mci_sai;
1N/A# endif /* SASL >= 20000 */
1N/A callbacks[CB_USER_IDX].context = (void *) &mci->mci_sai;
1N/A callbacks[CB_AUTHNAME_IDX].context = (void *) &mci->mci_sai;
1N/A callbacks[CB_GETREALM_IDX].context = (void *) &mci->mci_sai;
1N/A#if 0
1N/A callbacks[CB_SAFESASL_IDX].context = (void *) &mci->mci_sai;
1N/A#endif /* 0 */
1N/A
1N/A /* set default value for realm */
1N/A if ((mci->mci_sai)[SASL_DEFREALM] == NULL)
1N/A (mci->mci_sai)[SASL_DEFREALM] = sm_rpool_strdup_x(e->e_rpool,
1N/A macvalue('j', CurEnv));
1N/A
1N/A /* set default value for list of mechanism to use */
1N/A if ((mci->mci_sai)[SASL_MECHLIST] == NULL ||
1N/A *(mci->mci_sai)[SASL_MECHLIST] == '\0')
1N/A (mci->mci_sai)[SASL_MECHLIST] = AuthMechanisms;
1N/A
1N/A /* create list of mechanisms to try */
1N/A mci->mci_saslcap = intersect((mci->mci_sai)[SASL_MECHLIST],
1N/A mci->mci_saslcap, mci->mci_rpool);
1N/A
1N/A /* initialize sasl client library */
1N/A result = init_sasl_client();
1N/A if (result != SASL_OK)
1N/A return usedgetauth ? EX_TEMPFAIL : EX_UNAVAILABLE;
1N/A do
1N/A {
1N/A result = attemptauth(m, mci, e, &(mci->mci_sai));
1N/A if (result == EX_OK)
1N/A mci->mci_sasl_auth = true;
1N/A else if (result == EX_TEMPFAIL || result == EX_NOPERM)
1N/A {
1N/A mci->mci_saslcap = removemech((mci->mci_sai)[SASL_MECH],
1N/A mci->mci_saslcap,
1N/A mci->mci_rpool);
1N/A if (mci->mci_saslcap == NULL ||
1N/A *(mci->mci_saslcap) == '\0')
1N/A return usedgetauth ? result
1N/A : EX_UNAVAILABLE;
1N/A }
1N/A else
1N/A return result;
1N/A } while (result != EX_OK);
1N/A return result;
1N/A}
1N/A#endif /* SASL */
1N/A
1N/A/*
1N/A** SMTPMAILFROM -- send MAIL command
1N/A**
1N/A** Parameters:
1N/A** m -- the mailer.
1N/A** mci -- the mailer connection structure.
1N/A** e -- the envelope (including the sender to specify).
1N/A*/
1N/A
1N/Aint
1N/Asmtpmailfrom(m, mci, e)
1N/A MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A int r;
1N/A char *bufp;
1N/A char *bodytype;
1N/A char *enhsc;
1N/A char buf[MAXNAME + 1];
1N/A char optbuf[MAXLINE];
1N/A
1N/A if (tTd(18, 2))
1N/A sm_dprintf("smtpmailfrom: CurHost=%s\n", CurHostName);
1N/A enhsc = NULL;
1N/A
1N/A /*
1N/A ** Check if connection is gone, if so
1N/A ** it's a tempfail and we use mci_errno
1N/A ** for the reason.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A /* set up appropriate options to include */
1N/A if (bitset(MCIF_SIZE, mci->mci_flags) && e->e_msgsize > 0)
1N/A {
1N/A (void) sm_snprintf(optbuf, sizeof(optbuf), " SIZE=%ld",
1N/A e->e_msgsize);
1N/A bufp = &optbuf[strlen(optbuf)];
1N/A }
1N/A else
1N/A {
1N/A optbuf[0] = '\0';
1N/A bufp = optbuf;
1N/A }
1N/A
1N/A bodytype = e->e_bodytype;
1N/A if (bitset(MCIF_8BITMIME, mci->mci_flags))
1N/A {
1N/A if (bodytype == NULL &&
1N/A bitset(MM_MIME8BIT, MimeMode) &&
1N/A bitset(EF_HAS8BIT, e->e_flags) &&
1N/A !bitset(EF_DONT_MIME, e->e_flags) &&
1N/A !bitnset(M_8BITS, m->m_flags))
1N/A bodytype = "8BITMIME";
1N/A if (bodytype != NULL &&
1N/A SPACELEFT(optbuf, bufp) > strlen(bodytype) + 7)
1N/A {
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " BODY=%s", bodytype);
1N/A bufp += strlen(bufp);
1N/A }
1N/A }
1N/A else if (bitnset(M_8BITS, m->m_flags) ||
1N/A !bitset(EF_HAS8BIT, e->e_flags) ||
1N/A bitset(MCIF_8BITOK, mci->mci_flags))
1N/A {
1N/A /* EMPTY */
1N/A /* just pass it through */
1N/A }
1N/A#if MIME8TO7
1N/A else if (bitset(MM_CVTMIME, MimeMode) &&
1N/A !bitset(EF_DONT_MIME, e->e_flags) &&
1N/A (!bitset(MM_PASS8BIT, MimeMode) ||
1N/A bitset(EF_IS_MIME, e->e_flags)))
1N/A {
1N/A /* must convert from 8bit MIME format to 7bit encoded */
1N/A mci->mci_flags |= MCIF_CVT8TO7;
1N/A }
1N/A#endif /* MIME8TO7 */
1N/A else if (!bitset(MM_PASS8BIT, MimeMode))
1N/A {
1N/A /* cannot just send a 8-bit version */
1N/A extern char MsgBuf[];
1N/A
1N/A usrerrenh("5.6.3", "%s does not support 8BITMIME", CurHostName);
1N/A mci_setstat(mci, EX_NOTSTICKY, "5.6.3", MsgBuf);
1N/A return EX_DATAERR;
1N/A }
1N/A
1N/A if (bitset(MCIF_DSN, mci->mci_flags))
1N/A {
1N/A if (e->e_envid != NULL &&
1N/A SPACELEFT(optbuf, bufp) > strlen(e->e_envid) + 7)
1N/A {
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " ENVID=%s", e->e_envid);
1N/A bufp += strlen(bufp);
1N/A }
1N/A
1N/A /* RET= parameter */
1N/A if (bitset(EF_RET_PARAM, e->e_flags) &&
1N/A SPACELEFT(optbuf, bufp) > 9)
1N/A {
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " RET=%s",
1N/A bitset(EF_NO_BODY_RETN, e->e_flags) ?
1N/A "HDRS" : "FULL");
1N/A bufp += strlen(bufp);
1N/A }
1N/A }
1N/A
1N/A if (bitset(MCIF_AUTH, mci->mci_flags) && e->e_auth_param != NULL &&
1N/A SPACELEFT(optbuf, bufp) > strlen(e->e_auth_param) + 7
1N/A#if SASL
1N/A && (!bitset(SASL_AUTH_AUTH, SASLOpts) || mci->mci_sasl_auth)
1N/A#endif /* SASL */
1N/A )
1N/A {
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " AUTH=%s", e->e_auth_param);
1N/A bufp += strlen(bufp);
1N/A }
1N/A
1N/A /*
1N/A ** 17 is the max length required, we could use log() to compute
1N/A ** the exact length (and check IS_DLVR_TRACE())
1N/A */
1N/A
1N/A if (bitset(MCIF_DLVR_BY, mci->mci_flags) &&
1N/A IS_DLVR_BY(e) && SPACELEFT(optbuf, bufp) > 17)
1N/A {
1N/A long dby;
1N/A
1N/A /*
1N/A ** Avoid problems with delays (for R) since the check
1N/A ** in deliver() whether min-deliver-time is sufficient.
1N/A ** Alternatively we could pass the computed time to this
1N/A ** function.
1N/A */
1N/A
1N/A dby = e->e_deliver_by - (curtime() - e->e_ctime);
1N/A if (dby <= 0 && IS_DLVR_RETURN(e))
1N/A dby = mci->mci_min_by <= 0 ? 1 : mci->mci_min_by;
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " BY=%ld;%c%s",
1N/A dby,
1N/A IS_DLVR_RETURN(e) ? 'R' : 'N',
1N/A IS_DLVR_TRACE(e) ? "T" : "");
1N/A bufp += strlen(bufp);
1N/A }
1N/A
1N/A /*
1N/A ** Send the MAIL command.
1N/A ** Designates the sender.
1N/A */
1N/A
1N/A mci->mci_state = MCIS_MAIL;
1N/A
1N/A if (bitset(EF_RESPONSE, e->e_flags) &&
1N/A !bitnset(M_NO_NULL_FROM, m->m_flags))
1N/A buf[0] = '\0';
1N/A else
1N/A expand("\201g", buf, sizeof(buf), e);
1N/A if (buf[0] == '<')
1N/A {
1N/A /* strip off <angle brackets> (put back on below) */
1N/A bufp = &buf[strlen(buf) - 1];
1N/A if (*bufp == '>')
1N/A *bufp = '\0';
1N/A bufp = &buf[1];
1N/A }
1N/A else
1N/A bufp = buf;
1N/A if (bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) ||
1N/A !bitnset(M_FROMPATH, m->m_flags))
1N/A {
1N/A smtpmessage("MAIL From:<%s>%s", m, mci, bufp, optbuf);
1N/A }
1N/A else
1N/A {
1N/A smtpmessage("MAIL From:<@%s%c%s>%s", m, mci, MyHostName,
1N/A *bufp == '@' ? ',' : ':', bufp, optbuf);
1N/A }
1N/A SmtpPhase = mci->mci_phase = "client MAIL";
1N/A sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
1N/A CurHostName, mci->mci_phase);
1N/A r = reply(m, mci, e, TimeOuts.to_mail, NULL, &enhsc, XS_MAIL);
1N/A if (r < 0)
1N/A {
1N/A /* communications failure */
1N/A mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
1N/A return EX_TEMPFAIL;
1N/A }
1N/A else if (r == SMTPCLOSING)
1N/A {
1N/A /* service shutting down: handled by reply() */
1N/A return EX_TEMPFAIL;
1N/A }
1N/A else if (REPLYTYPE(r) == 4)
1N/A {
1N/A mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, smtptodsn(r)),
1N/A SmtpReplyBuffer);
1N/A return EX_TEMPFAIL;
1N/A }
1N/A else if (REPLYTYPE(r) == 2)
1N/A {
1N/A return EX_OK;
1N/A }
1N/A else if (r == 501)
1N/A {
1N/A /* syntax error in arguments */
1N/A mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.5.2"),
1N/A SmtpReplyBuffer);
1N/A return EX_DATAERR;
1N/A }
1N/A else if (r == 553)
1N/A {
1N/A /* mailbox name not allowed */
1N/A mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.1.3"),
1N/A SmtpReplyBuffer);
1N/A return EX_DATAERR;
1N/A }
1N/A else if (r == 552)
1N/A {
1N/A /* exceeded storage allocation */
1N/A mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.3.4"),
1N/A SmtpReplyBuffer);
1N/A if (bitset(MCIF_SIZE, mci->mci_flags))
1N/A e->e_flags |= EF_NO_BODY_RETN;
1N/A return EX_UNAVAILABLE;
1N/A }
1N/A else if (REPLYTYPE(r) == 5)
1N/A {
1N/A /* unknown error */
1N/A mci_setstat(mci, EX_NOTSTICKY, ENHSCN(enhsc, "5.0.0"),
1N/A SmtpReplyBuffer);
1N/A return EX_UNAVAILABLE;
1N/A }
1N/A
1N/A if (LogLevel > 1)
1N/A {
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP MAIL protocol error: %s",
1N/A CurHostName,
1N/A shortenstring(SmtpReplyBuffer, 403));
1N/A }
1N/A
1N/A /* protocol error -- close up */
1N/A mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
1N/A SmtpReplyBuffer);
1N/A smtpquit(m, mci, e);
1N/A return EX_PROTOCOL;
1N/A}
1N/A/*
1N/A** SMTPRCPT -- designate recipient.
1N/A**
1N/A** Parameters:
1N/A** to -- address of recipient.
1N/A** m -- the mailer we are sending to.
1N/A** mci -- the connection info for this transaction.
1N/A** e -- the envelope for this transaction.
1N/A**
1N/A** Returns:
1N/A** exit status corresponding to recipient status.
1N/A**
1N/A** Side Effects:
1N/A** Sends the mail via SMTP.
1N/A*/
1N/A
1N/Aint
1N/Asmtprcpt(to, m, mci, e, ctladdr, xstart)
1N/A ADDRESS *to;
1N/A register MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A ADDRESS *ctladdr;
1N/A time_t xstart;
1N/A{
1N/A char *bufp;
1N/A char optbuf[MAXLINE];
1N/A
1N/A#if PIPELINING
1N/A /*
1N/A ** If there is status waiting from the other end, read it.
1N/A ** This should normally happen because of SMTP pipelining.
1N/A */
1N/A
1N/A while (mci->mci_nextaddr != NULL &&
1N/A sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
1N/A {
1N/A int r;
1N/A
1N/A r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
1N/A if (r != EX_OK)
1N/A {
1N/A markfailure(e, mci->mci_nextaddr, mci, r, false);
1N/A giveresponse(r, mci->mci_nextaddr->q_status, m, mci,
1N/A ctladdr, xstart, e, to);
1N/A }
1N/A mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
1N/A }
1N/A#endif /* PIPELINING */
1N/A
1N/A /*
1N/A ** Check if connection is gone, if so
1N/A ** it's a tempfail and we use mci_errno
1N/A ** for the reason.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A optbuf[0] = '\0';
1N/A bufp = optbuf;
1N/A
1N/A /*
1N/A ** Warning: in the following it is assumed that the free space
1N/A ** in bufp is sizeof(optbuf)
1N/A */
1N/A
1N/A if (bitset(MCIF_DSN, mci->mci_flags))
1N/A {
1N/A if (IS_DLVR_NOTIFY(e) &&
1N/A !bitset(MCIF_DLVR_BY, mci->mci_flags))
1N/A {
1N/A /* RFC 2852: 4.1.4.2 */
1N/A if (!bitset(QHASNOTIFY, to->q_flags))
1N/A to->q_flags |= QPINGONFAILURE|QPINGONDELAY|QHASNOTIFY;
1N/A else if (bitset(QPINGONSUCCESS, to->q_flags) ||
1N/A bitset(QPINGONFAILURE, to->q_flags) ||
1N/A bitset(QPINGONDELAY, to->q_flags))
1N/A to->q_flags |= QPINGONDELAY;
1N/A }
1N/A
1N/A /* NOTIFY= parameter */
1N/A if (bitset(QHASNOTIFY, to->q_flags) &&
1N/A bitset(QPRIMARY, to->q_flags) &&
1N/A !bitnset(M_LOCALMAILER, m->m_flags))
1N/A {
1N/A bool firstone = true;
1N/A
1N/A (void) sm_strlcat(bufp, " NOTIFY=", sizeof(optbuf));
1N/A if (bitset(QPINGONSUCCESS, to->q_flags))
1N/A {
1N/A (void) sm_strlcat(bufp, "SUCCESS", sizeof(optbuf));
1N/A firstone = false;
1N/A }
1N/A if (bitset(QPINGONFAILURE, to->q_flags))
1N/A {
1N/A if (!firstone)
1N/A (void) sm_strlcat(bufp, ",",
1N/A sizeof(optbuf));
1N/A (void) sm_strlcat(bufp, "FAILURE", sizeof(optbuf));
1N/A firstone = false;
1N/A }
1N/A if (bitset(QPINGONDELAY, to->q_flags))
1N/A {
1N/A if (!firstone)
1N/A (void) sm_strlcat(bufp, ",",
1N/A sizeof(optbuf));
1N/A (void) sm_strlcat(bufp, "DELAY", sizeof(optbuf));
1N/A firstone = false;
1N/A }
1N/A if (firstone)
1N/A (void) sm_strlcat(bufp, "NEVER", sizeof(optbuf));
1N/A bufp += strlen(bufp);
1N/A }
1N/A
1N/A /* ORCPT= parameter */
1N/A if (to->q_orcpt != NULL &&
1N/A SPACELEFT(optbuf, bufp) > strlen(to->q_orcpt) + 7)
1N/A {
1N/A (void) sm_snprintf(bufp, SPACELEFT(optbuf, bufp),
1N/A " ORCPT=%s", to->q_orcpt);
1N/A bufp += strlen(bufp);
1N/A }
1N/A }
1N/A
1N/A smtpmessage("RCPT To:<%s>%s", m, mci, to->q_user, optbuf);
1N/A mci->mci_state = MCIS_RCPT;
1N/A
1N/A SmtpPhase = mci->mci_phase = "client RCPT";
1N/A sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
1N/A CurHostName, mci->mci_phase);
1N/A
1N/A#if PIPELINING
1N/A /*
1N/A ** If running SMTP pipelining, we will pick up status later
1N/A */
1N/A
1N/A if (bitset(MCIF_PIPELINED, mci->mci_flags))
1N/A return EX_OK;
1N/A#endif /* PIPELINING */
1N/A
1N/A return smtprcptstat(to, m, mci, e);
1N/A}
1N/A/*
1N/A** SMTPRCPTSTAT -- get recipient status
1N/A**
1N/A** This is only called during SMTP pipelining
1N/A**
1N/A** Parameters:
1N/A** to -- address of recipient.
1N/A** m -- mailer being sent to.
1N/A** mci -- the mailer connection information.
1N/A** e -- the envelope for this message.
1N/A**
1N/A** Returns:
1N/A** EX_* -- protocol status
1N/A*/
1N/A
1N/Astatic int
1N/Asmtprcptstat(to, m, mci, e)
1N/A ADDRESS *to;
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A register ENVELOPE *e;
1N/A{
1N/A int r;
1N/A int save_errno;
1N/A char *enhsc;
1N/A
1N/A /*
1N/A ** Check if connection is gone, if so
1N/A ** it's a tempfail and we use mci_errno
1N/A ** for the reason.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A enhsc = NULL;
1N/A r = reply(m, mci, e, TimeOuts.to_rcpt, NULL, &enhsc, XS_RCPT);
1N/A save_errno = errno;
1N/A to->q_rstatus = sm_rpool_strdup_x(e->e_rpool, SmtpReplyBuffer);
1N/A to->q_status = ENHSCN_RPOOL(enhsc, smtptodsn(r), e->e_rpool);
1N/A if (!bitnset(M_LMTP, m->m_flags))
1N/A to->q_statmta = mci->mci_host;
1N/A if (r < 0 || REPLYTYPE(r) == 4)
1N/A {
1N/A mci->mci_retryrcpt = true;
1N/A errno = save_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A else if (REPLYTYPE(r) == 2)
1N/A {
1N/A char *t;
1N/A
1N/A if ((t = mci->mci_tolist) != NULL)
1N/A {
1N/A char *p;
1N/A
1N/A *t++ = ',';
1N/A for (p = to->q_paddr; *p != '\0'; *t++ = *p++)
1N/A continue;
1N/A *t = '\0';
1N/A mci->mci_tolist = t;
1N/A }
1N/A#if PIPELINING
1N/A mci->mci_okrcpts++;
1N/A#endif /* PIPELINING */
1N/A return EX_OK;
1N/A }
1N/A else if (r == 550)
1N/A {
1N/A to->q_status = ENHSCN_RPOOL(enhsc, "5.1.1", e->e_rpool);
1N/A return EX_NOUSER;
1N/A }
1N/A else if (r == 551)
1N/A {
1N/A to->q_status = ENHSCN_RPOOL(enhsc, "5.1.6", e->e_rpool);
1N/A return EX_NOUSER;
1N/A }
1N/A else if (r == 553)
1N/A {
1N/A to->q_status = ENHSCN_RPOOL(enhsc, "5.1.3", e->e_rpool);
1N/A return EX_NOUSER;
1N/A }
1N/A else if (REPLYTYPE(r) == 5)
1N/A {
1N/A return EX_UNAVAILABLE;
1N/A }
1N/A
1N/A if (LogLevel > 1)
1N/A {
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP RCPT protocol error: %s",
1N/A CurHostName,
1N/A shortenstring(SmtpReplyBuffer, 403));
1N/A }
1N/A
1N/A mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
1N/A SmtpReplyBuffer);
1N/A return EX_PROTOCOL;
1N/A}
1N/A/*
1N/A** SMTPDATA -- send the data and clean up the transaction.
1N/A**
1N/A** Parameters:
1N/A** m -- mailer being sent to.
1N/A** mci -- the mailer connection information.
1N/A** e -- the envelope for this message.
1N/A**
1N/A** Returns:
1N/A** exit status corresponding to DATA command.
1N/A*/
1N/A
1N/Aint
1N/Asmtpdata(m, mci, e, ctladdr, xstart)
1N/A MAILER *m;
1N/A register MCI *mci;
1N/A register ENVELOPE *e;
1N/A ADDRESS *ctladdr;
1N/A time_t xstart;
1N/A{
1N/A register int r;
1N/A int rstat;
1N/A int xstat;
1N/A int timeout;
1N/A char *enhsc;
1N/A
1N/A /*
1N/A ** Check if connection is gone, if so
1N/A ** it's a tempfail and we use mci_errno
1N/A ** for the reason.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A
1N/A enhsc = NULL;
1N/A
1N/A /*
1N/A ** Send the data.
1N/A ** First send the command and check that it is ok.
1N/A ** Then send the data (if there are valid recipients).
1N/A ** Follow it up with a dot to terminate.
1N/A ** Finally get the results of the transaction.
1N/A */
1N/A
1N/A /* send the command and check ok to proceed */
1N/A smtpmessage("DATA", m, mci);
1N/A
1N/A#if PIPELINING
1N/A if (mci->mci_nextaddr != NULL)
1N/A {
1N/A char *oldto = e->e_to;
1N/A
1N/A /* pick up any pending RCPT responses for SMTP pipelining */
1N/A while (mci->mci_nextaddr != NULL)
1N/A {
1N/A int r;
1N/A
1N/A e->e_to = mci->mci_nextaddr->q_paddr;
1N/A r = smtprcptstat(mci->mci_nextaddr, m, mci, e);
1N/A if (r != EX_OK)
1N/A {
1N/A markfailure(e, mci->mci_nextaddr, mci, r,
1N/A false);
1N/A giveresponse(r, mci->mci_nextaddr->q_status, m,
1N/A mci, ctladdr, xstart, e,
1N/A mci->mci_nextaddr);
1N/A if (r == EX_TEMPFAIL)
1N/A mci->mci_nextaddr->q_state = QS_RETRY;
1N/A }
1N/A mci->mci_nextaddr = mci->mci_nextaddr->q_pchain;
1N/A }
1N/A e->e_to = oldto;
1N/A
1N/A /*
1N/A ** Connection might be closed in response to a RCPT command,
1N/A ** i.e., the server responded with 421. In that case (at
1N/A ** least) one RCPT has a temporary failure, hence we don't
1N/A ** need to check mci_okrcpts (as it is done below) to figure
1N/A ** out which error to return.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A }
1N/A#endif /* PIPELINING */
1N/A
1N/A /* now proceed with DATA phase */
1N/A SmtpPhase = mci->mci_phase = "client DATA 354";
1N/A mci->mci_state = MCIS_DATA;
1N/A sm_setproctitle(true, e, "%s %s: %s",
1N/A qid_printname(e), CurHostName, mci->mci_phase);
1N/A r = reply(m, mci, e, TimeOuts.to_datainit, NULL, &enhsc, XS_DATA);
1N/A if (r < 0 || REPLYTYPE(r) == 4)
1N/A {
1N/A if (r >= 0)
1N/A smtpquit(m, mci, e);
1N/A errno = mci->mci_errno;
1N/A return EX_TEMPFAIL;
1N/A }
1N/A else if (REPLYTYPE(r) == 5)
1N/A {
1N/A smtprset(m, mci, e);
1N/A#if PIPELINING
1N/A if (mci->mci_okrcpts <= 0)
1N/A return mci->mci_retryrcpt ? EX_TEMPFAIL
1N/A : EX_UNAVAILABLE;
1N/A#endif /* PIPELINING */
1N/A return EX_UNAVAILABLE;
1N/A }
1N/A else if (REPLYTYPE(r) != 3)
1N/A {
1N/A if (LogLevel > 1)
1N/A {
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP DATA-1 protocol error: %s",
1N/A CurHostName,
1N/A shortenstring(SmtpReplyBuffer, 403));
1N/A }
1N/A smtprset(m, mci, e);
1N/A mci_setstat(mci, EX_PROTOCOL, ENHSCN(enhsc, "5.5.1"),
1N/A SmtpReplyBuffer);
1N/A#if PIPELINING
1N/A if (mci->mci_okrcpts <= 0)
1N/A return mci->mci_retryrcpt ? EX_TEMPFAIL
1N/A : EX_PROTOCOL;
1N/A#endif /* PIPELINING */
1N/A return EX_PROTOCOL;
1N/A }
1N/A
1N/A#if PIPELINING
1N/A if (mci->mci_okrcpts > 0)
1N/A {
1N/A#endif /* PIPELINING */
1N/A
1N/A /*
1N/A ** Set timeout around data writes. Make it at least large
1N/A ** enough for DNS timeouts on all recipients plus some fudge
1N/A ** factor. The main thing is that it should not be infinite.
1N/A */
1N/A
1N/A if (tTd(18, 101))
1N/A {
1N/A /* simulate a DATA timeout */
1N/A timeout = 10;
1N/A }
1N/A else
1N/A timeout = DATA_PROGRESS_TIMEOUT * 1000;
1N/A sm_io_setinfo(mci->mci_out, SM_IO_WHAT_TIMEOUT, &timeout);
1N/A
1N/A
1N/A /*
1N/A ** Output the actual message.
1N/A */
1N/A
1N/A if (!(*e->e_puthdr)(mci, e->e_header, e, M87F_OUTER))
1N/A goto writeerr;
1N/A
1N/A if (tTd(18, 101))
1N/A {
1N/A /* simulate a DATA timeout */
1N/A (void) sleep(2);
1N/A }
1N/A
1N/A if (!(*e->e_putbody)(mci, e, NULL))
1N/A goto writeerr;
1N/A
1N/A /*
1N/A ** Cleanup after sending message.
1N/A */
1N/A
1N/A
1N/A#if PIPELINING
1N/A }
1N/A#endif /* PIPELINING */
1N/A
1N/A#if _FFR_CATCH_BROKEN_MTAS
1N/A if (sm_io_getinfo(mci->mci_in, SM_IO_IS_READABLE, NULL) > 0)
1N/A {
1N/A /* terminate the message */
1N/A (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, ".%s",
1N/A m->m_eol);
1N/A if (TrafficLogFile != NULL)
1N/A (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1N/A "%05d >>> .\n", (int) CurrentPid);
1N/A if (Verbose)
1N/A nmessage(">>> .");
1N/A
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
1N/A CurHostName);
1N/A mci->mci_errno = EIO;
1N/A mci->mci_state = MCIS_ERROR;
1N/A mci_setstat(mci, EX_PROTOCOL, "5.5.0", NULL);
1N/A smtpquit(m, mci, e);
1N/A return EX_PROTOCOL;
1N/A }
1N/A#endif /* _FFR_CATCH_BROKEN_MTAS */
1N/A
1N/A if (sm_io_error(mci->mci_out))
1N/A {
1N/A /* error during processing -- don't send the dot */
1N/A mci->mci_errno = EIO;
1N/A mci->mci_state = MCIS_ERROR;
1N/A mci_setstat(mci, EX_IOERR, "4.4.2", NULL);
1N/A smtpquit(m, mci, e);
1N/A return EX_IOERR;
1N/A }
1N/A
1N/A /* terminate the message */
1N/A if (sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s.%s",
1N/A bitset(MCIF_INLONGLINE, mci->mci_flags) ? m->m_eol : "",
1N/A m->m_eol) == SM_IO_EOF)
1N/A goto writeerr;
1N/A if (TrafficLogFile != NULL)
1N/A (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1N/A "%05d >>> .\n", (int) CurrentPid);
1N/A if (Verbose)
1N/A nmessage(">>> .");
1N/A
1N/A /* check for the results of the transaction */
1N/A SmtpPhase = mci->mci_phase = "client DATA status";
1N/A sm_setproctitle(true, e, "%s %s: %s", qid_printname(e),
1N/A CurHostName, mci->mci_phase);
1N/A if (bitnset(M_LMTP, m->m_flags))
1N/A return EX_OK;
1N/A r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_EOM);
1N/A if (r < 0)
1N/A return EX_TEMPFAIL;
1N/A if (mci->mci_state == MCIS_DATA)
1N/A mci->mci_state = MCIS_OPEN;
1N/A xstat = EX_NOTSTICKY;
1N/A if (r == 452)
1N/A rstat = EX_TEMPFAIL;
1N/A else if (REPLYTYPE(r) == 4)
1N/A rstat = xstat = EX_TEMPFAIL;
1N/A else if (REPLYTYPE(r) == 2)
1N/A rstat = xstat = EX_OK;
1N/A else if (REPLYCLASS(r) != 5)
1N/A rstat = xstat = EX_PROTOCOL;
1N/A else if (REPLYTYPE(r) == 5)
1N/A rstat = EX_UNAVAILABLE;
1N/A else
1N/A rstat = EX_PROTOCOL;
1N/A mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)),
1N/A SmtpReplyBuffer);
1N/A if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
1N/A (r = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
1N/A r += 5;
1N/A else
1N/A r = 4;
1N/A e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[r]);
1N/A SmtpPhase = mci->mci_phase = "idle";
1N/A sm_setproctitle(true, e, "%s: %s", CurHostName, mci->mci_phase);
1N/A if (rstat != EX_PROTOCOL)
1N/A return rstat;
1N/A if (LogLevel > 1)
1N/A {
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP DATA-2 protocol error: %s",
1N/A CurHostName,
1N/A shortenstring(SmtpReplyBuffer, 403));
1N/A }
1N/A return rstat;
1N/A
1N/A writeerr:
1N/A mci->mci_errno = errno;
1N/A mci->mci_state = MCIS_ERROR;
1N/A mci_setstat(mci, EX_TEMPFAIL, "4.4.2", NULL);
1N/A
1N/A /*
1N/A ** If putbody() couldn't finish due to a timeout,
1N/A ** rewind it here in the timeout handler. See
1N/A ** comments at the end of putbody() for reasoning.
1N/A */
1N/A
1N/A if (e->e_dfp != NULL)
1N/A (void) bfrewind(e->e_dfp);
1N/A
1N/A errno = mci->mci_errno;
1N/A syserr("451 4.4.1 timeout writing message to %s", CurHostName);
1N/A smtpquit(m, mci, e);
1N/A return EX_TEMPFAIL;
1N/A}
1N/A
1N/A/*
1N/A** SMTPGETSTAT -- get status code from DATA in LMTP
1N/A**
1N/A** Parameters:
1N/A** m -- the mailer to which we are sending the message.
1N/A** mci -- the mailer connection structure.
1N/A** e -- the current envelope.
1N/A**
1N/A** Returns:
1N/A** The exit status corresponding to the reply code.
1N/A*/
1N/A
1N/Aint
1N/Asmtpgetstat(m, mci, e)
1N/A MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A int r;
1N/A int off;
1N/A int status, xstat;
1N/A char *enhsc;
1N/A
1N/A enhsc = NULL;
1N/A
1N/A /* check for the results of the transaction */
1N/A r = reply(m, mci, e, TimeOuts.to_datafinal, NULL, &enhsc, XS_DATA2);
1N/A if (r < 0)
1N/A return EX_TEMPFAIL;
1N/A xstat = EX_NOTSTICKY;
1N/A if (REPLYTYPE(r) == 4)
1N/A status = EX_TEMPFAIL;
1N/A else if (REPLYTYPE(r) == 2)
1N/A status = xstat = EX_OK;
1N/A else if (REPLYCLASS(r) != 5)
1N/A status = xstat = EX_PROTOCOL;
1N/A else if (REPLYTYPE(r) == 5)
1N/A status = EX_UNAVAILABLE;
1N/A else
1N/A status = EX_PROTOCOL;
1N/A if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
1N/A (off = isenhsc(SmtpReplyBuffer + 4, ' ')) > 0)
1N/A off += 5;
1N/A else
1N/A off = 4;
1N/A e->e_statmsg = sm_rpool_strdup_x(e->e_rpool, &SmtpReplyBuffer[off]);
1N/A mci_setstat(mci, xstat, ENHSCN(enhsc, smtptodsn(r)), SmtpReplyBuffer);
1N/A if (LogLevel > 1 && status == EX_PROTOCOL)
1N/A {
1N/A sm_syslog(LOG_CRIT, e->e_id,
1N/A "%.100s: SMTP DATA-3 protocol error: %s",
1N/A CurHostName,
1N/A shortenstring(SmtpReplyBuffer, 403));
1N/A }
1N/A return status;
1N/A}
1N/A/*
1N/A** SMTPQUIT -- close the SMTP connection.
1N/A**
1N/A** Parameters:
1N/A** m -- a pointer to the mailer.
1N/A** mci -- the mailer connection information.
1N/A** e -- the current envelope.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** sends the final protocol and closes the connection.
1N/A*/
1N/A
1N/Avoid
1N/Asmtpquit(m, mci, e)
1N/A register MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A bool oldSuprErrs = SuprErrs;
1N/A int rcode;
1N/A char *oldcurhost;
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A mci_close(mci, "smtpquit:1");
1N/A return;
1N/A }
1N/A
1N/A oldcurhost = CurHostName;
1N/A CurHostName = mci->mci_host; /* XXX UGLY XXX */
1N/A if (CurHostName == NULL)
1N/A CurHostName = MyHostName;
1N/A
1N/A#if PIPELINING
1N/A mci->mci_okrcpts = 0;
1N/A#endif /* PIPELINING */
1N/A
1N/A /*
1N/A ** Suppress errors here -- we may be processing a different
1N/A ** job when we do the quit connection, and we don't want the
1N/A ** new job to be penalized for something that isn't it's
1N/A ** problem.
1N/A */
1N/A
1N/A SuprErrs = true;
1N/A
1N/A /* send the quit message if we haven't gotten I/O error */
1N/A if (mci->mci_state != MCIS_ERROR &&
1N/A mci->mci_state != MCIS_QUITING)
1N/A {
1N/A SmtpPhase = "client QUIT";
1N/A mci->mci_state = MCIS_QUITING;
1N/A smtpmessage("QUIT", m, mci);
1N/A (void) reply(m, mci, e, TimeOuts.to_quit, NULL, NULL, XS_QUIT);
1N/A SuprErrs = oldSuprErrs;
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A goto end;
1N/A }
1N/A
1N/A /* now actually close the connection and pick up the zombie */
1N/A rcode = endmailer(mci, e, NULL);
1N/A if (rcode != EX_OK)
1N/A {
1N/A char *mailer = NULL;
1N/A
1N/A if (mci->mci_mailer != NULL &&
1N/A mci->mci_mailer->m_name != NULL)
1N/A mailer = mci->mci_mailer->m_name;
1N/A
1N/A /* look for naughty mailers */
1N/A sm_syslog(LOG_ERR, e->e_id,
1N/A "smtpquit: mailer%s%s exited with exit value %d",
1N/A mailer == NULL ? "" : " ",
1N/A mailer == NULL ? "" : mailer,
1N/A rcode);
1N/A }
1N/A
1N/A SuprErrs = oldSuprErrs;
1N/A
1N/A end:
1N/A CurHostName = oldcurhost;
1N/A return;
1N/A}
1N/A/*
1N/A** SMTPRSET -- send a RSET (reset) command
1N/A**
1N/A** Parameters:
1N/A** m -- a pointer to the mailer.
1N/A** mci -- the mailer connection information.
1N/A** e -- the current envelope.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** closes the connection if there is no reply to RSET.
1N/A*/
1N/A
1N/Avoid
1N/Asmtprset(m, mci, e)
1N/A register MAILER *m;
1N/A register MCI *mci;
1N/A ENVELOPE *e;
1N/A{
1N/A int r;
1N/A
1N/A CurHostName = mci->mci_host; /* XXX UGLY XXX */
1N/A if (CurHostName == NULL)
1N/A CurHostName = MyHostName;
1N/A
1N/A#if PIPELINING
1N/A mci->mci_okrcpts = 0;
1N/A#endif /* PIPELINING */
1N/A
1N/A /*
1N/A ** Check if connection is gone, if so
1N/A ** it's a tempfail and we use mci_errno
1N/A ** for the reason.
1N/A */
1N/A
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A {
1N/A errno = mci->mci_errno;
1N/A return;
1N/A }
1N/A
1N/A SmtpPhase = "client RSET";
1N/A smtpmessage("RSET", m, mci);
1N/A r = reply(m, mci, e, TimeOuts.to_rset, NULL, NULL, XS_DEFAULT);
1N/A if (r < 0)
1N/A return;
1N/A
1N/A /*
1N/A ** Any response is deemed to be acceptable.
1N/A ** The standard does not state the proper action
1N/A ** to take when a value other than 250 is received.
1N/A **
1N/A ** However, if 421 is returned for the RSET, leave
1N/A ** mci_state alone (MCIS_SSD can be set in reply()
1N/A ** and MCIS_CLOSED can be set in smtpquit() if
1N/A ** reply() gets a 421 and calls smtpquit()).
1N/A */
1N/A
1N/A if (mci->mci_state != MCIS_SSD && mci->mci_state != MCIS_CLOSED)
1N/A mci->mci_state = MCIS_OPEN;
1N/A else if (mci->mci_exitstat == EX_OK)
1N/A mci_setstat(mci, EX_TEMPFAIL, "4.5.0", NULL);
1N/A}
1N/A/*
1N/A** SMTPPROBE -- check the connection state
1N/A**
1N/A** Parameters:
1N/A** mci -- the mailer connection information.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** closes the connection if there is no reply to RSET.
1N/A*/
1N/A
1N/Aint
1N/Asmtpprobe(mci)
1N/A register MCI *mci;
1N/A{
1N/A int r;
1N/A MAILER *m = mci->mci_mailer;
1N/A ENVELOPE *e;
1N/A extern ENVELOPE BlankEnvelope;
1N/A
1N/A CurHostName = mci->mci_host; /* XXX UGLY XXX */
1N/A if (CurHostName == NULL)
1N/A CurHostName = MyHostName;
1N/A
1N/A e = &BlankEnvelope;
1N/A SmtpPhase = "client probe";
1N/A smtpmessage("RSET", m, mci);
1N/A r = reply(m, mci, e, TimeOuts.to_miscshort, NULL, NULL, XS_DEFAULT);
1N/A if (REPLYTYPE(r) != 2)
1N/A smtpquit(m, mci, e);
1N/A return r;
1N/A}
1N/A/*
1N/A** REPLY -- read arpanet reply
1N/A**
1N/A** Parameters:
1N/A** m -- the mailer we are reading the reply from.
1N/A** mci -- the mailer connection info structure.
1N/A** e -- the current envelope.
1N/A** timeout -- the timeout for reads.
1N/A** pfunc -- processing function called on each line of response.
1N/A** If null, no special processing is done.
1N/A** enhstat -- optional, returns enhanced error code string (if set)
1N/A** rtype -- type of SmtpMsgBuffer: does it contains secret data?
1N/A**
1N/A** Returns:
1N/A** reply code it reads.
1N/A**
1N/A** Side Effects:
1N/A** flushes the mail file.
1N/A*/
1N/A
1N/Aint
1N/Areply(m, mci, e, timeout, pfunc, enhstat, rtype)
1N/A MAILER *m;
1N/A MCI *mci;
1N/A ENVELOPE *e;
1N/A time_t timeout;
1N/A void (*pfunc) __P((char *, bool, MAILER *, MCI *, ENVELOPE *));
1N/A char **enhstat;
1N/A int rtype;
1N/A{
1N/A register char *bufp;
1N/A register int r;
1N/A bool firstline = true;
1N/A char junkbuf[MAXLINE];
1N/A static char enhstatcode[ENHSCLEN];
1N/A int save_errno;
1N/A
1N/A /*
1N/A ** Flush the output before reading response.
1N/A **
1N/A ** For SMTP pipelining, it would be better if we didn't do
1N/A ** this if there was already data waiting to be read. But
1N/A ** to do it properly means pushing it to the I/O library,
1N/A ** since it really needs to be done below the buffer layer.
1N/A */
1N/A
1N/A if (mci->mci_out != NULL)
1N/A (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
1N/A
1N/A if (tTd(18, 1))
1N/A sm_dprintf("reply\n");
1N/A
1N/A /*
1N/A ** Read the input line, being careful not to hang.
1N/A */
1N/A
1N/A bufp = SmtpReplyBuffer;
1N/A set_tls_rd_tmo(timeout);
1N/A for (;;)
1N/A {
1N/A register char *p;
1N/A
1N/A /* actually do the read */
1N/A if (e->e_xfp != NULL) /* for debugging */
1N/A (void) sm_io_flush(e->e_xfp, SM_TIME_DEFAULT);
1N/A
1N/A /* if we are in the process of closing just give the code */
1N/A if (mci->mci_state == MCIS_CLOSED)
1N/A return SMTPCLOSING;
1N/A
1N/A /* don't try to read from a non-existent fd */
1N/A if (mci->mci_in == NULL)
1N/A {
1N/A if (mci->mci_errno == 0)
1N/A mci->mci_errno = EBADF;
1N/A
1N/A /* errors on QUIT should be ignored */
1N/A if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
1N/A {
1N/A errno = mci->mci_errno;
1N/A mci_close(mci, "reply:1");
1N/A return -1;
1N/A }
1N/A mci->mci_state = MCIS_ERROR;
1N/A smtpquit(m, mci, e);
1N/A errno = mci->mci_errno;
1N/A return -1;
1N/A }
1N/A
1N/A if (mci->mci_out != NULL)
1N/A (void) sm_io_flush(mci->mci_out, SM_TIME_DEFAULT);
1N/A
1N/A /* get the line from the other side */
1N/A p = sfgets(bufp, MAXLINE, mci->mci_in, timeout, SmtpPhase);
1N/A save_errno = errno;
1N/A mci->mci_lastuse = curtime();
1N/A
1N/A if (p == NULL)
1N/A {
1N/A bool oldholderrs;
1N/A extern char MsgBuf[];
1N/A
1N/A /* errors on QUIT should be ignored */
1N/A if (strncmp(SmtpMsgBuffer, "QUIT", 4) == 0)
1N/A {
1N/A mci_close(mci, "reply:2");
1N/A return -1;
1N/A }
1N/A
1N/A /* if the remote end closed early, fake an error */
1N/A errno = save_errno;
1N/A if (errno == 0)
1N/A {
1N/A (void) sm_snprintf(SmtpReplyBuffer,
1N/A sizeof(SmtpReplyBuffer),
1N/A "421 4.4.1 Connection reset by %s",
1N/A CURHOSTNAME);
1N/A#ifdef ECONNRESET
1N/A errno = ECONNRESET;
1N/A#else /* ECONNRESET */
1N/A errno = EPIPE;
1N/A#endif /* ECONNRESET */
1N/A }
1N/A
1N/A mci->mci_errno = errno;
1N/A oldholderrs = HoldErrs;
1N/A HoldErrs = true;
1N/A usrerr("451 4.4.1 reply: read error from %s",
1N/A CURHOSTNAME);
1N/A mci_setstat(mci, EX_TEMPFAIL, "4.4.2", MsgBuf);
1N/A
1N/A /* if debugging, pause so we can see state */
1N/A if (tTd(18, 100))
1N/A (void) pause();
1N/A mci->mci_state = MCIS_ERROR;
1N/A smtpquit(m, mci, e);
1N/A#if XDEBUG
1N/A {
1N/A char wbuf[MAXLINE];
1N/A
1N/A p = wbuf;
1N/A if (e->e_to != NULL)
1N/A {
1N/A (void) sm_snprintf(p,
1N/A SPACELEFT(wbuf, p),
1N/A "%s... ",
1N/A shortenstring(e->e_to, MAXSHORTSTR));
1N/A p += strlen(p);
1N/A }
1N/A (void) sm_snprintf(p, SPACELEFT(wbuf, p),
1N/A "reply(%.100s) during %s",
1N/A CURHOSTNAME, SmtpPhase);
1N/A checkfd012(wbuf);
1N/A }
1N/A#endif /* XDEBUG */
1N/A HoldErrs = oldholderrs;
1N/A errno = save_errno;
1N/A return -1;
1N/A }
1N/A fixcrlf(bufp, true);
1N/A
1N/A /* EHLO failure is not a real error */
1N/A if (e->e_xfp != NULL && (bufp[0] == '4' ||
1N/A (bufp[0] == '5' && strncmp(SmtpMsgBuffer, "EHLO", 4) != 0)))
1N/A {
1N/A /* serious error -- log the previous command */
1N/A if (SmtpNeedIntro)
1N/A {
1N/A /* inform user who we are chatting with */
1N/A (void) sm_io_fprintf(CurEnv->e_xfp,
1N/A SM_TIME_DEFAULT,
1N/A "... while talking to %s:\n",
1N/A CURHOSTNAME);
1N/A SmtpNeedIntro = false;
1N/A }
1N/A if (SmtpMsgBuffer[0] != '\0')
1N/A {
1N/A (void) sm_io_fprintf(e->e_xfp,
1N/A SM_TIME_DEFAULT,
1N/A ">>> %s\n",
1N/A (rtype == XS_STARTTLS)
1N/A ? "STARTTLS dialogue"
1N/A : ((rtype == XS_AUTH)
1N/A ? "AUTH dialogue"
1N/A : SmtpMsgBuffer));
1N/A SmtpMsgBuffer[0] = '\0';
1N/A }
1N/A
1N/A /* now log the message as from the other side */
1N/A (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
1N/A "<<< %s\n", bufp);
1N/A }
1N/A
1N/A /* display the input for verbose mode */
1N/A if (Verbose)
1N/A nmessage("050 %s", bufp);
1N/A
1N/A /* ignore improperly formatted input */
1N/A if (!ISSMTPREPLY(bufp))
1N/A continue;
1N/A
1N/A if (bitset(MCIF_ENHSTAT, mci->mci_flags) &&
1N/A enhstat != NULL &&
1N/A extenhsc(bufp + 4, ' ', enhstatcode) > 0)
1N/A *enhstat = enhstatcode;
1N/A
1N/A /* process the line */
1N/A if (pfunc != NULL)
1N/A (*pfunc)(bufp, firstline, m, mci, e);
1N/A
1N/A /* decode the reply code */
1N/A r = atoi(bufp);
1N/A
1N/A /* extra semantics: 0xx codes are "informational" */
1N/A if (r < 100)
1N/A {
1N/A firstline = false;
1N/A continue;
1N/A }
1N/A
1N/A firstline = false;
1N/A
1N/A /* if no continuation lines, return this line */
1N/A if (bufp[3] != '-')
1N/A break;
1N/A
1N/A /* first line of real reply -- ignore rest */
1N/A bufp = junkbuf;
1N/A }
1N/A
1N/A /*
1N/A ** Now look at SmtpReplyBuffer -- only care about the first
1N/A ** line of the response from here on out.
1N/A */
1N/A
1N/A /* save temporary failure messages for posterity */
1N/A if (SmtpReplyBuffer[0] == '4')
1N/A (void) sm_strlcpy(SmtpError, SmtpReplyBuffer, sizeof(SmtpError));
1N/A
1N/A /* reply code 421 is "Service Shutting Down" */
1N/A if (r == SMTPCLOSING && mci->mci_state != MCIS_SSD &&
1N/A mci->mci_state != MCIS_QUITING)
1N/A {
1N/A /* send the quit protocol */
1N/A mci->mci_state = MCIS_SSD;
1N/A smtpquit(m, mci, e);
1N/A }
1N/A
1N/A return r;
1N/A}
1N/A/*
1N/A** SMTPMESSAGE -- send message to server
1N/A**
1N/A** Parameters:
1N/A** f -- format
1N/A** m -- the mailer to control formatting.
1N/A** a, b, c -- parameters
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** writes message to mci->mci_out.
1N/A*/
1N/A
1N/A/*VARARGS1*/
1N/Avoid
1N/A#ifdef __STDC__
1N/Asmtpmessage(char *f, MAILER *m, MCI *mci, ...)
1N/A#else /* __STDC__ */
1N/Asmtpmessage(f, m, mci, va_alist)
1N/A char *f;
1N/A MAILER *m;
1N/A MCI *mci;
1N/A va_dcl
1N/A#endif /* __STDC__ */
1N/A{
1N/A SM_VA_LOCAL_DECL
1N/A
1N/A SM_VA_START(ap, mci);
1N/A (void) sm_vsnprintf(SmtpMsgBuffer, sizeof(SmtpMsgBuffer), f, ap);
1N/A SM_VA_END(ap);
1N/A
1N/A if (tTd(18, 1) || Verbose)
1N/A nmessage(">>> %s", SmtpMsgBuffer);
1N/A if (TrafficLogFile != NULL)
1N/A (void) sm_io_fprintf(TrafficLogFile, SM_TIME_DEFAULT,
1N/A "%05d >>> %s\n", (int) CurrentPid,
1N/A SmtpMsgBuffer);
1N/A if (mci->mci_out != NULL)
1N/A {
1N/A (void) sm_io_fprintf(mci->mci_out, SM_TIME_DEFAULT, "%s%s",
1N/A SmtpMsgBuffer, m == NULL ? "\r\n"
1N/A : m->m_eol);
1N/A }
1N/A else if (tTd(18, 1))
1N/A {
1N/A sm_dprintf("smtpmessage: NULL mci_out\n");
1N/A }
1N/A}