diff options
Diffstat (limited to 'contrib/sendmail/src/usersmtp.c')
-rw-r--r-- | contrib/sendmail/src/usersmtp.c | 3318 |
1 files changed, 0 insertions, 3318 deletions
diff --git a/contrib/sendmail/src/usersmtp.c b/contrib/sendmail/src/usersmtp.c deleted file mode 100644 index 724f10c..0000000 --- a/contrib/sendmail/src/usersmtp.c +++ /dev/null @@ -1,3318 +0,0 @@ -/* - * Copyright (c) 1998-2006 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.470 2007/10/17 21:35:30 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; - char in64[MAXOUTLEN]; -#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, MAXOUTLEN, 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, - MAXOUTLEN, 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; - 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"); - } -} |