srvrsmtp.c revision 3ee0e49223f178da635734759b9167f924321ff0
/*
* 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.
*
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sendmail.h>
#if MILTER
#endif /* MILTER */
SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.924.2.2 2006/05/31 20:56:37 ca Exp $")
# include "sfsasl.h"
#endif /* SASL || STARTTLS */
#if SASL
#endif /* SASL */
#if STARTTLS
# include <sysexits.h>
static bool tls_ok_srv = false;
#if _FFR_DM_ONE
static bool NotFirstDelivery = false;
#endif /* _FFR_DM_ONE */
#endif /* STARTTLS */
/* server features */
#if PIPELINING
# if _FFR_NO_PIPE
# endif /* _FFR_NO_PIPE */
#endif /* PIPELINING */
bool, char *, ENVELOPE *));
unsigned int));
static void setup_smtpd_io __P((void));
#if SASL
# if SASL >= 20000
# define RESET_SASLCONN \
do \
{ \
sasl_ok = false; \
} while (0)
# else /* SASL >= 20000 */
struct sockaddr_in *_saddr_r,
struct sockaddr_in *_saddr_l,
# define RESET_SASLCONN \
do \
{ \
sasl_ok = false; \
} while (0)
# endif /* SASL >= 20000 */
#endif /* SASL */
extern ENVELOPE BlankEnvelope;
#define NBADRCPTS \
do \
{ \
char buf[16]; \
} while (0)
(s)++
/*
** SMTP -- run the SMTP protocol.
**
** Parameters:
** nullserver -- if non-NULL, rejection message for
** (almost) all SMTP commands.
** d_flags -- daemon flags
** e -- the envelope.
**
** Returns:
** never.
**
** Side Effects:
** Reads commands from the input channel and processes them.
*/
/*
** Notice: The smtp server doesn't have a session context like the client
** side has (mci). Therefore some data (session oriented) is allocated
** or assigned to the "wrong" structure (esp. STARTTLS, AUTH).
** This should be fixed in a successor version.
*/
struct cmd
{
char *cmd_name; /* command name */
int cmd_code; /* internal code, see below */
};
/* values for cmd_code */
#define CMDERROR 0 /* bad command */
#if SASL
#endif /* SASL */
#if STARTTLS
#endif /* STARTTLS */
/* non-standard commands */
/* unimplemented commands from RFC 821 */
/* use this to catch and log "door handle" attempts on your system */
/* debugging-only commands, only enabled if SMTPDEBUG is defined */
/*
** Note: If you change this list, remember to update 'helpfile'
*/
{
{ "mail", CMDMAIL },
{ "rcpt", CMDRCPT },
{ "data", CMDDATA },
{ "rset", CMDRSET },
{ "vrfy", CMDVRFY },
{ "expn", CMDEXPN },
{ "help", CMDHELP },
{ "noop", CMDNOOP },
{ "quit", CMDQUIT },
{ "helo", CMDHELO },
{ "ehlo", CMDEHLO },
{ "etrn", CMDETRN },
{ "verb", CMDVERB },
{ "send", CMDUNIMPL },
{ "saml", CMDUNIMPL },
{ "soml", CMDUNIMPL },
{ "turn", CMDUNIMPL },
#if SASL
{ "auth", CMDAUTH, },
#endif /* SASL */
#if STARTTLS
{ "starttls", CMDSTLS, },
#endif /* STARTTLS */
/* remaining commands are here only to trap and log attempts to use them */
{ "showq", CMDDBGQSHOW },
{ "debug", CMDDBGDEBUG },
{ "wiz", CMDLOGBOGUS },
};
static char *CurSmtpClient; /* who's at the other end of channel */
#ifndef MAXBADCOMMANDS
#endif /* ! MAXBADCOMMANDS */
#ifndef MAXHELOCOMMANDS
#endif /* ! MAXHELOCOMMANDS */
#ifndef MAXVRFYCOMMANDS
#endif /* ! MAXVRFYCOMMANDS */
#ifndef MAXETRNCOMMANDS
#endif /* ! MAXETRNCOMMANDS */
#ifndef MAXTIMEOUT
#endif /* ! MAXTIMEOUT */
/*
** Maximum shift value to compute timeout for bad commands.
** This introduces an upper limit of 2^MAXSHIFT for the timeout.
*/
#ifndef MAXSHIFT
# define MAXSHIFT 8
#endif /* ! MAXSHIFT */
#if MAXSHIFT > 31
#endif /* MAXSHIFT */
#if MAXBADCOMMANDS > 0
# define STOP_IF_ATTACK(r) do \
{ \
if ((r) == STOP_ATTACK) \
goto stopattack; \
} while (0)
#else /* MAXBADCOMMANDS > 0 */
# define STOP_IF_ATTACK(r) r
#endif /* MAXBADCOMMANDS > 0 */
#if SM_HEAP_CHECK
"@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $");
#endif /* SM_HEAP_CHECK */
typedef struct
{
bool sm_gotmail; /* mail command received */
unsigned int sm_nrcpts; /* number of successful RCPT commands */
bool sm_discard;
#if MILTER
bool sm_milterize;
bool sm_milterlist; /* any filters in the list? */
#endif /* MILTER */
char *sm_quarmsg; /* carry quarantining across messages */
} SMTP_T;
#define MSG_TEMPFAIL "451 4.3.2 Please try again later"
#if MILTER
# define MILTER_ABORT(e) milter_abort((e))
# define MILTER_REPLY(str) \
{ \
int savelogusrerrs = LogUsrErrs; \
\
switch (state) \
{ \
case SMFIR_SHUTDOWN: \
if (MilterLogLevel > 3) \
{ \
"Milter: %s=%s, reject=421, errormode=4", \
LogUsrErrs = false; \
} \
{ \
bool tsave = QuickAbort; \
\
QuickAbort = false; \
usrerr("421 4.3.0 closing connection"); \
QuickAbort = tsave; \
e->e_sendqueue = NULL; \
goto doquit; \
} \
break; \
case SMFIR_REPLYCODE: \
if (MilterLogLevel > 3) \
{ \
"Milter: %s=%s, reject=%s", \
LogUsrErrs = false; \
} \
{ \
bool tsave = QuickAbort; \
\
QuickAbort = false; \
QuickAbort = tsave; \
e->e_sendqueue = NULL; \
goto doquit; \
} \
else \
break; \
\
case SMFIR_REJECT: \
if (MilterLogLevel > 3) \
{ \
"Milter: %s=%s, reject=550 5.7.1 Command rejected", \
LogUsrErrs = false; \
} \
usrerr("550 5.7.1 Command rejected"); \
break; \
\
case SMFIR_DISCARD: \
if (MilterLogLevel > 3) \
"Milter: %s=%s, discard", \
e->e_flags |= EF_DISCARD; \
break; \
\
case SMFIR_TEMPFAIL: \
if (MilterLogLevel > 3) \
{ \
"Milter: %s=%s, reject=%s", \
LogUsrErrs = false; \
} \
usrerr(MSG_TEMPFAIL); \
break; \
} \
LogUsrErrs = savelogusrerrs; \
}
#else /* MILTER */
# define MILTER_ABORT(e)
#endif /* MILTER */
#define CLEAR_STATE(cmd) \
do \
{ \
/* abort milter filters */ \
MILTER_ABORT(e); \
\
{ \
} \
\
e->e_sendqueue = NULL; \
e->e_flags |= EF_CLRQUEUE; \
\
e->e_flags &= ~EF_LOGSENDER; \
\
/* clean up a bit */ \
smtp.sm_gotmail = false; \
SuprErrs = true; \
dropenvelope(e, true, false); \
sm_rpool_free(e->e_rpool); \
CurEnv = e; \
\
/* put back discard bit */ \
if (smtp.sm_discard) \
e->e_flags |= EF_DISCARD; \
\
/* restore connection quarantining */ \
{ \
} \
else \
{ \
smtp.sm_quarmsg); \
e->e_quarmsg); \
} \
} while (0)
/* sleep to flatten out connection load */
/* is it worth setting the process title for 1s? */
#define DELAY_CONN(cmd) \
{ \
\
sm_setproctitle(true, e, \
"%s: %s: delaying %s: load average: %d", \
qid_printname(e), CurSmtpClient, \
{ \
"delaying=%s, load average=%d >= %d", \
} \
(void) sleep(1); \
sm_setproctitle(true, e, "%s %s: %.80s", \
}
void
char *volatile nullserver;
register ENVELOPE *volatile e;
{
register char *volatile p;
char *cmd;
ADDRESS *a;
volatile bool gothello; /* helo command received */
bool vrfy; /* set if this is a vrfy command */
char *volatile protocol; /* sending protocol */
char *volatile sendinghost; /* sending hostname */
char *volatile peerhostname; /* name of SMTP peer or "localhost" */
auto char *delimptr;
char *id;
volatile unsigned int n_badcmds = 0; /* count of bad commands */
volatile unsigned int n_badrcpts = 0; /* number of rejected RCPT */
volatile unsigned int n_verifies = 0; /* count of VRFY/EXPN */
volatile unsigned int n_etrn = 0; /* count of ETRN */
volatile int save_sevenbitinput;
bool ok;
volatile bool first;
#endif /* _FFR_BLOCK_PROXIES */
volatile bool tempfail = false;
volatile bool lognullconnection = true;
register char *q;
char *addr;
char *greetcode = "220";
char *hostname; /* my hostname ($j) */
int argno;
char *args[MAXSMTPARGS];
#if SASL
volatile bool sasl_ok;
volatile unsigned int n_auth = 0; /* count of AUTH commands */
bool ismore;
int result;
volatile int authenticating;
char *user;
# if SASL >= 20000
char *auth_id;
const char *out;
# else /* SASL >= 20000 */
char *out;
const char *errstr;
struct sockaddr_in saddr_l;
struct sockaddr_in saddr_r;
# endif /* SASL >= 20000 */
unsigned int outlen;
char *volatile auth_type;
char *mechlist;
volatile unsigned int n_mechs;
unsigned int len;
#endif /* SASL */
int r;
#if STARTTLS
volatile bool tls_active = false;
bool saveQuickAbort;
bool saveSuprErrs;
#endif /* STARTTLS */
volatile unsigned int features;
#if PIPELINING
# if _FFR_NO_PIPE
int np_log = 0;
# endif /* _FFR_NO_PIPE */
#endif /* PIPELINING */
#if MILTER
smtp.sm_milterlist = false;
#endif /* MILTER */
/* setup I/O fd correctly for the SMTP server */
#if SM_HEAP_CHECK
{
}
#endif /* SM_HEAP_CHECK */
/* XXX the rpool should be set when e is initialized in main() */
settime(e);
sm_getla();
if (peerhostname == NULL)
peerhostname = "localhost";
if (CurSmtpClient == NULL)
/* check_relay may have set discard bit, save for later */
#if PIPELINING
/* auto-flush output when reading input */
#endif /* PIPELINING */
/* Set default features for server. */
? SRV_NONE : SRV_OFFER_VERB)))
? SRV_NONE : SRV_OFFER_DSN)
#if SASL
: SRV_NONE)
#endif /* SASL */
#if PIPELINING
#endif /* PIPELINING */
#if STARTTLS
: SRV_VRFY_CLT)
#endif /* STARTTLS */
;
if (nullserver == NULL)
{
{
if (LogLevel > 4)
"ERROR: srv_features=tempfail, relay=%.100s, access temporarily disabled",
nullserver = "450 4.3.0 Please try again later.";
}
else
{
#if PIPELINING
# if _FFR_NO_PIPE
{
/* for consistency */
features &= ~SRV_OFFER_PIPE;
}
# endif /* _FFR_NO_PIPE */
#endif /* PIPELINING */
#if SASL
else
#endif /* SASL */
}
}
{
goto doquit;
}
#if SASL
n_mechs = 0;
/* SASL server new connection */
if (sasl_ok)
{
# if SASL >= 20000
/* use empty realm: only works in SASL > 1.5.5 */
# else /* SASL >= 20000 */
/* use no realm -> realm is set to hostname by SASL lib */
&conn);
# endif /* SASL >= 20000 */
if (!sasl_ok)
{
if (LogLevel > 9)
"AUTH error: sasl_server_new failed=%d",
result);
}
}
if (sasl_ok)
{
/*
** SASL set properties for sasl
** XXX Cyrus SASL v1 only supports IPv4
**
** Kerberos_v4
*/
# if SASL >= 20000
# if NETINET6
# endif /* NETINET6 */
{
NULL),
&addrsize) == 0)
{
{
remoteip);
}
NULL),
&addrsize) == 0)
{
sizeof localip))
{
localip);
}
}
}
}
# endif /* NETINET || NETINET6 */
# else /* SASL >= 20000 */
# if NETINET
{
addrsize = sizeof(struct sockaddr_in);
NULL),
&addrsize) == 0)
{
addrsize = sizeof(struct sockaddr_in);
NULL),
&addrsize) == 0)
&saddr_l);
}
}
# endif /* NETINET */
# endif /* SASL >= 20000 */
# if 0
# endif /* 0 */
/* set properties */
/* XXX should these be options settable via .cf ? */
/* ssp.min_ssf = 0; is default due to memset() */
{
}
if (sasl_ok)
{
/*
** external security strength factor;
** currently we have none so zero
*/
# if SASL >= 20000
ext_ssf = 0;
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
}
if (sasl_ok)
}
#endif /* SASL */
#if STARTTLS
#endif /* STARTTLS */
#if MILTER
if (smtp.sm_milterize)
{
char state;
/* initialize mail filter connection */
switch (state)
{
case SMFIR_REJECT:
if (MilterLogLevel > 3)
"Milter: initialization failed, rejecting commands");
greetcode = "554";
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
"Milter: initialization failed, temp failing commands");
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
"Milter: initialization failed, closing connection");
tempfail = true;
smtp.sm_milterize = false;
message("421 4.7.0 %s closing connection",
/* arrange to ignore send list */
e->e_sendqueue = NULL;
goto doquit;
}
}
{
char state;
char *response;
e, &state);
switch (state)
{
case SMFIR_REPLYCODE: /* REPLYCODE shouldn't happen */
case SMFIR_REJECT:
if (MilterLogLevel > 3)
"Milter: connect: host=%s, addr=%s, rejecting commands",
greetcode = "554";
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
"Milter: connect: host=%s, addr=%s, temp failing commands",
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
"Milter: connect: host=%s, addr=%s, shutdown",
tempfail = true;
smtp.sm_milterize = false;
message("421 4.7.0 %s closing connection",
/* arrange to ignore send list */
e->e_sendqueue = NULL;
goto doquit;
}
}
#endif /* MILTER */
/*
** Broken proxies and SMTP slammers
** push data without waiting, catch them
*/
if (
#if STARTTLS
!smtps &&
#endif /* STARTTLS */
*greetcode == '2')
{
char **pvp;
/* Ask the rulesets how long to pause */
anynet_ntoa(&RealHostAddr), e,
{
}
if (msecs > 0)
{
int fd;
#endif /* _FFR_LOG_GREET_PAUSE */
/* pause for a moment */
/* Obey RFC 2821: 4.3.5.2: 220 timeout of 5 minutes */
{
}
/* check if data is on the socket during the pause */
#endif /* _FFR_LOG_GREET_PAUSE */
{
#endif /* _FFR_LOG_GREET_PAUSE */
greetcode = "554";
nullserver = "Command rejected";
"rejecting commands from %s [%s] after %d seconds due to pre-greeting traffic",
#else /* _FFR_LOG_GREET_PAUSE */
"rejecting commands from %s [%s] due to pre-greeting traffic",
#endif /* _FFR_LOG_GREET_PAUSE */
#endif /* _FFR_LOG_GREET_PAUSE */
);
}
}
}
#if STARTTLS
/* If this an smtps connection, start TLS now */
if (smtps)
{
Errors = 0;
goto starttls;
}
#endif /* STARTTLS */
/* output the first line, inserting "ESMTP" as second word */
if (*greetcode == '5')
hostname);
else
if (p != NULL)
*p++ = '\0';
if (p == NULL)
"%s %%.*s ESMTP%%s", greetcode);
else
"%s-%%.*s ESMTP%%s", greetcode);
/* output remaining lines */
{
*p++ = '\0';
id++;
}
{
id++;
}
else
/* sendinghost's storage must outlive the current envelope */
if (sendinghost != NULL)
first = true;
#endif /* _FFR_BLOCK_PROXIES */
gothello = false;
smtp.sm_gotmail = false;
for (;;)
{
{
QuickAbort = false;
HoldErrs = false;
SuprErrs = false;
LogUsrErrs = false;
OnlyOneError = true;
/* setup for the read */
Errors = 0;
/* read the input line */
SmtpPhase = "server cmd read";
#if SASL
/*
** XXX SMTP AUTH requires accepting any length,
*/
#endif /* SASL */
/* handle errors */
if (sm_io_error(OutChannel) ||
{
char *d;
if (d == NULL)
d = "stdin";
/* end of file, just die */
disconnect(1, e);
#if MILTER
/* close out milter filters */
milter_quit(e);
#endif /* MILTER */
message("421 4.4.1 %s Lost input channel from %s",
"lost input channel from %s to %s after %s",
CurSmtpClient, d,
/*
** If have not accepted mail (DATA), do not bounce
** bad addresses back to sender.
*/
e->e_sendqueue = NULL;
goto doquit;
}
if (first)
{
int idx;
char *http_cmd;
idx++)
{
{
/* Open proxy, drop it */
message("421 4.7.0 %s Rejecting open proxy %s",
"%s: probable open proxy: command=%.40s",
CurSmtpClient, inp);
goto doquit;
}
}
first = false;
}
#endif /* _FFR_BLOCK_PROXIES */
/* clean up end of line */
#if PIPELINING
# if _FFR_NO_PIPE
/*
** if there is more input and pipelining is disabled:
** delay ... (and maybe discard the input?)
** XXX this doesn't really work, at least in tests using
** telnet SM_IO_IS_READABLE only returns 1 if there were
** more than 2 input lines available.
*/
{
if (++np_log < 3)
"unauthorized PIPELINING, sleeping");
sleep(1);
}
# endif /* _FFR_NO_PIPE */
#endif /* PIPELINING */
#if SASL
if (authenticating == SASL_PROC_AUTH)
{
# if 0
if (*inp == '\0')
{
message("501 5.5.2 missing input");
continue;
}
# endif /* 0 */
{
/* rfc 2254 4. */
message("501 5.0.0 AUTH aborted");
continue;
}
/* could this be shorter? XXX */
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
{
/* rfc 2254 4. */
message("501 5.5.4 cannot decode AUTH parameter %s",
inp);
# if SASL >= 20000
# endif /* SASL >= 20000 */
continue;
}
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
/* get an OK if we're done */
{
message("235 2.0.0 OK Authenticated");
# if SASL >= 20000
/* get security strength (features) */
(const void **) &ssf);
# else /* SASL >= 20000 */
(void **)&user);
{
user = "";
}
else
{
macid("{auth_authen}"),
}
# if 0
/* get realm? */
# endif /* 0 */
/* get security strength (features) */
(void **) &ssf);
# endif /* SASL >= 20000 */
{
}
else
{
char pbuf[8];
"%u", *ssf);
sm_dprintf("AUTH auth_ssf: %u\n",
*ssf);
}
/*
** Only switch to encrypted connection
** if a security layer has been negotiated
*/
{
int tmo;
/*
** Convert I/O layer to use SASL.
** If the call fails, the connection
** is aborted.
*/
{
/* restart dialogue */
n_helo = 0;
# if PIPELINING
(void) sm_io_autoflush(InChannel,
# endif /* PIPELINING */
}
else
syserr("503 5.3.3 SASL TLS failed");
}
/* NULL pointer ok since it's our function */
if (LogLevel > 8)
"AUTH=server, relay=%s, authid=%.128s, mech=%.16s, bits=%d",
}
else if (result == SASL_CONTINUE)
{
&out2len);
{
/* correct code? XXX */
/* 454 Temp. authentication failure */
message("454 4.5.4 Internal error: unable to encode64");
if (LogLevel > 5)
"AUTH encode64 error [%d for \"%s\"]",
/* start over? */
}
else
{
sm_dprintf("AUTH continue: msg='%s' len=%u\n",
}
# if SASL >= 20000
# endif /* SASL >= 20000 */
}
else
{
/* not SASL_OK or SASL_CONT */
message("535 5.7.0 authentication failed");
if (LogLevel > 9)
"AUTH failure (%s): %s (%d) %s",
NULL),
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
}
}
else
{
/* don't want to do any of this if authenticating */
#endif /* SASL */
/* echo command to transcript */
"<<< %s\n", inp);
if (LogLevel > 14)
/* break off command */
continue;
while (*p != '\0' &&
*cmd++ = *p++;
*cmd = '\0';
/* throw away leading whitespace */
SKIP_SPACE(p);
/* decode command */
{
break;
}
/* reset errors */
errno = 0;
/* check whether a "non-null" command has been used */
switch (c->cmd_code)
{
#if SASL
case CMDAUTH:
/* avoid information leak; take first two words? */
q = "AUTH";
break;
#endif /* SASL */
case CMDMAIL:
case CMDEXPN:
case CMDVRFY:
case CMDETRN:
lognullconnection = false;
/* FALLTHROUGH */
default:
q = inp;
break;
}
sm_setproctitle(true, e, "%s: %.80s",
CurSmtpClient, q);
else
sm_setproctitle(true, e, "%s %s: %.80s",
qid_printname(e),
CurSmtpClient, q);
/*
** Process command.
**
** If we are running as a null server, return 550
** to almost everything.
*/
{
switch (c->cmd_code)
{
case CMDQUIT:
case CMDHELO:
case CMDEHLO:
case CMDNOOP:
case CMDRSET:
case CMDERROR:
/* process normally */
break;
case CMDETRN:
nullserver == NULL)
break;
DELAY_CONN("ETRN");
/* FALLTHROUGH */
default:
#if MAXBADCOMMANDS > 0
/* theoretically this could overflow */
if (nullserver != NULL &&
++n_badcmds > MAXBADCOMMANDS)
{
message("421 4.7.0 %s Too many bad commands; closing connection",
/* arrange to ignore send list */
e->e_sendqueue = NULL;
goto doquit;
}
#endif /* MAXBADCOMMANDS > 0 */
if (nullserver != NULL)
{
if (ISSMTPREPLY(nullserver))
else
usrerr("550 5.0.0 %s",
}
else
usrerr("452 4.4.5 Insufficient disk space; try again later");
continue;
}
}
switch (c->cmd_code)
{
#if SASL
case CMDAUTH: /* sasl */
DELAY_CONN("AUTH");
{
message("503 5.3.3 AUTH not available");
break;
}
if (authenticating == SASL_IS_AUTH)
{
message("503 5.5.0 Already Authenticated");
break;
}
if (smtp.sm_gotmail)
{
message("503 5.5.0 AUTH not permitted during a mail transaction");
break;
}
if (tempfail)
{
if (LogLevel > 9)
"SMTP AUTH command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
usrerr("454 4.3.0 Please try again later");
break;
}
ismore = false;
/* crude way to avoid crack attempts */
true, "AUTH", e));
/* make sure mechanism (p) is a valid string */
for (q = p; *q != '\0' && isascii(*q); q++)
{
if (isspace(*q))
{
*q = '\0';
while (*++q != '\0' &&
continue;
*(q - 1) = '\0';
ismore = (*q != '\0');
break;
}
}
if (*p == '\0')
{
message("501 5.5.2 AUTH mechanism must be specified");
break;
}
/* check whether mechanism is available */
{
message("504 5.3.3 AUTH mechanism %.32s not available",
p);
break;
}
if (ismore)
{
/* could this be shorter? XXX */
# if SASL >= 20000
# else /* SASL >= 20000 */
&inlen);
# endif /* SASL >= 20000 */
{
message("501 5.5.4 cannot BASE64 decode '%s'",
q);
if (LogLevel > 5)
"AUTH decode64 error [%d for \"%s\"]",
result, q);
/* start over? */
# if SASL >= 20000
# endif /* SASL >= 20000 */
inlen = 0;
break;
}
}
else
{
inlen = 0;
}
/* see if that auth type exists */
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
{
message("535 5.7.0 authentication failed");
if (LogLevel > 9)
"AUTH failure (%s): %s (%d) %s",
p,
NULL),
# if SASL >= 20000
# else /* SASL >= 20000 */
errstr);
# endif /* SASL >= 20000 */
break;
}
{
/* ugly, but same code */
goto authenticated;
/* authenticated by the initial response */
}
/* len is at least 2 */
&out2len);
{
message("454 4.5.4 Temporary authentication failure");
if (LogLevel > 5)
"AUTH encode64 error [%d for \"%s\"]",
/* start over? */
}
else
{
}
# if SASL >= 20000
# endif /* SASL >= 20000 */
break;
#endif /* SASL */
#if STARTTLS
case CMDSTLS: /* starttls */
DELAY_CONN("STARTTLS");
if (*p != '\0')
{
message("501 5.5.2 Syntax error (no parameters allowed)");
break;
}
{
message("503 5.5.0 TLS not available");
break;
}
if (!tls_ok_srv)
{
message("454 4.3.3 TLS not available after start");
break;
}
if (smtp.sm_gotmail)
{
message("503 5.5.0 TLS not permitted during a mail transaction");
break;
}
if (tempfail)
{
if (LogLevel > 9)
"SMTP STARTTLS command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
usrerr("454 4.7.0 Please try again later");
break;
}
# if TLS_NO_RSA
/*
** XXX do we need a temp key ?
*/
# else /* TLS_NO_RSA */
# endif /* TLS_NO_RSA */
# if TLS_VRFY_PER_CTX
/*
** Note: this sets the verification globally
** (per SSL_CTX)
** it's ok since it applies only to one transaction
*/
# endif /* TLS_VRFY_PER_CTX */
{
message("454 4.3.3 TLS not available: error generating SSL handle");
if (LogLevel > 8)
tlslogerr("server");
goto tls_done;
}
# if !TLS_VRFY_PER_CTX
/*
** this could be used if it were possible to set
** verification per SSL (connection)
** not just per SSL_CTX (global)
*/
# endif /* !TLS_VRFY_PER_CTX */
{
message("454 4.3.3 TLS not available: error set fd");
goto tls_done;
}
if (!smtps)
message("220 2.0.0 Ready to start TLS");
# if PIPELINING
# endif /* PIPELINING */
# define SSL_ACC(s) SSL_accept(s)
{
int i, ssl_err;
"server");
if (i > 0)
goto ssl_retry;
if (LogLevel > 5)
{
"STARTTLS=server, error: accept failed=%d, SSL_error=%d, errno=%d, retry=%d",
if (LogLevel > 8)
tlslogerr("server");
}
tls_ok_srv = false;
/*
** according to the next draft of
** RFC 2487 the connection should be dropped
*/
/* arrange to ignore any current send list */
e->e_sendqueue = NULL;
goto doquit;
}
/* ignore return code for now, it's in {verify} */
(void) tls_get_info(srv_ssl, true,
/*
** call Stls_client to find out whether
** to accept the connection from the client
*/
SuprErrs = true;
QuickAbort = false;
if (rscheck("tls_client",
"STARTTLS", e,
Errors > 0)
{
extern char MsgBuf[];
else
nullserver = "503 5.7.0 Authentication required.";
}
tls_ok_srv = false; /* don't offer STARTTLS again */
n_helo = 0;
# if SASL
if (sasl_ok)
{
int cipher_bits;
bool verified;
char *s, *v, *c;
{
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
if (sasl_ok)
&mechlist);
}
}
# endif /* SASL */
/* switch to secure connection */
{
tls_active = true;
# if PIPELINING
# endif /* PIPELINING */
}
else
{
/*
** XXX this is an internal error
** how to deal with it?
** we can't generate an error message
** since the other side switched to an
** encrypted layer, but we could not...
** just "hang up"?
*/
nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer";
syserr("STARTTLS: can't switch to encrypted layer");
}
if (smtps)
{
if (tls_active)
goto greeting;
else
goto doquit;
}
break;
#endif /* STARTTLS */
case CMDHELO: /* hello -- introduce yourself */
case CMDEHLO: /* extended hello */
DELAY_CONN("EHLO");
{
protocol = "ESMTP";
SmtpPhase = "server EHLO";
}
else
{
protocol = "SMTP";
SmtpPhase = "server HELO";
}
/* avoid denial-of-service */
#if 0
if (gothello)
{
break;
}
#endif /* 0 */
/* check for valid domain name (re 1123 5.2.5) */
if (*p == '\0' && !AllowBogusHELO)
{
usrerr("501 %s requires domain address",
cmdbuf);
break;
}
/* check for long domain name (hides Received: info) */
{
usrerr("501 Invalid domain name");
if (LogLevel > 9)
"invalid domain name (too long) from %s",
break;
}
ok = true;
for (q = p; *q != '\0'; q++)
{
if (!isascii(*q))
break;
if (isalnum(*q))
continue;
if (isspace(*q))
{
*q = '\0';
/* only complain if strict check */
ok = AllowBogusHELO;
/* allow trailing whitespace */
while (!ok && *++q != '\0' &&
isspace(*q))
;
if (*q == '\0')
ok = true;
break;
}
break;
}
if (*q == '\0' && ok)
{
q = "pleased to meet you";
sendinghost = sm_strdup_x(p);
}
else if (!AllowBogusHELO)
{
usrerr("501 Invalid domain name");
if (LogLevel > 9)
"invalid domain name (%s) from %.100s",
p, CurSmtpClient);
break;
}
else
{
q = "accepting invalid domain name";
}
#if MILTER
{
char state;
char *response;
switch (state)
{
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
"Milter: helo=%s, reject=%s",
p, response);
smtp.sm_milterize = false;
break;
case SMFIR_REJECT:
if (MilterLogLevel > 3)
"Milter: helo=%s, reject=Command rejected",
p);
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
"Milter: helo=%s, reject=%s",
p, MSG_TEMPFAIL);
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
"Milter: helo=%s, reject=421 4.7.0 %s closing connection",
p, MyHostName);
tempfail = true;
smtp.sm_milterize = false;
message("421 4.7.0 %s closing connection",
/* arrange to ignore send list */
e->e_sendqueue = NULL;
goto doquit;
}
/*
** save between messages
*/
}
#endif /* MILTER */
gothello = true;
/* print HELO response message */
{
message("250 %s Hello %s, %s",
MyHostName, CurSmtpClient, q);
break;
}
message("250-%s Hello %s, %s",
MyHostName, CurSmtpClient, q);
/* offer ENHSC even for nullserver */
if (nullserver != NULL)
{
message("250 ENHANCEDSTATUSCODES");
break;
}
/*
** print EHLO features list
**
** Note: If you change this list,
** remember to update 'helpfile'
*/
message("250-ENHANCEDSTATUSCODES");
#if PIPELINING
message("250-PIPELINING");
#endif /* PIPELINING */
{
message("250-EXPN");
message("250-VERB");
}
#if MIME8TO7
message("250-8BITMIME");
#endif /* MIME8TO7 */
if (MaxMessageSize > 0)
else
message("250-SIZE");
#if DSN
message("250-DSN");
#endif /* DSN */
message("250-ETRN");
#if SASL
#endif /* SASL */
#if STARTTLS
if (tls_ok_srv &&
message("250-STARTTLS");
#endif /* STARTTLS */
if (DeliverByMin > 0)
message("250-DELIVERBY %ld",
(long) DeliverByMin);
else if (DeliverByMin == 0)
message("250-DELIVERBY");
/* < 0: no deliver-by */
message("250 HELP");
break;
case CMDMAIL: /* mail -- designate sender */
SmtpPhase = "server MAIL";
DELAY_CONN("MAIL");
/* check for validity of this command */
{
usrerr("503 5.0.0 Polite people say HELO first");
break;
}
if (smtp.sm_gotmail)
{
usrerr("503 5.5.0 Sender already specified");
break;
}
#if SASL
{
usrerr("530 5.7.0 Authentication required");
break;
}
#endif /* SASL */
p = skipword(p, "from");
if (p == NULL)
break;
if (tempfail)
{
if (LogLevel > 9)
"SMTP MAIL command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
break;
}
/* make sure we know who the sending host is */
if (sendinghost == NULL)
#if SM_HEAP_CHECK
{
sm_dprintf("smtp() heap group #%d\n",
sm_heap_group());
}
#endif /* SM_HEAP_CHECK */
if (Errors > 0)
goto undo_no_pm;
if (!gothello)
{
auth_warning(e, "%s didn't use HELO protocol",
}
#ifdef PICKY_HELO_CHECK
{
auth_warning(e, "Host %s claimed to be %s",
}
#endif /* PICKY_HELO_CHECK */
protocol = "SMTP";
if (Errors > 0)
goto undo_no_pm;
n_badrcpts = 0;
"0");
e->e_flags |= EF_CLRQUEUE;
sm_setproctitle(true, e, "%s %s: %.80s",
qid_printname(e),
CurSmtpClient, inp);
/* do the processing */
{
extern char *FullName;
QuickAbort = true;
/* must parse sender first */
*delimptr++ = '\0';
if (Errors > 0)
/* Successfully set e_from, allow logging */
e->e_flags |= EF_LOGSENDER;
/* put resulting triple from parseaddr() into macros */
macid("{mail_mailer}"),
else
macid("{mail_host}"),
else
macid("{mail_addr}"),
else
if (Errors > 0)
/* check for possible spoofing */
{
auth_warning(e, "%s owned process doing -bs",
}
/* reset to default value */
/* now parse ESMTP arguments */
e->e_msgsize = 0;
addr = p;
argno = 0;
p = delimptr;
while (p != NULL && *p != '\0')
{
char *kp;
/* locate the beginning of the keyword */
SKIP_SPACE(p);
if (*p == '\0')
break;
kp = p;
/* skip to the value portion */
p++;
if (*p == '=')
{
equal = p;
*p++ = '\0';
vp = p;
/* skip to the end of the value */
while (*p != '\0' && *p != ' ' &&
*p != '=')
p++;
}
if (*p != '\0')
*p++ = '\0';
*equal = '=';
usrerr("501 5.5.4 Too many parameters");
if (Errors > 0)
}
if (Errors > 0)
#if SASL
# if _FFR_AUTH_PASSING
/* set the default AUTH= if the sender didn't */
if (e->e_auth_param == NULL)
{
/* XXX only do this for an MSA? */
e);
if (e->e_auth_param == NULL)
e->e_auth_param = "<>";
/*
** XXX should we invoke Strust_auth now?
** authorizing as the client that just
** authenticated, so we'll trust implicitly
*/
}
# endif /* _FFR_AUTH_PASSING */
#endif /* SASL */
/* do config file checking of the sender */
#if _FFR_MAIL_MACRO
/* make the "real" sender address available */
#endif /* _FFR_MAIL_MACRO */
Errors > 0)
if (MaxMessageSize > 0 &&
(e->e_msgsize > MaxMessageSize ||
e->e_msgsize < 0))
{
usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)",
}
/*
** XXX always check whether there is at least one fs
** with enough space?
** However, this may not help much: the queue group
** selection may later on select a FS that hasn't
** enough space.
*/
!enoughdiskspace(e->e_msgsize, e)
#if _FFR_ANY_FREE_FS
&& !filesys_free(e->e_msgsize)
#endif /* _FFR_ANY_FREE_FS */
)
{
/*
** We perform this test again when the
** queue directory is selected, in collect.
*/
usrerr("452 4.4.5 Insufficient disk space; try again later");
}
if (Errors > 0)
LogUsrErrs = true;
#if MILTER
{
char state;
char *response;
MILTER_REPLY("from");
}
#endif /* MILTER */
if (Errors > 0)
message("250 2.1.0 Sender ok");
smtp.sm_gotmail = true;
}
{
/*
** An error occurred while processing a MAIL command.
** Jump to the common error handling code.
*/
goto undo_no_pm;
}
break;
e->e_flags &= ~EF_PM_NOTIFY;
undo:
break;
case CMDRCPT: /* rcpt -- designate recipient */
DELAY_CONN("RCPT");
if (BadRcptThrottle > 0 &&
{
if (LogLevel > 5 &&
{
"%s: Possible SMTP RCPT flood, throttling.",
/* To avoid duplicated message */
n_badrcpts++;
}
/*
** Don't use exponential backoff for now.
** Some servers will open more connections
** and actually overload the receiver even
** more.
*/
(void) sleep(1);
}
if (!smtp.sm_gotmail)
{
usrerr("503 5.0.0 Need MAIL before RCPT");
break;
}
SmtpPhase = "server RCPT";
{
QuickAbort = true;
LogUsrErrs = true;
/* limit flooding of our machine */
if (MaxRcptPerMsg > 0 &&
{
/* sleep(1); / * slow down? */
usrerr("452 4.5.3 Too many recipients");
goto rcpt_done;
}
if (e->e_sendmode != SM_DELIVER
#if _FFR_DM_ONE
#endif /* _FFR_DM_ONE */
)
e->e_flags |= EF_VRFYONLY;
#if MILTER
/*
** If the filter will be deleting recipients,
** don't expand them at RCPT time (in the call
** to recipient()). If they are expanded, it
** is impossible for removefromlist() to figure
** out the expanded members of the original
** recipient and mark them as QS_DONTSEND.
*/
if (milter_can_delrcpts())
e->e_flags |= EF_VRFYONLY;
#endif /* MILTER */
p = skipword(p, "to");
if (p == NULL)
goto rcpt_done;
e, true);
if (Errors > 0)
goto rcpt_done;
if (a == NULL)
{
usrerr("501 5.0.0 Missing recipient");
goto rcpt_done;
}
*delimptr++ = '\0';
/* put resulting triple from parseaddr() into macros */
macid("{rcpt_mailer}"),
else
else
else
if (Errors > 0)
goto rcpt_done;
/* now parse ESMTP arguments */
addr = p;
argno = 0;
p = delimptr;
while (p != NULL && *p != '\0')
{
char *kp;
/* locate the beginning of the keyword */
SKIP_SPACE(p);
if (*p == '\0')
break;
kp = p;
/* skip to the value portion */
p++;
if (*p == '=')
{
equal = p;
*p++ = '\0';
vp = p;
/* skip to the end of the value */
while (*p != '\0' && *p != ' ' &&
*p != '=')
p++;
}
if (*p != '\0')
*p++ = '\0';
*equal = '=';
usrerr("501 5.5.4 Too many parameters");
if (Errors > 0)
break;
}
if (Errors > 0)
goto rcpt_done;
/* do config file checking of the recipient */
Errors > 0)
goto rcpt_done;
/* If discarding, don't bother to verify user */
a->q_state = QS_VERIFIED;
#if MILTER
{
char state;
char *response;
MILTER_REPLY("to");
}
#endif /* MILTER */
if (Errors > 0)
goto rcpt_done;
/* save in recipient list after ESMTP mods */
a = recipient(a, &e->e_sendqueue, 0, e);
if (Errors > 0)
goto rcpt_done;
/* no errors during parsing, but might be a duplicate */
if (!QS_IS_BADADDR(a->q_state))
{
initsys(e);
message("250 2.1.5 Recipient ok%s",
QS_IS_QUEUEUP(a->q_state) ?
" (will queue)" : "");
}
else
{
/* punt -- should keep message in ADDRESS.... */
usrerr("550 5.1.1 Addressee unknown");
}
if (Errors > 0)
{
++n_badrcpts;
}
}
{
/* An exception occurred while processing RCPT */
++n_badrcpts;
}
break;
case CMDDATA: /* data -- text of mail */
DELAY_CONN("DATA");
goto doquit;
break;
case CMDRSET: /* rset -- reset state */
message("451 4.0.0 Test failure");
else
message("250 2.0.0 Reset state");
break;
case CMDVRFY: /* vrfy -- verify address */
case CMDEXPN: /* expn -- expand address */
if (tempfail)
{
if (LogLevel > 9)
"SMTP %s command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
/* RFC 821 doesn't allow 4xy reply code */
usrerr("550 5.7.1 Please try again later");
break;
}
{
if (vrfy)
message("252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)");
else
message("502 5.7.0 Sorry, we do not allow this operation");
if (LogLevel > 5)
"%s: %s [rejected]",
break;
}
else if (!gothello &&
{
usrerr("503 5.0.0 I demand that you introduce yourself first");
break;
}
if (Errors > 0)
break;
if (LogLevel > 5)
{
QuickAbort = true;
if (vrfy)
e->e_flags |= EF_VRFYONLY;
p++;
if (*p == '\0')
{
usrerr("501 5.5.2 Argument required");
}
else
{
/* do config file checking of the address */
p, NULL, e, RSF_RMCOMM,
Errors > 0)
}
if (wt > 0)
{
time_t t;
if (t > 0)
(void) sleep(t);
}
if (Errors > 0)
{
}
{
{
continue;
}
/* see if there is more in the vrfy list */
a = vrfyqueue;
(!QS_IS_UNDELIVERED(a->q_state)))
continue;
vrfyqueue = a;
}
}
{
/*
*/
goto undo;
}
break;
case CMDETRN: /* etrn -- force queue flush */
DELAY_CONN("ETRN");
/* Don't leak queue information via debug flags */
{
/* different message for MSA ? */
message("502 5.7.0 Sorry, we do not allow this operation");
if (LogLevel > 5)
"%s: %s [rejected]",
break;
}
if (tempfail)
{
if (LogLevel > 9)
"SMTP ETRN command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
break;
}
if (strlen(p) <= 0)
{
usrerr("500 5.5.2 Parameter required");
break;
}
/* crude way to avoid denial-of-service attacks */
true, "ETRN", e));
/*
** Do config file checking of the parameter.
** Even though we have srv_features now, we still
** need this ruleset because the former is called
** when the connection has been established, while
** this ruleset is called when the command is
** actually issued and therefore has all information
** available to make a decision.
*/
Errors > 0)
break;
if (LogLevel > 5)
"%s: ETRN %s", CurSmtpClient,
shortenstring(p, MAXSHORTSTR));
id = p;
if (*id == '#')
{
int i, qgrp;
id++;
if (!ISVALIDQGRP(qgrp))
{
usrerr("459 4.5.4 Queue %s unknown",
id);
break;
}
i++)
break;
}
if (*id == '@')
id++;
else
*--id = '@';
{
syserr("500 5.5.0 ETRN out of memory");
break;
}
new->queue_negate = false;
message("250 2.0.0 Queuing for node %s started", p);
break;
case CMDHELP: /* help -- give user info */
DELAY_CONN("HELP");
help(p, e);
break;
case CMDNOOP: /* noop -- do nothing */
DELAY_CONN("NOOP");
true, "NOOP", e));
message("250 2.0.0 OK");
break;
case CMDQUIT: /* quit -- leave mail */
#if PIPELINING
#endif /* PIPELINING */
/* arrange to ignore any current send list */
e->e_sendqueue = NULL;
#if STARTTLS
/* shutdown TLS connection */
if (tls_active)
{
tls_active = false;
}
#endif /* STARTTLS */
#if SASL
if (authenticating == SASL_IS_AUTH)
{
sasl_dispose(&conn);
/* XXX sasl_done(); this is a child */
}
#endif /* SASL */
/* avoid future 050 messages */
disconnect(1, e);
#if MILTER
/* close out milter filters */
milter_quit(e);
#endif /* MILTER */
e->e_flags &= ~EF_LOGSENDER;
nullserver == NULL)
{
char *d;
if (d == NULL)
d = "stdin";
/*
** even though this id is "bogus", it makes
** it simpler to "grep" related events, e.g.,
** timeouts for the same connection.
*/
CurSmtpClient, d);
}
{
/* return to handle next connection */
return;
}
/* NOTREACHED */
/* just to avoid bogus warning from some compilers */
case CMDVERB: /* set verbose mode */
DELAY_CONN("VERB");
{
/* this would give out the same info */
message("502 5.7.0 Verbose unavailable");
break;
}
true, "VERB", e));
Verbose = 1;
message("250 2.0.0 Verbose mode");
break;
#if SMTPDEBUG
case CMDDBGQSHOW: /* show queues */
"Send Queue=");
break;
case CMDDBGDEBUG: /* set debug mode */
tTflag(p);
message("200 2.0.0 Debug set");
break;
#else /* SMTPDEBUG */
case CMDDBGQSHOW: /* show queues */
case CMDDBGDEBUG: /* set debug mode */
#endif /* SMTPDEBUG */
case CMDLOGBOGUS: /* bogus command */
DELAY_CONN("Bogus");
if (LogLevel > 0)
"\"%s\" command from %s (%.100s)",
c->cmd_name, CurSmtpClient,
/* FALLTHROUGH */
case CMDERROR: /* unknown command */
#if MAXBADCOMMANDS > 0
if (++n_badcmds > MAXBADCOMMANDS)
{
message("421 4.7.0 %s Too many bad commands; closing connection",
/* arrange to ignore any current send list */
e->e_sendqueue = NULL;
goto doquit;
}
#endif /* MAXBADCOMMANDS > 0 */
{
char state;
char *response;
if (MilterLogLevel > 9)
"Sending \"%s\" to Milter", inp);
MILTER_REPLY("unknown");
if (state == SMFIR_REPLYCODE ||
state == SMFIR_REJECT ||
state == SMFIR_TEMPFAIL ||
state == SMFIR_SHUTDOWN)
{
/* MILTER_REPLY already gave an error */
break;
}
}
#endif /* MILTER && SMFI_VERSION > 2 */
usrerr("500 5.5.1 Command unrecognized: \"%s\"",
break;
case CMDUNIMPL:
DELAY_CONN("Unimpl");
usrerr("502 5.5.1 Command not implemented: \"%s\"",
break;
default:
DELAY_CONN("default");
errno = 0;
break;
}
#if SASL
}
#endif /* SASL */
}
{
/*
** The only possible exception is "E:mta.quickabort".
** There is nothing to do except fall through and loop.
*/
}
}
}
/*
** SMTP_DATA -- implement the SMTP DATA command.
**
** Parameters:
** smtp -- status of SMTP connection.
** e -- envelope.
**
** Returns:
** true iff SMTP session can continue.
**
** Side Effects:
** possibly sends message.
*/
static bool
ENVELOPE *e;
{
#if MILTER
bool milteraccept;
#endif /* MILTER */
bool aborting;
bool doublequeue;
ADDRESS *a;
char *id;
char *oldid;
char buf[32];
bool rv = true;
SmtpPhase = "server DATA";
if (!smtp->sm_gotmail)
{
usrerr("503 5.0.0 Need MAIL command");
return true;
}
{
usrerr("503 5.0.0 Need RCPT (recipient)");
return true;
}
return true;
{
char state;
char *response;
int savelogusrerrs = LogUsrErrs;
switch (state)
{
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
{
"Milter: cmd=data, reject=%s",
response);
LogUsrErrs = false;
}
{
e->e_sendqueue = NULL;
return false;
}
return true;
case SMFIR_REJECT:
if (MilterLogLevel > 3)
{
"Milter: cmd=data, reject=550 5.7.1 Command rejected");
LogUsrErrs = false;
}
usrerr("550 5.7.1 Command rejected");
return true;
case SMFIR_DISCARD:
if (MilterLogLevel > 3)
"Milter: cmd=data, discard");
e->e_flags |= EF_DISCARD;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
{
"Milter: cmd=data, reject=%s",
LogUsrErrs = false;
}
return true;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
{
"Milter: cmd=data, reject=421 4.7.0 %s closing connection",
LogUsrErrs = false;
}
e->e_sendqueue = NULL;
return false;
}
}
#endif /* MILTER && SMFI_VERSION > 3 */
/* put back discard bit */
if (smtp->sm_discard)
e->e_flags |= EF_DISCARD;
/* check to see if we need to re-expand aliases */
/* also reset QS_BADADDR on already-diagnosted addrs */
doublequeue = false;
{
if (QS_IS_VERIFIED(a->q_state) &&
{
/* need to re-expand aliases */
doublequeue = true;
}
if (QS_IS_BADADDR(a->q_state))
{
/* make this "go away" */
a->q_state = QS_DONTSEND;
}
}
/* collect the text of the message */
SmtpPhase = "collect";
/* redefine message size */
#if _FFR_CHECK_EOM
/* rscheck() will set Errors or EF_DISCARD if it trips */
#endif /* _FFR_CHECK_EOM */
#if MILTER
milteraccept = true;
Errors <= 0 &&
{
char state;
char *response;
switch (state)
{
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
"Milter: data, reject=%s",
response);
milteraccept = false;
break;
case SMFIR_REJECT:
milteraccept = false;
if (MilterLogLevel > 3)
"Milter: data, reject=554 5.7.1 Command rejected");
usrerr("554 5.7.1 Command rejected");
break;
case SMFIR_DISCARD:
if (MilterLogLevel > 3)
"Milter: data, discard");
milteraccept = false;
e->e_flags |= EF_DISCARD;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
"Milter: data, reject=%s",
milteraccept = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
"Milter: data, reject=421 4.7.0 %s closing connection",
milteraccept = false;
rv = false;
break;
}
}
/* Milter may have changed message size */
/* abort message filters that didn't get the body & log msg is OK */
{
milter_abort(e);
}
/*
** If SuperSafe is SAFE_REALLY_POSTMILTER, and we don't have milter or
** milter accepted message, sync it now
**
** XXX This is almost a copy of the code in collect(): put it into
** a function that is called from both places?
*/
{
int afd;
char *dfname;
{
int save_errno;
save_errno = errno;
if (save_errno == EEXIST)
{
int dfd;
syserr("@collect: bfcommit(%s): already on disk, size=%ld",
if (dfd >= 0)
}
errno = save_errno;
flush_errors(true);
}
{
flush_errors(true);
/* NOTREACHED */
}
{
flush_errors(true);
/* NOTREACHED */
}
{
flush_errors(true);
/* NOTREACHED */
}
/* Now reopen the df file */
SM_IO_RDONLY, NULL);
{
/* we haven't acked receipt yet, so just chuck this */
/* NOTREACHED */
}
}
#endif /* MILTER */
/* Check if quarantining stats should be updated */
/*
** set EF_DISCARD, don't queueup the message --
** that would lose the EF_DISCARD bit and deliver
** the message.
*/
doublequeue = false;
!split_by_recipient(e))
if (aborting)
{
/* Log who the mail would have gone to */
flush_errors(true);
goto abortmessage;
}
/* from now on, we have to operate silently */
#if 0
/*
** Clear message, it may contain an error from the SMTP dialogue.
** This error must not show up in the queue.
** Some error message should show up, e.g., alias database
** not available, but others shouldn't, e.g., from check_rcpt.
*/
#endif /* 0 */
/*
** Arrange to send to everyone.
** If sending to multiple people, mail back
** errors rather than reporting directly.
** In any case, don't mail back errors for
** anything that has happened up to
** now (the other end will do this).
** Truncate our transcript -- the mail has gotten
** to us successfully, and if we have
** to mail this back, it will be easier
** on the reader.
** Then send to everyone.
** Finally give a reply code. If an error has
** already been given, don't mail a
** message back.
** We goose error returns by clearing error bit.
*/
SmtpPhase = "delivery";
#if NAMED_BIND
#endif /* NAMED_BIND */
{
/* make sure we actually do delivery */
/* from now on, operate silently */
if (doublequeue)
{
/* make sure it is in the queue */
}
else
{
int mode;
/* send to all recipients */
mode = SM_DEFAULT;
#if _FFR_DM_ONE
if (SM_DM_ONE == e->e_sendmode)
{
if (NotFirstDelivery)
{
e->e_sendmode = SM_QUEUE;
}
else
{
NotFirstDelivery = true;
}
}
#endif /* _FFR_DM_ONE */
}
}
/* put back id for SMTP logging in putoutmsg() */
/* issue success message */
#if _FFR_MSG_ACCEPT
{
}
else
#endif /* _FFR_MSG_ACCEPT */
/* if we just queued, poke it */
if (doublequeue)
{
bool anything_to_send = false;
sm_getla();
{
continue;
{
continue;
}
else if (QueueMode != QM_QUARANTINE &&
{
continue;
}
anything_to_send = true;
/* close all the queue files */
{
}
}
if (anything_to_send)
{
#if PIPELINING
/*
** XXX if we don't do this, we get 250 twice
** because it is also flushed in the child.
*/
#endif /* PIPELINING */
(void) doworklist(e, true, true);
}
}
e->e_flags &= ~EF_LOGSENDER;
/* clean up a bit */
smtp->sm_gotmail = false;
/*
** Call dropenvelope if and only if the envelope is *not*
** being processed by the child process forked by doworklist().
*/
dropenvelope(e, true, false);
else
{
{
if (!doublequeue &&
QueueMode != QM_QUARANTINE &&
{
dropenvelope(ee, true, false);
continue;
}
dropenvelope(ee, true, false);
}
}
sm_rpool_free(e->e_rpool);
/*
** At this point, e == &MainEnvelope, but if we did splitting,
** then CurEnv may point to an envelope structure that was just
** freed with the rpool. So reset CurEnv *before* calling
** newenvelope.
*/
CurEnv = e;
/* restore connection quarantining */
{
}
else
{
}
return rv;
}
/*
** LOGUNDELRCPTS -- log undelivered (or all) recipients.
**
** Parameters:
** e -- envelope.
** msg -- message for Stat=
** level -- log level.
** all -- log all recipients.
**
** Returns:
** none.
**
** Side Effects:
** logs undelivered (or all) recipients
*/
void
ENVELOPE *e;
char *msg;
int level;
bool all;
{
ADDRESS *a;
return;
/* Clear $h so relay= doesn't get mislogged by logdelivery() */
/* Log who the mail would have gone to */
{
continue;
(time_t) 0, e);
}
}
/*
** CHECKSMTPATTACK -- check for denial-of-service attack by repetition
**
** Parameters:
** pcounter -- pointer to a counter for this command.
** maxcount -- maximum value for this counter before we
** slow down.
** waitnow -- sleep now (in this routine)?
** cname -- command name for logging.
** e -- the current envelope.
**
** Returns:
** time to wait,
** STOP_ATTACK if twice as many commands as allowed and
** MaxChildren > 0.
**
** Side Effects:
** Slows down if we seem to be under attack.
*/
static time_t
volatile unsigned int *pcounter;
unsigned int maxcount;
bool waitnow;
char *cname;
ENVELOPE *e;
{
if (maxcount <= 0) /* no limit */
return (time_t) 0;
{
unsigned int shift;
time_t s;
{
"%s: possible SMTP attack: command=%.40s, count=%u",
}
s = 1 << shift;
s = MAXTIMEOUT;
? STOP_ATTACK : (time_t) s)
/* sleep at least 1 second before returning */
if (s >= MAXTIMEOUT || s < 0)
s = MAXTIMEOUT;
if (waitnow && s > 0)
{
(void) sleep(s);
return IS_ATTACK(0);
}
return IS_ATTACK(s);
}
return (time_t) 0;
}
/*
** SETUP_SMTPD_IO -- setup I/O fd correctly for the SMTP server
**
** Parameters:
** none.
**
** Returns:
** nothing.
**
** Side Effects:
** may change I/O fd.
*/
static void
{
{
/* arrange for debugging output to go to remote host */
}
/*
** and connected to ttys
** and fcntl(STDIN, F_SETFL, O_NONBLOCKING) also changes STDOUT,
** then "chain" them together.
*/
{
if (inmode == -1)
{
if (LogLevel > 11)
"fcntl(inchfd, F_GETFL) failed: %s",
return;
}
if (outmode == -1)
{
if (LogLevel > 11)
"fcntl(outchfd, F_GETFL) failed: %s",
return;
}
return;
{
/* changing InChannel also changes OutChannel */
"set automode for I (%d)/O (%d) in SMTP server",
}
/* undo change of inchfd */
}
}
/*
** SKIPWORD -- skip a fixed word.
**
** Parameters:
** p -- place to start looking.
** w -- word to skip.
**
** Returns:
** p following w.
** NULL on error.
**
** Side Effects:
** clobbers the p data area.
*/
static char *
skipword(p, w)
register char *volatile p;
char *w;
{
register char *q;
char *firstp = p;
/* find beginning of word */
SKIP_SPACE(p);
q = p;
/* find end of word */
p++;
*p++ = '\0';
if (*p != ':')
{
usrerr("501 5.5.2 Syntax error in parameters scanning \"%s\"",
return NULL;
}
*p++ = '\0';
SKIP_SPACE(p);
if (*p == '\0')
goto syntax;
/* see if the input word matches desired word */
if (sm_strcasecmp(q, w))
goto syntax;
return p;
}
/*
** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line
**
** Parameters:
** kp -- the parameter key.
** vp -- the value of that parameter.
** e -- the envelope.
** features -- current server features
**
** Returns:
** none.
*/
static void
char *kp;
char *vp;
ENVELOPE *e;
unsigned int features;
{
{
{
usrerr("501 5.5.2 SIZE requires a value");
/* NOTREACHED */
}
errno = 0;
{
usrerr("552 5.2.3 Message size exceeds maximum value");
/* NOTREACHED */
}
if (e->e_msgsize < 0)
{
usrerr("552 5.2.3 Message size invalid");
/* NOTREACHED */
}
}
{
{
usrerr("501 5.5.2 BODY requires a value");
/* NOTREACHED */
}
{
SevenBitInput = false;
}
{
SevenBitInput = true;
}
else
{
/* NOTREACHED */
}
}
{
{
usrerr("504 5.7.0 Sorry, ENVID not supported, we do not allow DSN");
/* NOTREACHED */
}
{
usrerr("501 5.5.2 ENVID requires a value");
/* NOTREACHED */
}
{
usrerr("501 5.5.4 Syntax error in ENVID parameter value");
/* NOTREACHED */
}
{
usrerr("501 5.5.0 Duplicate ENVID parameter");
/* NOTREACHED */
}
}
{
{
usrerr("504 5.7.0 Sorry, RET not supported, we do not allow DSN");
/* NOTREACHED */
}
{
usrerr("501 5.5.2 RET requires a value");
/* NOTREACHED */
}
{
usrerr("501 5.5.0 Duplicate RET parameter");
/* NOTREACHED */
}
e->e_flags |= EF_RET_PARAM;
e->e_flags |= EF_NO_BODY_RETN;
{
/* NOTREACHED */
}
}
#if SASL
{
int len;
char *q;
char *auth_param; /* the value of the AUTH=x */
bool saveQuickAbort = QuickAbort;
bool saveSuprErrs = SuprErrs;
bool saveExitStat = ExitStat;
{
usrerr("501 5.5.2 AUTH= requires a value");
/* NOTREACHED */
}
if (e->e_auth_param != NULL)
{
usrerr("501 5.5.0 Duplicate AUTH parameter");
/* NOTREACHED */
}
else
if (!xtextok(auth_param))
{
usrerr("501 5.5.4 Syntax error in AUTH parameter value");
/* just a warning? */
/* NOTREACHED */
}
/* XXX define this always or only if trusted? */
/*
** call Strust_auth to find out whether
** auth_param is acceptable (trusted)
** we shouldn't trust it if not authenticated
** (required by RFC, leave it to ruleset?)
*/
SuprErrs = true;
QuickAbort = false;
{
{
q = e->e_auth_param;
sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n",
}
/* not trusted */
e->e_auth_param = "<>";
# if _FFR_AUTH_PASSING
# endif /* _FFR_AUTH_PASSING */
}
else
{
}
/* reset values */
Errors = 0;
}
#endif /* SASL */
/*
** "by" is only accepted if DeliverByMin >= 0.
** We maybe could add this to the list of server_features.
*/
{
char *s;
{
usrerr("501 5.5.2 BY= requires a value");
/* NOTREACHED */
}
errno = 0;
if (e->e_deliver_by == LONG_MIN ||
e->e_deliver_by == LONG_MAX ||
e->e_deliver_by > 999999999l ||
e->e_deliver_by < -999999999l)
{
/* NOTREACHED */
}
if (s == NULL || *s != ';')
{
usrerr("501 5.5.2 BY= missing ';'");
/* NOTREACHED */
}
e->e_dlvr_flag = 0;
++s; /* XXX: spaces allowed? */
SKIP_SPACE(s);
switch (tolower(*s))
{
case 'n':
e->e_dlvr_flag = DLVR_NOTIFY;
break;
case 'r':
e->e_dlvr_flag = DLVR_RETURN;
if (e->e_deliver_by <= 0)
{
usrerr("501 5.5.4 mode R requires BY time > 0");
/* NOTREACHED */
}
if (DeliverByMin > 0 && e->e_deliver_by > 0 &&
e->e_deliver_by < DeliverByMin)
{
usrerr("555 5.5.2 time %ld less than %ld",
e->e_deliver_by, (long) DeliverByMin);
/* NOTREACHED */
}
break;
default:
/* NOTREACHED */
}
++s; /* XXX: spaces allowed? */
SKIP_SPACE(s);
switch (tolower(*s))
{
case 't':
e->e_dlvr_flag |= DLVR_TRACE;
break;
case '\0':
break;
default:
/* NOTREACHED */
}
/* XXX: check whether more characters follow? */
}
else
{
/* NOTREACHED */
}
}
/*
** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line
**
** Parameters:
** a -- the address corresponding to the To: parameter.
** kp -- the parameter key.
** vp -- the value of that parameter.
** e -- the envelope.
** features -- current server features
**
** Returns:
** none.
*/
static void
ADDRESS *a;
char *kp;
char *vp;
ENVELOPE *e;
unsigned int features;
{
{
char *p;
{
usrerr("504 5.7.0 Sorry, NOTIFY not supported, we do not allow DSN");
/* NOTREACHED */
}
{
usrerr("501 5.5.2 NOTIFY requires a value");
/* NOTREACHED */
}
a->q_flags |= QHASNOTIFY;
return;
{
char *s;
s = p = strchr(p, ',');
if (p != NULL)
*p++ = '\0';
a->q_flags |= QPINGONSUCCESS;
a->q_flags |= QPINGONFAILURE;
a->q_flags |= QPINGONDELAY;
else
{
usrerr("501 5.5.4 Bad argument \"%s\" to NOTIFY",
vp);
/* NOTREACHED */
}
if (s != NULL)
*s = ',';
}
}
{
{
usrerr("504 5.7.0 Sorry, ORCPT not supported, we do not allow DSN");
/* NOTREACHED */
}
{
usrerr("501 5.5.2 ORCPT requires a value");
/* NOTREACHED */
}
{
usrerr("501 5.5.4 Syntax error in ORCPT parameter value");
/* NOTREACHED */
}
{
usrerr("501 5.5.0 Duplicate ORCPT parameter");
/* NOTREACHED */
}
}
else
{
/* NOTREACHED */
}
}
/*
** PRINTVRFYADDR -- print an entry in the verify queue
**
** Parameters:
** a -- the address to print.
** last -- set if this is the last one.
** vrfy -- set if this is a VRFY command.
**
** Returns:
** none.
**
** Side Effects:
** Prints the appropriate 250 codes.
*/
static void
register ADDRESS *a;
bool last;
bool vrfy;
{
char fmtbuf[30];
else
if (a->q_fullname == NULL)
{
else
}
else
{
else
}
}
#if SASL
/*
** SASLMECHS -- get list of possible AUTH mechanisms
**
** Parameters:
** conn -- SASL connection info.
** mechlist -- output parameter for list of mechanisms.
**
** Returns:
** number of mechs.
*/
static int
char **mechlist;
{
/* "user" is currently unused */
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
{
if (LogLevel > 9)
"AUTH error: listmech=%d, num=%d",
num = 0;
}
if (num > 0)
{
if (LogLevel > 11)
"AUTH: available mech=%s, allowed mech=%s",
}
else
{
"AUTH warning: no mechanisms");
}
return num;
}
# if SASL >= 20000
/*
** PROXY_POLICY -- define proxy policy for AUTH
**
** Parameters:
** conn -- unused.
** context -- unused.
** requested_user -- authorization identity.
** rlen -- authorization identity length.
** auth_identity -- authentication identity.
** alen -- authentication identity length.
** def_realm -- default user realm.
** urlen -- user realm length.
** propctx -- unused.
**
** Returns:
** ok?
**
** Side Effects:
** sets {auth_authen} macro.
*/
int
void *context;
const char *requested_user;
unsigned rlen;
const char *auth_identity;
unsigned alen;
const char *def_realm;
unsigned urlen;
{
if (auth_identity == NULL)
return SASL_FAIL;
return SASL_OK;
}
# else /* SASL >= 20000 */
/*
** PROXY_POLICY -- define proxy policy for AUTH
**
** Parameters:
** context -- unused.
** auth_identity -- authentication identity.
** requested_user -- authorization identity.
** user -- allowed user (output).
** errstr -- possible error string (output).
**
** Returns:
** ok?
*/
int
void *context;
const char *auth_identity;
const char *requested_user;
const char **user;
const char **errstr;
{
return SASL_FAIL;
return SASL_OK;
}
# endif /* SASL >= 20000 */
#endif /* SASL */
#if STARTTLS
/*
** INITSRVTLS -- initialize server side TLS
**
** Parameters:
** tls_ok -- should tls initialization be done?
**
** Returns:
** succeeded?
**
** Side Effects:
** sets tls_ok_srv which is a static variable in this module.
** Do NOT remove assignments to it!
*/
bool
bool tls_ok;
{
if (!tls_ok)
return false;
/* do NOT remove assignment */
return tls_ok_srv;
}
#endif /* STARTTLS */
/*
** SRVFEATURES -- get features for SMTP server
**
** Parameters:
** e -- envelope (should be session context).
** clientname -- name of client.
** features -- default features for this invocation.
**
** Returns:
** server features.
*/
/* table with options: it uses just one character, how about strings? */
static struct
{
char srvf_opt;
unsigned int srvf_flag;
} srv_feat_table[] =
{
{ 'A', SRV_OFFER_AUTH },
{ 'B', SRV_OFFER_VERB },
{ 'C', SRV_REQ_SEC },
{ 'D', SRV_OFFER_DSN },
{ 'E', SRV_OFFER_ETRN },
{ 'L', SRV_REQ_AUTH },
#if PIPELINING
# if _FFR_NO_PIPE
{ 'N', SRV_NO_PIPE },
# endif /* _FFR_NO_PIPE */
{ 'P', SRV_OFFER_PIPE },
#endif /* PIPELINING */
{ 'S', SRV_OFFER_TLS },
/* { 'T', SRV_TMP_FAIL }, */
{ 'V', SRV_VRFY_CLT },
{ 'X', SRV_OFFER_EXPN },
/* { 'Y', SRV_OFFER_VRFY }, */
{ '\0', SRV_NONE }
};
static unsigned int
ENVELOPE *e;
char *clientname;
unsigned int features;
{
int r, i, j;
sizeof(pvpbuf));
if (r != EX_OK)
return features;
return features;
return SRV_TMP_FAIL;
/*
** General rule (see sendmail.h, d_flags):
**
** Since we can change some features per daemon, we have both
*/
{
c = pvp[i][0];
j = 0;
for (;;)
{
{
if (LogLevel > 9)
"srvfeatures: unknown feature %s",
pvp[i]);
break;
}
if (c == opt)
{
break;
}
{
break;
}
++j;
}
}
return features;
}
/*
** HELP -- implement the HELP command.
**
** Parameters:
** topic -- the topic we want help for.
** e -- envelope.
**
** Returns:
** none.
**
** Side Effects:
** outputs the help file to message output.
*/
#define HELPVSTR "#vers "
#define HELPVERSION 2
void
char *topic;
ENVELOPE *e;
{
register char *p;
int len;
bool noinfo;
bool first = true;
static int foundvers = -1;
extern char Version[];
if (DontLockReadFiles)
sff |= SFF_NOLOCK;
sff |= SFF_SAFEDIRPATH;
{
/* no help */
errno = 0;
message("502 5.3.0 Sendmail %s -- HELP not implemented",
Version);
return;
}
{
topic = "smtp";
noinfo = false;
}
else
{
noinfo = true;
}
{
if (buf[0] == '#')
{
if (foundvers < 0 &&
{
int h;
&h) == 1)
foundvers = h;
}
continue;
}
{
if (first)
{
first = false;
}
if (p == NULL)
else
p++;
fixcrlf(p, true);
if (foundvers >= 2)
{
p = inp;
}
message("214-2.0.0 %s", p);
noinfo = false;
}
}
if (noinfo)
else
message("214 2.0.0 End of HELP info");
{
if (LogLevel > 1)
"%s too old (require version %d)",
/* avoid log next time */
foundvers = 0;
}
}
#if SASL
/*
** RESET_SASLCONN -- reset SASL connection data
**
** Parameters:
** conn -- SASL connection context
** hostname -- host name
** various connection data
**
** Returns:
** SASL result
*/
static int
# if SASL >= 20000
# else /* SASL >= 20000 */
# endif /* SASL >= 20000 */
{
int result;
# if SASL >= 20000
/* use empty realm: only works in SASL > 1.5.5 */
# else /* SASL >= 20000 */
/* use no realm -> realm is set to hostname by SASL lib */
conn);
# endif /* SASL >= 20000 */
return result;
# if SASL >= 20000
return result;
return result;
# endif /* NETINET || NETINET6 */
return result;
return result;
# else /* SASL >= 20000 */
# if NETINET
return result;
return result;
# endif /* NETINET */
return result;
# endif /* SASL >= 20000 */
return SASL_OK;
}
#endif /* SASL */