/*
* Copyright (c) 1998-2006, 2008, 2009 Sendmail, Inc. and its suppliers.
* All rights reserved.
* Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*
*/
#include <sendmail.h>
SM_RCSID("@(#)$Id: usersmtp.c,v 8.473 2009/06/17 17:26:51 ca Exp $")
#include <sysexits.h>
#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.
*/
/*
** 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
MAILER *m;
ENVELOPE *e;
bool onlyhelo;
{
register int r;
int state;
register char *p;
register char *hn;
char *enhsc;
{
sm_dprintf("smtpinit ");
}
/*
** Open the connection to the mailer.
*/
SmtpError[0] = '\0';
SmtpMsgBuffer[0] = '\0';
if (CurHostName == NULL)
SmtpNeedIntro = true;
switch (state)
{
case MCIS_MAIL:
case MCIS_RCPT:
case MCIS_DATA:
/* need to clear old information */
/* FALLTHROUGH */
case MCIS_OPEN:
if (!onlyhelo)
return;
break;
case MCIS_ERROR:
case MCIS_QUITING:
case MCIS_SSD:
/* shouldn't happen */
/* FALLTHROUGH */
case MCIS_CLOSED:
return;
case MCIS_OPENING:
break;
}
if (onlyhelo)
goto helo;
clrsessenvelope(e);
/*
** Get the greeting message.
** This should appear spontaneously. Give it five minutes to
** happen.
*/
sm_setproctitle(true, e, "%s %s: %s",
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:
#endif /* _FFR_IGNORE_EXT_ON_HELO */
{
}
{
}
else
{
#endif /* _FFR_IGNORE_EXT_ON_HELO */
}
if (r < 0)
goto tempfail1;
else if (REPLYTYPE(r) == 5)
{
{
/* try old SMTP instead */
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.
*/
if (p != NULL)
*p = '\0';
{
syserr("553 5.3.5 %s config error: mail loops back to me (MX problem?)",
"553 5.3.5 system config error");
return;
}
/*
** If this is expected to be another sendmail, send some internal
** commands.
** If we're running as MSP, "propagate" -v flag if possible.
*/
# endif /* !_FFR_DEPRECATE_MAILER_FLAG_I */
)
{
/* tell it to be verbose */
if (r < 0)
goto tempfail1;
}
{
return;
}
/* got a 421 error code during startup */
return;
/* XXX should use code from other end iff ENHANCEDSTATUSCODES */
return;
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
char *line;
bool firstline;
MAILER *m;
ENVELOPE *e;
{
/*
** 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 SASL
/* specify prototype so compiler can check calls */
/*
** 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 *
{
return s2;
return s1;
{
return s1;
return s2;
}
h = s2;
/* walk through s2 */
{
/* is there something after the current word? */
*h = '\0';
/* does the current word appear in s1 ? */
{
/* add space as delimiter */
*hr++ = ' ';
/* copy the item */
/* advance pointer in result list */
*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
char *line;
bool firstline;
MAILER *m;
ENVELOPE *e;
{
register char *p;
static bool logged = false;
#endif /* _FFR_IGNORE_EXT_ON_HELO */
if (firstline)
{
#if SASL
#endif /* SASL */
logged = false;
#endif /* _FFR_IGNORE_EXT_ON_HELO */
return;
}
{
{
"server=%s [%s] returned extensions despite HELO command",
logged = true;
}
return;
}
#endif /* _FFR_IGNORE_EXT_ON_HELO */
return;
line += 4;
if (p != NULL)
*p++ = '\0';
{
if (p != NULL)
}
{
}
#if STARTTLS
#endif /* STARTTLS */
{
if (p != NULL)
}
#if SASL
{
if (p != NULL && *p != '\0')
{
{
/*
** Create the union with previous auth
** offerings because we recognize "auth "
** and "auth=" (old format).
*/
}
else
{
int l;
l = strlen(p) + 1;
mci->mci_saslcap = (char *)
{
l);
}
}
}
}
#endif /* SASL */
}
#if SASL
static int saslgetrealm __P((void *, int, const char **, const char **));
{
#define CB_GETREALM_IDX 0
};
/*
** INIT_SASL_CLIENT -- initialize client side of Cyrus-SASL
**
** Parameters:
** none.
**
** Returns:
** SASL_OK -- if successful.
** SASL error code -- otherwise.
**
** Side Effects:
**
** 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
{
int result;
if (sasl_clt_init)
return SASL_OK;
/* should we retry later again or just remember that it failed? */
sasl_clt_init = true;
return result;
}
/*
** STOP_SASL_CLIENT -- shutdown client side of Cyrus-SASL
**
** Parameters:
** none.
**
** Returns:
** none.
**
** Side Effects:
*/
void
{
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
char *line;
bool firstline;
MAILER *m;
ENVELOPE *e;
{
int len;
int result;
# if SASL < 20000
char *out;
# endif /* SASL < 20000 */
/* if not a continue we don't care about it */
if ((len <= 4) ||
(line[0] != '3') ||
{
return;
}
/* forget about "334 " */
line += 4;
len -= 4;
# if SASL >= 20000
{
{
}
}
else
(unsigned int *) &mci->mci_sasl_string_len);
{
mci->mci_sasl_string_len = 0;
}
# else /* SASL >= 20000 */
{
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.
*/
{
{
}
}
else
# 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
char *filename;
bool safe;
{
SM_FILE_T *f;
long sff;
int lc;
char *s;
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;
i = 0;
{
if (i >= MAXPV)
break;
argv[i++] = p;
}
if (pid < 0)
f = NULL;
else
}
else
#endif /* !_FFR_ALLOW_SASLINFO */
{
pid = -1;
# endif /* _FFR_GROUPREADABLEAUTHINFOFILE */
sff |= SFF_NOGRFILES;
if (DontLockReadFiles)
sff |= SFF_NOLOCK;
/*
** 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 */
}
if (f == NULL)
{
if (LogLevel > 5)
"AUTH=client, error: can't open %s: %s",
return EX_TEMPFAIL;
}
lc = 0;
while (lc <= SASL_MECHLIST &&
{
if (buf[0] != '#')
{
*s = '\0';
lc++;
}
}
(void) sm_io_close(f, SM_TIME_DEFAULT);
if (pid > 0)
if (lc < SASL_PASSWORD)
{
if (LogLevel > 8)
"AUTH=client, error: can't read %s from %s",
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
ENVELOPE *e;
{
char **pvp;
if (r != EX_OK)
return EX_UNAVAILABLE;
/* other than expected return value: ok (i.e., no auth) */
return EX_OK;
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 '='
*/
i = 0;
got = 0;
while (i < SASL_ENTRIES)
{
break;
break;
{
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;
}
/* check syntax */
goto fail;
/* remove closing quote */
/* remove "TD and " */
l -= 4;
goto tempfail;
{
/* ':text' (just copy) */
got |= 1 << r;
}
{
unsigned int len;
/* '=base64' (decode) */
# if SASL >= 20000
(unsigned int) l, (*sai)[r],
(unsigned int) l + 1, &len);
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
goto fail;
got |= 1 << r;
}
else
goto fail;
sasl_info_name[r], (*sai)[r]);
++i;
}
/* did we get the expected data? */
/* XXX: EXTERNAL mechanism only requires (and only uses) SASL_USER */
goto fail;
/* no authid? copy uid */
{
l + 1);
goto tempfail;
}
/* no uid? copy authid */
{
l + 1);
goto tempfail;
}
return EX_OK;
ret = EX_TEMPFAIL;
fail:
if (LogLevel > 8)
"AUTH=client, relay=%.64s [%.16s], authinfo %sfailed",
for (i = 0; i <= SASL_MECHLIST; i++)
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:
*/
static int
void *context;
int id;
const char **result;
unsigned *len;
{
return SASL_BADPARAM;
switch (id)
{
case SASL_CB_USER:
*result);
break;
case SASL_CB_AUTHNAME:
*result);
break;
case SASL_CB_LANGUAGE:
*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:
*/
static int
int id;
{
int len;
char *authpass;
return SASL_BADPARAM;
/*
** use an rpool because we are responsible for free()ing the secret,
** but we can't free() it until after the auth completes
*/
sizeof(sasl_secret_t) +
len + 1);
return SASL_FAIL;
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:
*/
static int
void *context;
int id;
const char **result;
unsigned *len;
{
char *h, *s;
# if SASL > 10509
bool addrealm;
# endif /* SASL > 10509 */
size_t l;
return SASL_BADPARAM;
/*
** 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:
s = sm_sasl_malloc(l);
if (s == NULL)
{
*len = 0;
return SASL_NOMEM;
}
*result = s;
*result);
break;
case SASL_CB_AUTHNAME:
h = (*sai)[SASL_AUTHID];
# if SASL > 10509
/* XXX maybe other mechanisms too?! */
/*
** Add realm to authentication id unless authid contains
** '@' (i.e., a realm) or the default realm is empty.
*/
{
/* has this been done before? */
{
char *realm;
/* do not add an empty realm */
if (*realm == '\0')
{
authid = h;
}
else
{
/* should use rpool, but from where? */
authid = sm_sasl_malloc(l);
{
(void) sm_snprintf(authid, l,
"%s@%s",
h, realm);
}
else
{
authid = h;
}
}
}
else
}
else
# endif /* SASL > 10509 */
authid = h;
s = sm_sasl_malloc(l);
if (s == NULL)
{
*len = 0;
return SASL_NOMEM;
}
(void) sm_strlcpy(s, authid, l);
*result = s;
*result);
break;
case SASL_CB_LANGUAGE:
*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:
*/
static int
int id;
{
int len;
char *authpass;
return SASL_BADPARAM;
len + 1);
return SASL_FAIL;
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
#else /* SASL > 10515 */
#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
# else /* SASL >= 20000 */
int type;
# endif /* SASL >= 20000 */
#endif /* SASL > 10515 */
{
long sff;
int r;
#if SASL <= 10515
#endif /* SASL <= 10515 */
char *p;
return SASL_OK;
#if SASL <= 10515
p = file;
else
++p;
/* everything beside libs and .conf files must not be readable */
{
sff |= SFF_NORFILES;
sff |= SFF_NOGWFILES;
}
#else /* SASL <= 10515 */
/* files containing passwords should be not readable */
if (type == SASL_VRFY_PASSWD)
{
sff |= SFF_NOWRFILES;
else
sff |= SFF_NORFILES;
sff |= SFF_NOGWFILES;
}
#endif /* SASL <= 10515 */
p = (char *) file;
return SASL_OK;
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:
*/
static int
void *context;
int id;
const char **availrealms;
const char **result;
{
char *r;
return SASL_FAIL;
r = (*sai)[SASL_DEFREALM];
if (LogLevel > 12)
"AUTH=client, realm=%s, available realms=%s",
r == NULL ? "<No Realm>" : r,
? "<No Realms>" : *availrealms);
/* check whether context is in list */
{
NULL)
{
if (LogLevel > 8)
"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 *
char *item;
char *list;
char *delim;
{
char *s;
int len;
return NULL;
return NULL;
s = list;
while (s != NULL && *s != '\0')
{
return s;
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 *
char *rem;
char *list;
{
char *ret;
char *needle;
int len;
return NULL;
{
/* take out what? */
return NULL;
}
/* find the item in the list */
{
/* not in there: return original */
return list;
}
/* length of string without rem */
if (len <= 0)
{
*ret = '\0';
return ret;
}
/* copy from start to removed item */
/* length of rest of string past removed item */
if (len > 0)
{
/* not last item -- copy into string */
len);
}
else
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
MAILER *m;
ENVELOPE *e;
{
# if SASL >= 20000
const char *auth_id;
const char *out;
# else /* SASL >= 20000 */
char *out;
# endif /* SASL >= 20000 */
unsigned int outlen;
char *mechusing;
/* MUST NOT be a multiple of 4: bug in some sasl_encode64() versions */
extern SOCKADDR CurHostAddr;
#endif /* NETINET || (NETINET6 && SASL >= 20000) */
/* no mechanism selected (yet) */
/* dispose old connection */
/* 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.
*/
: "smtp",
# else /* SASL >= 20000 */
: "smtp",
# endif /* SASL >= 20000 */
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
/* set properties */
/* XXX should these be options settable via .cf ? */
{
# if 0
# endif /* 0 */
}
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if SASL >= 20000
/* external security strength factor, authentication id */
ssf = 0;
# if STARTTLS
# endif /* STARTTLS */
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if NETINET6
# endif /* NETINET6 */
{
{
case AF_INET:
addrsize = sizeof(struct sockaddr_in);
break;
# if NETINET6
case AF_INET6:
addrsize = sizeof(struct sockaddr_in6);
break;
# endif /* NETINET6 */
default:
break;
}
{
return EX_TEMPFAIL;
}
NULL),
{
{
return EX_TEMPFAIL;
}
}
}
# endif /* NETINET || NETINET6 */
/* start client side of sasl */
(const char **) &mechusing);
# else /* SASL >= 20000 */
/* external security strength factor, authentication id */
# if STARTTLS
# endif /* STARTTLS */
if (saslresult != SASL_OK)
return EX_TEMPFAIL;
# if NETINET
{
struct sockaddr_in saddr_l;
(struct sockaddr_in *) &CurHostAddr)
!= SASL_OK)
return EX_TEMPFAIL;
addrsize = sizeof(struct sockaddr_in);
NULL),
{
return EX_TEMPFAIL;
}
}
# endif /* NETINET */
/* start client side of sasl */
(const char **) &mechusing);
# endif /* SASL >= 20000 */
{
{
"AUTH=client, available mechanisms do not fulfill requirements");
}
return EX_TEMPFAIL;
}
/* just point current mechanism to the data in the sasl library */
/* send the info across the wire */
/* login and digest-md5 up to 1.5.28 set out="" */
|| (outlen == 0 &&
)
{
/* no initial response */
}
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"
*/
}
else
{
NULL);
{
if (LogLevel > 8)
"encode64 for AUTH failed");
return EX_TEMPFAIL;
}
}
# if SASL < 20000
# endif /* SASL < 20000 */
/* get the reply */
XS_AUTH);
for (;;)
{
/* check return code from server */
if (smtpresult == 235)
{
return EX_OK;
}
if (smtpresult == -1)
return EX_IOERR;
return EX_NOPERM; /* ugly, but ... */
{
/* should we fail deliberately, see RFC 2554 4. ? */
/* smtpmessage("*", m, mci); */
return EX_TEMPFAIL;
}
{
sm_dprintf("AUTH FAIL=%s (%d)\n",
/* fail deliberately, see RFC 2554 4. */
/*
** but we should only fail for this authentication
** mechanism; how to do that?
*/
return EX_NOPERM;
}
if (outlen > 0)
{
if (saslresult != SASL_OK)
{
/* give an error reply to the other side! */
return EX_TEMPFAIL;
}
}
else
in64[0] = '\0';
# if SASL < 20000
# endif /* SASL < 20000 */
}
/* 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
MAILER *m;
ENVELOPE *e;
{
int result;
int i;
bool usedgetauth;
mci->mci_sasl_auth = false;
for (i = 0; i < SASL_MECH ; i++)
if (result == EX_TEMPFAIL)
return result;
usedgetauth = true;
/* no data available: don't try to authenticate */
return result;
{
return EX_UNAVAILABLE;
/* read authinfo from file */
return result;
usedgetauth = false;
}
/* check whether sufficient data is available */
return EX_UNAVAILABLE;
return EX_UNAVAILABLE;
/* set the context for the callback function to sai */
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
#if 0
#endif /* 0 */
/* set default value for realm */
/* set default value for list of mechanism to use */
/* create list of mechanisms to try */
/* initialize sasl client library */
result = init_sasl_client();
do
{
mci->mci_sasl_auth = true;
{
return usedgetauth ? result
}
else
return result;
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
MAILER *m;
ENVELOPE *e;
{
int r;
char *bufp;
char *bodytype;
char *enhsc;
/*
** Check if connection is gone, if so
** it's a tempfail and we use mci_errno
** for the reason.
*/
{
return EX_TEMPFAIL;
}
/* set up appropriate options to include */
{
e->e_msgsize);
}
else
{
optbuf[0] = '\0';
}
bodytype = e->e_bodytype;
{
bodytype = "8BITMIME";
{
" BODY=%s", bodytype);
}
}
{
/* EMPTY */
/* just pass it through */
}
#if MIME8TO7
{
/* must convert from 8bit MIME format to 7bit encoded */
}
#endif /* MIME8TO7 */
{
/* cannot just send a 8-bit version */
extern char MsgBuf[];
return EX_DATAERR;
}
{
{
" ENVID=%s", e->e_envid);
}
/* RET= parameter */
{
" RET=%s",
"HDRS" : "FULL");
}
}
#if SASL
#endif /* SASL */
)
{
" AUTH=%s", e->e_auth_param);
}
/*
** 17 is the max length required, we could use log() to compute
** the exact length (and check IS_DLVR_TRACE())
*/
{
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.
*/
if (dby <= 0 && IS_DLVR_RETURN(e))
" BY=%ld;%c%s",
dby,
}
/*
** Send the MAIL command.
** Designates the sender.
*/
buf[0] = '\0';
else
if (buf[0] == '<')
{
/* strip off <angle brackets> (put back on below) */
if (*bufp == '>')
*bufp = '\0';
}
else
{
}
else
{
}
if (r < 0)
{
/* communications failure */
return EX_TEMPFAIL;
}
else if (r == SMTPCLOSING)
{
/* service shutting down: handled by reply() */
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 4)
{
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 2)
{
return EX_OK;
}
else if (r == 501)
{
/* syntax error in arguments */
return EX_DATAERR;
}
else if (r == 553)
{
/* mailbox name not allowed */
return EX_DATAERR;
}
else if (r == 552)
{
/* exceeded storage allocation */
e->e_flags |= EF_NO_BODY_RETN;
return EX_UNAVAILABLE;
}
else if (REPLYTYPE(r) == 5)
{
/* unknown error */
return EX_UNAVAILABLE;
}
if (LogLevel > 1)
{
"%.100s: SMTP MAIL protocol error: %s",
}
/* protocol error -- close up */
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
register MAILER *m;
ENVELOPE *e;
{
char *bufp;
#if PIPELINING
/*
** If there is status waiting from the other end, read it.
** This should normally happen because of SMTP pipelining.
*/
{
int r;
if (r != EX_OK)
{
}
}
#endif /* PIPELINING */
/*
** Check if connection is gone, if so
** it's a tempfail and we use mci_errno
** for the reason.
*/
{
return EX_TEMPFAIL;
}
optbuf[0] = '\0';
/*
** Warning: in the following it is assumed that the free space
** in bufp is sizeof(optbuf)
*/
{
if (IS_DLVR_NOTIFY(e) &&
{
/* RFC 2852: 4.1.4.2 */
}
/* NOTIFY= parameter */
{
bool firstone = true;
{
firstone = false;
}
{
if (!firstone)
sizeof(optbuf));
firstone = false;
}
{
if (!firstone)
sizeof(optbuf));
firstone = false;
}
if (firstone)
}
/* ORCPT= parameter */
{
}
}
#if PIPELINING
/*
** If running SMTP pipelining, we will pick up status later
*/
return EX_OK;
#endif /* PIPELINING */
}
/*
** 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
MAILER *m;
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.
*/
{
return EX_TEMPFAIL;
}
save_errno = errno;
if (r < 0 || REPLYTYPE(r) == 4)
{
mci->mci_retryrcpt = true;
errno = save_errno;
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 2)
{
char *t;
{
char *p;
*t++ = ',';
continue;
*t = '\0';
mci->mci_tolist = t;
}
#if PIPELINING
mci->mci_okrcpts++;
#endif /* PIPELINING */
return EX_OK;
}
else if (r == 550)
{
return EX_NOUSER;
}
else if (r == 551)
{
return EX_NOUSER;
}
else if (r == 553)
{
return EX_NOUSER;
}
else if (REPLYTYPE(r) == 5)
{
return EX_UNAVAILABLE;
}
if (LogLevel > 1)
{
"%.100s: SMTP RCPT protocol error: %s",
}
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
MAILER *m;
register ENVELOPE *e;
{
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.
*/
{
return EX_TEMPFAIL;
}
/*
** 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 */
#if PIPELINING
{
/* pick up any pending RCPT responses for SMTP pipelining */
{
int r;
if (r != EX_OK)
{
false);
mci->mci_nextaddr);
if (r == EX_TEMPFAIL)
}
}
/*
** 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.
*/
{
return EX_TEMPFAIL;
}
}
#endif /* PIPELINING */
/* now proceed with DATA phase */
sm_setproctitle(true, e, "%s %s: %s",
if (r < 0 || REPLYTYPE(r) == 4)
{
if (r >= 0)
return EX_TEMPFAIL;
}
else if (REPLYTYPE(r) == 5)
{
#if PIPELINING
if (mci->mci_okrcpts <= 0)
#endif /* PIPELINING */
return EX_UNAVAILABLE;
}
else if (REPLYTYPE(r) != 3)
{
if (LogLevel > 1)
{
"%.100s: SMTP DATA-1 protocol error: %s",
}
#if PIPELINING
if (mci->mci_okrcpts <= 0)
: 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.
*/
{
/* simulate a DATA timeout */
timeout = 10;
}
else
/*
** Output the actual message.
*/
goto writeerr;
{
/* simulate a DATA timeout */
(void) sleep(2);
}
goto writeerr;
/*
** Cleanup after sending message.
*/
#if PIPELINING
}
#endif /* PIPELINING */
{
/* terminate the message */
m->m_eol);
if (TrafficLogFile != NULL)
"%05d >>> .\n", (int) CurrentPid);
if (Verbose)
nmessage(">>> .");
"%.100s: SMTP DATA-1 protocol error: remote server returned response before final dot",
return EX_PROTOCOL;
}
#endif /* _FFR_CATCH_BROKEN_MTAS */
{
/* error during processing -- don't send the dot */
return EX_IOERR;
}
/* terminate the message */
goto writeerr;
if (TrafficLogFile != NULL)
"%05d >>> .\n", (int) CurrentPid);
if (Verbose)
nmessage(">>> .");
/* check for the results of the transaction */
return EX_OK;
if (r < 0)
return EX_TEMPFAIL;
if (r == 452)
rstat = EX_TEMPFAIL;
else if (REPLYTYPE(r) == 4)
else if (REPLYTYPE(r) == 2)
else if (REPLYCLASS(r) != 5)
else if (REPLYTYPE(r) == 5)
else
rstat = EX_PROTOCOL;
r += 5;
else
r = 4;
if (rstat != EX_PROTOCOL)
return rstat;
if (LogLevel > 1)
{
"%.100s: SMTP DATA-2 protocol error: %s",
}
return rstat;
/*
** 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.
*/
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
MAILER *m;
ENVELOPE *e;
{
int r;
int off;
char *enhsc;
/* check for the results of the transaction */
if (r < 0)
return EX_TEMPFAIL;
if (REPLYTYPE(r) == 4)
else if (REPLYTYPE(r) == 2)
else if (REPLYCLASS(r) != 5)
else if (REPLYTYPE(r) == 5)
else
off += 5;
else
off = 4;
{
"%.100s: SMTP DATA-3 protocol error: %s",
}
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
register MAILER *m;
ENVELOPE *e;
{
int rcode;
char *oldcurhost;
{
return;
}
if (CurHostName == NULL)
#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 */
{
SmtpPhase = "client QUIT";
goto end;
}
/* now actually close the connection and pick up the zombie */
{
/* look for naughty mailers */
"smtpquit: mailer%s%s exited with exit value %d",
rcode);
}
end:
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
register MAILER *m;
ENVELOPE *e;
{
int r;
if (CurHostName == NULL)
#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.
*/
{
return;
}
SmtpPhase = "client RSET";
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()).
*/
}
/*
** 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
{
int r;
ENVELOPE *e;
extern ENVELOPE BlankEnvelope;
if (CurHostName == NULL)
e = &BlankEnvelope;
SmtpPhase = "client probe";
if (REPLYTYPE(r) != 2)
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
MAILER *m;
ENVELOPE *e;
char **enhstat;
int rtype;
{
register char *bufp;
register int r;
bool firstline = true;
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.
*/
sm_dprintf("reply\n");
/*
** Read the input line, being careful not to hang.
*/
for (;;)
{
register char *p;
/* actually do the read */
/* if we are in the process of closing just give the code */
return SMTPCLOSING;
/* don't try to read from a non-existent fd */
{
/* errors on QUIT should be ignored */
{
return -1;
}
return -1;
}
/* get the line from the other side */
save_errno = errno;
if (p == NULL)
{
bool oldholderrs;
extern char MsgBuf[];
/* errors on QUIT should be ignored */
{
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",
#ifdef ECONNRESET
errno = ECONNRESET;
#else /* ECONNRESET */
#endif /* ECONNRESET */
}
HoldErrs = true;
usrerr("451 4.4.1 reply: read error from %s",
/* if debugging, pause so we can see state */
(void) pause();
#if XDEBUG
{
p = wbuf;
{
(void) sm_snprintf(p,
"%s... ",
p += strlen(p);
}
"reply(%.100s) during %s",
}
#endif /* XDEBUG */
errno = save_errno;
return -1;
}
/* EHLO failure is not a real error */
{
/* serious error -- log the previous command */
if (SmtpNeedIntro)
{
/* inform user who we are chatting with */
"... while talking to %s:\n",
SmtpNeedIntro = false;
}
if (SmtpMsgBuffer[0] != '\0')
{
(void) sm_io_fprintf(e->e_xfp,
">>> %s\n",
(rtype == XS_STARTTLS)
? "STARTTLS dialogue"
? "AUTH dialogue"
: SmtpMsgBuffer));
SmtpMsgBuffer[0] = '\0';
}
/* now log the message as from the other side */
"<<< %s\n", bufp);
}
/* display the input for verbose mode */
if (Verbose)
/* ignore improperly formatted input */
if (!ISSMTPREPLY(bufp))
continue;
*enhstat = enhstatcode;
/* process the line */
firstline = false;
/* decode the reply code */
/* extra semantics: 0xx codes are "informational" */
if (r < 100)
continue;
/* if no continuation lines, return this line */
break;
/* first line of real reply -- ignore rest */
}
/*
** 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')
/* reply code 421 is "Service Shutting Down" */
{
/* send the quit protocol */
}
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__
#else /* __STDC__ */
char *f;
MAILER *m;
#endif /* __STDC__ */
{
if (TrafficLogFile != NULL)
"%05d >>> %s\n", (int) CurrentPid,
{
: m->m_eol);
}
{
sm_dprintf("smtpmessage: NULL mci_out\n");
}
}