#include <sendmail.h>
#if MILTER
# include <libmilter/mfapi.h>
# include <libmilter/mfdef.h>
#endif
SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.989 2009/12/18 17:08:01 ca Exp $")
#include <sm/time.h>
#include <sm/fdset.h>
#if SASL || STARTTLS
# include "sfsasl.h"
#endif
#if SASL
# define ENC64LEN(l) (((l) + 2) * 4 / 3 + 1)
static int saslmechs __P((sasl_conn_t *, char **));
#endif
#if STARTTLS
# include <sysexits.h>
static SSL_CTX *srv_ctx = NULL;
static SSL *srv_ssl = NULL;
static bool tls_ok_srv = false;
# define TLS_VERIFY_CLIENT() tls_set_verify(srv_ctx, srv_ssl, \
bitset(SRV_VRFY_CLT, features))
#endif
#if _FFR_DM_ONE
static bool NotFirstDelivery = false;
#endif
#define SRV_NONE 0x0000
#define SRV_OFFER_TLS 0x0001
#define SRV_VRFY_CLT 0x0002
#define SRV_OFFER_AUTH 0x0004
#define SRV_OFFER_ETRN 0x0008
#define SRV_OFFER_VRFY 0x0010
#define SRV_OFFER_EXPN 0x0020
#define SRV_OFFER_VERB 0x0040
#define SRV_OFFER_DSN 0x0080
#if PIPELINING
# define SRV_OFFER_PIPE 0x0100
# if _FFR_NO_PIPE
# define SRV_NO_PIPE 0x0200
# endif
#endif
#define SRV_REQ_AUTH 0x0400
#define SRV_REQ_SEC 0x0800
#define SRV_TMP_FAIL 0x1000
static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int));
#define STOP_ATTACK ((time_t) -1)
static time_t checksmtpattack __P((volatile unsigned int *, unsigned int,
bool, char *, ENVELOPE *));
static void printvrfyaddr __P((ADDRESS *, bool, bool));
static char *skipword __P((char *volatile, char *));
static void setup_smtpd_io __P((void));
#if SASL
# if SASL >= 20000
static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname,
char *_remoteip, char *_localip,
char *_auth_id, sasl_ssf_t *_ext_ssf));
# define RESET_SASLCONN \
do \
{ \
result = reset_saslconn(&conn, AuthRealm, remoteip, \
localip, auth_id, &ext_ssf); \
if (result != SASL_OK) \
sasl_ok = false; \
} while (0)
# else
static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname,
struct sockaddr_in *_saddr_r,
struct sockaddr_in *_saddr_l,
sasl_external_properties_t *_ext_ssf));
# define RESET_SASLCONN \
do \
{ \
result = reset_saslconn(&conn, AuthRealm, &saddr_r, \
&saddr_l, &ext_ssf); \
if (result != SASL_OK) \
sasl_ok = false; \
} while (0)
# endif
#endif
extern ENVELOPE BlankEnvelope;
#define NBADRCPTS \
do \
{ \
char buf[16]; \
(void) sm_snprintf(buf, sizeof(buf), "%d", \
BadRcptThrottle > 0 && n_badrcpts > BadRcptThrottle \
? n_badrcpts - 1 : n_badrcpts); \
macdefine(&e->e_macro, A_TEMP, macid("{nbadrcpts}"), buf); \
} while (0)
#define SKIP_SPACE(s) while (isascii(*s) && isspace(*s)) \
(s)++
void
parse_esmtp_args(e, addr_st, p, delimptr, which, args, esmtp_args)
ENVELOPE *e;
ADDRESS *addr_st;
char *p;
char *delimptr;
char *which;
char *args[];
esmtp_args_F esmtp_args;
{
int argno;
argno = 0;
if (args != NULL)
args[argno++] = p;
p = delimptr;
while (p != NULL && *p != '\0')
{
char *kp;
char *vp = NULL;
char *equal = NULL;
SKIP_SPACE(p);
if (*p == '\0')
break;
kp = p;
while ((isascii(*p) && isalnum(*p)) || *p == '-')
p++;
if (*p == '=')
{
equal = p;
*p++ = '\0';
vp = p;
while (*p != '\0' && *p != ' ' &&
!(isascii(*p) && iscntrl(*p)) &&
*p != '=')
p++;
}
if (*p != '\0')
*p++ = '\0';
if (tTd(19, 1))
sm_dprintf("%s: got arg %s=\"%s\"\n", which, kp,
vp == NULL ? "<null>" : vp);
esmtp_args(addr_st, kp, vp, e);
if (equal != NULL)
*equal = '=';
if (args != NULL)
args[argno] = kp;
argno++;
if (argno >= MAXSMTPARGS - 1)
usrerr("501 5.5.4 Too many parameters");
if (Errors > 0)
break;
}
if (args != NULL)
args[argno] = NULL;
}
struct cmd
{
char *cmd_name;
int cmd_code;
};
#define CMDERROR 0
#define CMDMAIL 1
#define CMDRCPT 2
#define CMDDATA 3
#define CMDRSET 4
#define CMDVRFY 5
#define CMDEXPN 6
#define CMDNOOP 7
#define CMDQUIT 8
#define CMDHELO 9
#define CMDHELP 10
#define CMDEHLO 11
#define CMDETRN 12
#if SASL
# define CMDAUTH 13
#endif
#if STARTTLS
# define CMDSTLS 14
#endif
#define CMDVERB 17
#define CMDUNIMPL 19
#define CMDLOGBOGUS 23
#define CMDDBGQSHOW 24
#define CMDDBGDEBUG 25
static struct cmd CmdTab[] =
{
{ "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
#if STARTTLS
{ "starttls", CMDSTLS, },
#endif
{ "showq", CMDDBGQSHOW },
{ "debug", CMDDBGDEBUG },
{ "wiz", CMDLOGBOGUS },
{ NULL, CMDERROR }
};
static char *CurSmtpClient;
#ifndef MAXBADCOMMANDS
# define MAXBADCOMMANDS 25
#endif
#ifndef MAXHELOCOMMANDS
# define MAXHELOCOMMANDS 3
#endif
#ifndef MAXVRFYCOMMANDS
# define MAXVRFYCOMMANDS 6
#endif
#ifndef MAXETRNCOMMANDS
# define MAXETRNCOMMANDS 8
#endif
#ifndef MAXTIMEOUT
# define MAXTIMEOUT (4 * 60)
#endif
#ifndef MAXSHIFT
# define MAXSHIFT 8
#endif
#if MAXSHIFT > 31
ERROR _MAXSHIFT > 31 is invalid
#endif
#if MAXBADCOMMANDS > 0
# define STOP_IF_ATTACK(r) do \
{ \
if ((r) == STOP_ATTACK) \
goto stopattack; \
} while (0)
#else
# define STOP_IF_ATTACK(r) r
#endif
#if SM_HEAP_CHECK
static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp",
"@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $");
#endif
typedef struct
{
bool sm_gotmail;
unsigned int sm_nrcpts;
bool sm_discard;
#if MILTER
bool sm_milterize;
bool sm_milterlist;
milters_T sm_milters;
unsigned int sm_e_nrcpts_orig;
#endif
char *sm_quarmsg;
} SMTP_T;
static bool smtp_data __P((SMTP_T *, ENVELOPE *));
#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; \
\
milter_cmd_fail = true; \
switch (state) \
{ \
case SMFIR_SHUTDOWN: \
if (MilterLogLevel > 3) \
{ \
sm_syslog(LOG_INFO, e->e_id, \
"Milter: %s=%s, reject=421, errormode=4", \
str, addr); \
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) \
{ \
sm_syslog(LOG_INFO, e->e_id, \
"Milter: %s=%s, reject=%s", \
str, addr, response); \
LogUsrErrs = false; \
} \
if (strncmp(response, "421 ", 4) == 0 \
|| strncmp(response, "421-", 4) == 0) \
{ \
bool tsave = QuickAbort; \
\
QuickAbort = false; \
usrerr(response); \
QuickAbort = tsave; \
e->e_sendqueue = NULL; \
goto doquit; \
} \
else \
usrerr(response); \
break; \
\
case SMFIR_REJECT: \
if (MilterLogLevel > 3) \
{ \
sm_syslog(LOG_INFO, e->e_id, \
"Milter: %s=%s, reject=550 5.7.1 Command rejected", \
str, addr); \
LogUsrErrs = false; \
} \
usrerr("550 5.7.1 Command rejected"); \
break; \
\
case SMFIR_DISCARD: \
if (MilterLogLevel > 3) \
sm_syslog(LOG_INFO, e->e_id, \
"Milter: %s=%s, discard", \
str, addr); \
e->e_flags |= EF_DISCARD; \
milter_cmd_fail = false; \
break; \
\
case SMFIR_TEMPFAIL: \
if (MilterLogLevel > 3) \
{ \
sm_syslog(LOG_INFO, e->e_id, \
"Milter: %s=%s, reject=%s", \
str, addr, MSG_TEMPFAIL); \
LogUsrErrs = false; \
} \
usrerr(MSG_TEMPFAIL); \
break; \
default: \
milter_cmd_fail = false; \
break; \
} \
LogUsrErrs = savelogusrerrs; \
if (response != NULL) \
sm_free(response); \
}
#else
# define MILTER_ABORT(e)
#endif
#define CLEAR_STATE(cmd) \
do \
{ \
\
MILTER_ABORT(e); \
\
if (smtp.sm_nrcpts > 0) \
{ \
logundelrcpts(e, cmd, 10, false); \
smtp.sm_nrcpts = 0; \
macdefine(&e->e_macro, A_PERM, \
macid("{nrcpts}"), "0"); \
} \
\
e->e_sendqueue = NULL; \
e->e_flags |= EF_CLRQUEUE; \
\
if (tTd(92, 2)) \
sm_dprintf("CLEAR_STATE: e_id=%s, EF_LOGSENDER=%d, LogLevel=%d\n",\
e->e_id, bitset(EF_LOGSENDER, e->e_flags), LogLevel);\
if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) \
logsender(e, NULL); \
e->e_flags &= ~EF_LOGSENDER; \
\
\
smtp.sm_gotmail = false; \
SuprErrs = true; \
(void) dropenvelope(e, true, false); \
sm_rpool_free(e->e_rpool); \
e = newenvelope(e, CurEnv, sm_rpool_new_x(NULL)); \
CurEnv = e; \
e->e_features = features; \
\
\
if (smtp.sm_discard) \
e->e_flags |= EF_DISCARD; \
\
\
if (smtp.sm_quarmsg == NULL) \
{ \
e->e_quarmsg = NULL; \
macdefine(&e->e_macro, A_PERM, \
macid("{quarantine}"), ""); \
} \
else \
{ \
e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \
smtp.sm_quarmsg); \
macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \
e->e_quarmsg); \
} \
} while (0)
#define MIN_DELAY_LOG 15
#define DELAY_CONN(cmd) \
if (DelayLA > 0 && (CurrentLA = getla()) >= DelayLA) \
{ \
time_t dnow; \
\
sm_setproctitle(true, e, \
"%s: %s: delaying %s: load average: %d", \
qid_printname(e), CurSmtpClient, \
cmd, DelayLA); \
if (LogLevel > 8 && (dnow = curtime()) > log_delay) \
{ \
sm_syslog(LOG_INFO, e->e_id, \
"delaying=%s, load average=%d >= %d", \
cmd, CurrentLA, DelayLA); \
log_delay = dnow + MIN_DELAY_LOG; \
} \
(void) sleep(1); \
sm_setproctitle(true, e, "%s %s: %.80s", \
qid_printname(e), CurSmtpClient, inp); \
}
static bool SevenBitInput_Saved;
void
smtp(nullserver, d_flags, e)
char *volatile nullserver;
BITMAP256 d_flags;
register ENVELOPE *volatile e;
{
register char *volatile p;
register struct cmd *volatile c = NULL;
char *cmd;
auto ADDRESS *vrfyqueue;
ADDRESS *a;
volatile bool gothello;
bool vrfy;
char *volatile protocol;
char *volatile sendinghost;
char *volatile peerhostname;
auto char *delimptr;
char *id;
volatile unsigned int n_badcmds = 0;
volatile unsigned int n_badrcpts = 0;
volatile unsigned int n_verifies = 0;
volatile unsigned int n_etrn = 0;
volatile unsigned int n_noop = 0;
volatile unsigned int n_helo = 0;
bool ok;
volatile bool first;
volatile bool tempfail = false;
volatile time_t wt;
volatile time_t previous;
volatile bool lognullconnection = true;
register char *q;
SMTP_T smtp;
char *addr;
char *greetcode = "220";
char *hostname;
QUEUE_CHAR *new;
char *args[MAXSMTPARGS];
char inp[MAXINPLINE];
#if MAXINPLINE < MAXLINE
ERROR _MAXINPLINE must NOT be less than _MAXLINE: MAXINPLINE < MAXLINE
#endif
char cmdbuf[MAXLINE];
#if SASL
sasl_conn_t *conn;
volatile bool sasl_ok;
volatile unsigned int n_auth = 0;
bool ismore;
int result;
volatile int authenticating;
char *user;
char *in, *out2;
# if SASL >= 20000
char *auth_id = NULL;
const char *out;
sasl_ssf_t ext_ssf;
char localip[60], remoteip[60];
# else
char *out;
const char *errstr;
sasl_external_properties_t ext_ssf;
struct sockaddr_in saddr_l;
struct sockaddr_in saddr_r;
# endif
sasl_security_properties_t ssp;
sasl_ssf_t *ssf;
unsigned int inlen, out2len;
unsigned int outlen;
char *volatile auth_type;
char *mechlist;
volatile unsigned int n_mechs;
unsigned int len;
#else
#endif
int r;
#if STARTTLS
int rfd, wfd;
volatile bool tls_active = false;
volatile bool smtps = bitnset(D_SMTPS, d_flags);
bool saveQuickAbort;
bool saveSuprErrs;
time_t tlsstart;
#endif
volatile unsigned int features;
#if PIPELINING
# if _FFR_NO_PIPE
int np_log = 0;
# endif
#endif
volatile time_t log_delay = (time_t) 0;
#if MILTER
volatile bool milter_cmd_done, milter_cmd_safe;
volatile bool milter_rcpt_added, milter_cmd_fail;
ADDRESS addr_st;
# define p_addr_st &addr_st
#else
# define p_addr_st NULL
#endif
size_t inplen;
#if _FFR_BADRCPT_SHUTDOWN
int n_badrcpts_adj;
#endif
SevenBitInput_Saved = SevenBitInput;
smtp.sm_nrcpts = 0;
#if MILTER
smtp.sm_milterize = (nullserver == NULL);
smtp.sm_milterlist = false;
addr = NULL;
#endif
setup_smtpd_io();
#if SM_HEAP_CHECK
if (sm_debug_active(&DebugLeakSmtp, 1))
{
sm_heap_newgroup();
sm_dprintf("smtp() heap group #%d\n", sm_heap_group());
}
#endif
e->e_rpool = sm_rpool_new_x(NULL);
e->e_macro.mac_rpool = e->e_rpool;
settime(e);
sm_getla();
peerhostname = RealHostName;
if (peerhostname == NULL)
peerhostname = "localhost";
CurHostName = peerhostname;
CurSmtpClient = macvalue('_', e);
if (CurSmtpClient == NULL)
CurSmtpClient = CurHostName;
smtp.sm_discard = bitset(EF_DISCARD, e->e_flags);
#if PIPELINING
(void) sm_io_autoflush(InChannel, OutChannel);
#endif
sm_setproctitle(true, e, "server %s startup", CurSmtpClient);
features = ((bitset(PRIV_NOETRN, PrivacyFlags) ||
bitnset(D_NOETRN, d_flags)) ? SRV_NONE : SRV_OFFER_ETRN)
| (bitnset(D_AUTHREQ, d_flags) ? SRV_REQ_AUTH : SRV_NONE)
| (bitset(PRIV_NOEXPN, PrivacyFlags) ? SRV_NONE
: (SRV_OFFER_EXPN
| (bitset(PRIV_NOVERB, PrivacyFlags)
? SRV_NONE : SRV_OFFER_VERB)))
| ((bitset(PRIV_NORECEIPTS, PrivacyFlags) || !SendMIMEErrors)
? SRV_NONE : SRV_OFFER_DSN)
#if SASL
| (bitnset(D_NOAUTH, d_flags) ? SRV_NONE : SRV_OFFER_AUTH)
| (bitset(SASL_SEC_NOPLAINTEXT, SASLOpts) ? SRV_REQ_SEC
: SRV_NONE)
#endif
#if PIPELINING
| SRV_OFFER_PIPE
#endif
#if STARTTLS
| (bitnset(D_NOTLS, d_flags) ? SRV_NONE : SRV_OFFER_TLS)
| (bitset(TLS_I_NO_VRFY, TLS_Srv_Opts) ? SRV_NONE
: SRV_VRFY_CLT)
#endif
;
if (nullserver == NULL)
{
features = srvfeatures(e, CurSmtpClient, features);
if (bitset(SRV_TMP_FAIL, features))
{
if (LogLevel > 4)
sm_syslog(LOG_ERR, NOQID,
"ERROR: srv_features=tempfail, relay=%.100s, access temporarily disabled",
CurSmtpClient);
nullserver = "450 4.3.0 Please try again later.";
}
else
{
#if PIPELINING
# if _FFR_NO_PIPE
if (bitset(SRV_NO_PIPE, features))
{
features &= ~SRV_OFFER_PIPE;
}
# endif
#endif
#if SASL
if (bitset(SRV_REQ_SEC, features))
SASLOpts |= SASL_SEC_NOPLAINTEXT;
else
SASLOpts &= ~SASL_SEC_NOPLAINTEXT;
#endif
}
}
else if (strncmp(nullserver, "421 ", 4) == 0)
{
message(nullserver);
goto doquit;
}
e->e_features = features;
hostname = macvalue('j', e);
#if SASL
if (AuthRealm == NULL)
AuthRealm = hostname;
sasl_ok = bitset(SRV_OFFER_AUTH, features);
n_mechs = 0;
authenticating = SASL_NOT_AUTH;
if (sasl_ok)
{
# if SASL >= 20000
result = sasl_server_new("smtp", AuthRealm, NULL, NULL, NULL,
NULL, 0, &conn);
# elif SASL > 10505
result = sasl_server_new("smtp", AuthRealm, "", NULL, 0, &conn);
# else
result = sasl_server_new("smtp", AuthRealm, NULL, NULL, 0,
&conn);
# endif
sasl_ok = result == SASL_OK;
if (!sasl_ok)
{
if (LogLevel > 9)
sm_syslog(LOG_WARNING, NOQID,
"AUTH error: sasl_server_new failed=%d",
result);
}
}
if (sasl_ok)
{
# if SASL >= 20000
localip[0] = remoteip[0] = '\0';
# if NETINET || NETINET6
in = macvalue(macid("{daemon_family}"), e);
if (in != NULL && (
# if NETINET6
strcmp(in, "inet6") == 0 ||
# endif
strcmp(in, "inet") == 0))
{
SOCKADDR_LEN_T addrsize;
SOCKADDR saddr_l;
SOCKADDR saddr_r;
addrsize = sizeof(saddr_r);
if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
NULL),
(struct sockaddr *) &saddr_r,
&addrsize) == 0)
{
if (iptostring(&saddr_r, addrsize,
remoteip, sizeof(remoteip)))
{
sasl_setprop(conn, SASL_IPREMOTEPORT,
remoteip);
}
addrsize = sizeof(saddr_l);
if (getsockname(sm_io_getinfo(InChannel,
SM_IO_WHAT_FD,
NULL),
(struct sockaddr *) &saddr_l,
&addrsize) == 0)
{
if (iptostring(&saddr_l, addrsize,
localip,
sizeof(localip)))
{
sasl_setprop(conn,
SASL_IPLOCALPORT,
localip);
}
}
}
}
# endif
# else
# if NETINET
in = macvalue(macid("{daemon_family}"), e);
if (in != NULL && strcmp(in, "inet") == 0)
{
SOCKADDR_LEN_T addrsize;
addrsize = sizeof(struct sockaddr_in);
if (getpeername(sm_io_getinfo(InChannel, SM_IO_WHAT_FD,
NULL),
(struct sockaddr *)&saddr_r,
&addrsize) == 0)
{
sasl_setprop(conn, SASL_IP_REMOTE, &saddr_r);
addrsize = sizeof(struct sockaddr_in);
if (getsockname(sm_io_getinfo(InChannel,
SM_IO_WHAT_FD,
NULL),
(struct sockaddr *)&saddr_l,
&addrsize) == 0)
sasl_setprop(conn, SASL_IP_LOCAL,
&saddr_l);
}
}
# endif
# endif
auth_type = NULL;
mechlist = NULL;
user = NULL;
# if 0
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{auth_author}"), NULL);
# endif
(void) memset(&ssp, '\0', sizeof(ssp));
{
ssp.max_ssf = MaxSLBits;
ssp.maxbufsize = MAXOUTLEN;
}
ssp.security_flags = SASLOpts & SASL_SEC_MASK;
sasl_ok = sasl_setprop(conn, SASL_SEC_PROPS, &ssp) == SASL_OK;
if (sasl_ok)
{
# if SASL >= 20000
ext_ssf = 0;
auth_id = NULL;
sasl_ok = ((sasl_setprop(conn, SASL_SSF_EXTERNAL,
&ext_ssf) == SASL_OK) &&
(sasl_setprop(conn, SASL_AUTH_EXTERNAL,
auth_id) == SASL_OK));
# else
ext_ssf.ssf = 0;
ext_ssf.auth_id = NULL;
sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL,
&ext_ssf) == SASL_OK;
# endif
}
if (sasl_ok)
n_mechs = saslmechs(conn, &mechlist);
}
#endif
#if STARTTLS
# if USE_OPENSSL_ENGINE
if (tls_ok_srv && bitset(SRV_OFFER_TLS, features) &&
!SSL_set_engine(NULL))
{
sm_syslog(LOG_ERR, NOQID,
"STARTTLS=server, SSL_set_engine=failed");
tls_ok_srv = false;
}
# endif
set_tls_rd_tmo(TimeOuts.to_nextcommand);
#endif
#if MILTER
if (smtp.sm_milterize)
{
char state;
smtp.sm_milterlist = milter_init(e, &state, &smtp.sm_milters);
switch (state)
{
case SMFIR_REJECT:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: initialization failed, rejecting commands");
greetcode = "554";
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: initialization failed, temp failing commands");
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: initialization failed, closing connection");
tempfail = true;
smtp.sm_milterize = false;
message("421 4.7.0 %s closing connection",
MyHostName);
e->e_sendqueue = NULL;
lognullconnection = false;
goto doquit;
}
}
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
q = macvalue(macid("{client_name}"), e);
SM_ASSERT(q != NULL || OpMode == MD_SMTP);
if (q == NULL)
q = "localhost";
response = milter_connect(q, RealHostAddr, e, &state);
switch (state)
{
case SMFIR_REPLYCODE:
case SMFIR_REJECT:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: connect: host=%s, addr=%s, rejecting commands",
peerhostname,
anynet_ntoa(&RealHostAddr));
greetcode = "554";
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: connect: host=%s, addr=%s, temp failing commands",
peerhostname,
anynet_ntoa(&RealHostAddr));
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: connect: host=%s, addr=%s, shutdown",
peerhostname,
anynet_ntoa(&RealHostAddr));
tempfail = true;
smtp.sm_milterize = false;
message("421 4.7.0 %s closing connection",
MyHostName);
e->e_sendqueue = NULL;
goto doquit;
}
if (response != NULL)
sm_free(response);
}
#endif
if (
#if STARTTLS
!smtps &&
#endif
*greetcode == '2' && nullserver == NULL)
{
time_t msecs = 0;
char **pvp;
char pvpbuf[PSBUFSIZE];
pvp = NULL;
r = rscap("greet_pause", peerhostname,
anynet_ntoa(&RealHostAddr), e,
&pvp, pvpbuf, sizeof(pvpbuf));
if (r == EX_OK && pvp != NULL && pvp[0] != NULL &&
(pvp[0][0] & 0377) == CANONNET && pvp[1] != NULL)
{
msecs = strtol(pvp[1], NULL, 10);
}
if (msecs > 0)
{
int fd;
fd_set readfds;
struct timeval timeout;
struct timeval bp, ep, tp;
int eoftest;
timeout.tv_sec = msecs / 1000;
timeout.tv_usec = (msecs % 1000) * 1000;
if (timeout.tv_sec >= 300)
{
timeout.tv_sec = 300;
timeout.tv_usec = 0;
}
fd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
FD_ZERO(&readfds);
SM_FD_SET(fd, &readfds);
gettimeofday(&bp, NULL);
if (select(fd + 1, FDSET_CAST &readfds,
NULL, NULL, &timeout) > 0 &&
FD_ISSET(fd, &readfds) &&
(eoftest = sm_io_getc(InChannel, SM_TIME_DEFAULT))
!= SM_IO_EOF)
{
sm_io_ungetc(InChannel, SM_TIME_DEFAULT,
eoftest);
gettimeofday(&ep, NULL);
timersub(&ep, &bp, &tp);
greetcode = "554";
nullserver = "Command rejected";
sm_syslog(LOG_INFO, e->e_id,
"rejecting commands from %s [%s] due to pre-greeting traffic after %d seconds",
peerhostname,
anynet_ntoa(&RealHostAddr),
(int) tp.tv_sec +
(tp.tv_usec >= 500000 ? 1 : 0)
);
}
}
}
#if STARTTLS
if (smtps)
{
Errors = 0;
goto starttls;
}
greeting:
#endif
if (*greetcode == '5')
(void) sm_snprintf(inp, sizeof(inp),
"%s not accepting messages", hostname);
else
expand(SmtpGreeting, inp, sizeof(inp), e);
p = strchr(inp, '\n');
if (p != NULL)
*p++ = '\0';
id = strchr(inp, ' ');
if (id == NULL)
id = &inp[strlen(inp)];
if (p == NULL)
(void) sm_snprintf(cmdbuf, sizeof(cmdbuf),
"%s %%.*s ESMTP%%s", greetcode);
else
(void) sm_snprintf(cmdbuf, sizeof(cmdbuf),
"%s-%%.*s ESMTP%%s", greetcode);
message(cmdbuf, (int) (id - inp), inp, id);
while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL)
{
*p++ = '\0';
if (isascii(*id) && isspace(*id))
id++;
(void) sm_strlcpyn(cmdbuf, sizeof(cmdbuf), 2, greetcode, "-%s");
message(cmdbuf, id);
}
if (id != NULL)
{
if (isascii(*id) && isspace(*id))
id++;
(void) sm_strlcpyn(cmdbuf, sizeof(cmdbuf), 2, greetcode, " %s");
message(cmdbuf, id);
}
protocol = NULL;
sendinghost = macvalue('s', e);
if (e->e_quarmsg == NULL)
smtp.sm_quarmsg = NULL;
else
smtp.sm_quarmsg = newstr(e->e_quarmsg);
if (sendinghost != NULL)
sendinghost = sm_strdup_x(sendinghost);
first = true;
gothello = false;
smtp.sm_gotmail = false;
for (;;)
{
SM_TRY
{
QuickAbort = false;
HoldErrs = false;
SuprErrs = false;
LogUsrErrs = false;
OnlyOneError = true;
e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS);
#if MILTER
milter_cmd_fail = false;
#endif
e->e_to = NULL;
Errors = 0;
FileName = NULL;
(void) sm_io_flush(smioout, SM_TIME_DEFAULT);
SmtpPhase = "server cmd read";
sm_setproctitle(true, e, "server %s cmd read", CurSmtpClient);
if (sm_io_error(OutChannel) ||
(p = sfgets(inp, sizeof(inp), InChannel,
TimeOuts.to_nextcommand, SmtpPhase)) == NULL)
{
char *d;
d = macvalue(macid("{daemon_name}"), e);
if (d == NULL)
d = "stdin";
disconnect(1, e);
#if MILTER
milter_quit(e);
#endif
message("421 4.4.1 %s Lost input channel from %s",
MyHostName, CurSmtpClient);
if (LogLevel > (smtp.sm_gotmail ? 1 : 19))
sm_syslog(LOG_NOTICE, e->e_id,
"lost input channel from %s to %s after %s",
CurSmtpClient, d,
(c == NULL || c->cmd_name == NULL) ? "startup" : c->cmd_name);
if (bitset(EF_CLRQUEUE, e->e_flags))
e->e_sendqueue = NULL;
goto doquit;
}
inplen = strlen(inp);
#if SASL
if (authenticating != SASL_PROC_AUTH
&& sm_strncasecmp(inp, "AUTH ", 5) != 0
&& inplen > MAXLINE)
{
message("421 4.7.0 %s Command too long, possible attack %s",
MyHostName, CurSmtpClient);
sm_syslog(LOG_INFO, e->e_id,
"%s: SMTP violation, input too long: %lu",
CurSmtpClient, (unsigned long) inplen);
goto doquit;
}
#endif
if (first)
{
size_t cmdlen;
int idx;
char *http_cmd;
static char *http_cmds[] = { "GET", "POST",
"CONNECT", "USER", NULL };
for (idx = 0; (http_cmd = http_cmds[idx]) != NULL;
idx++)
{
cmdlen = strlen(http_cmd);
if (cmdlen < inplen &&
sm_strncasecmp(inp, http_cmd, cmdlen) == 0 &&
isascii(inp[cmdlen]) && isspace(inp[cmdlen]))
{
message("421 4.7.0 %s Rejecting open proxy %s",
MyHostName, CurSmtpClient);
sm_syslog(LOG_INFO, e->e_id,
"%s: probable open proxy: command=%.40s",
CurSmtpClient, inp);
goto doquit;
}
}
first = false;
}
fixcrlf(inp, true);
#if PIPELINING
# if _FFR_NO_PIPE
if (bitset(SRV_NO_PIPE, features) &&
sm_io_getinfo(InChannel, SM_IO_IS_READABLE, NULL) > 0)
{
if (++np_log < 3)
sm_syslog(LOG_INFO, NOQID,
"unauthorized PIPELINING, sleeping, relay=%.100s",
CurSmtpClient);
sleep(1);
}
# endif
#endif
#if SASL
if (authenticating == SASL_PROC_AUTH)
{
# if 0
if (*inp == '\0')
{
authenticating = SASL_NOT_AUTH;
message("501 5.5.2 missing input");
RESET_SASLCONN;
continue;
}
# endif
if (*inp == '*' && *(inp + 1) == '\0')
{
authenticating = SASL_NOT_AUTH;
message("501 5.0.0 AUTH aborted");
RESET_SASLCONN;
continue;
}
# if SASL >= 20000
in = xalloc(strlen(inp) + 1);
result = sasl_decode64(inp, strlen(inp), in,
strlen(inp), &inlen);
# else
out = xalloc(strlen(inp));
result = sasl_decode64(inp, strlen(inp), out, &outlen);
# endif
if (result != SASL_OK)
{
authenticating = SASL_NOT_AUTH;
message("501 5.5.4 cannot decode AUTH parameter %s",
inp);
# if SASL >= 20000
sm_free(in);
# endif
RESET_SASLCONN;
continue;
}
# if SASL >= 20000
result = sasl_server_step(conn, in, inlen,
&out, &outlen);
sm_free(in);
# else
result = sasl_server_step(conn, out, outlen,
&out, &outlen, &errstr);
# endif
if (result == SASL_OK)
{
authenticated:
message("235 2.0.0 OK Authenticated");
authenticating = SASL_IS_AUTH;
macdefine(&BlankEnvelope.e_macro, A_TEMP,
macid("{auth_type}"), auth_type);
# if SASL >= 20000
user = macvalue(macid("{auth_authen}"), e);
result = sasl_getprop(conn, SASL_SSF,
(const void **) &ssf);
# else
result = sasl_getprop(conn, SASL_USERNAME,
(void **)&user);
if (result != SASL_OK)
{
user = "";
macdefine(&BlankEnvelope.e_macro,
A_PERM,
macid("{auth_authen}"), NULL);
}
else
{
macdefine(&BlankEnvelope.e_macro,
A_TEMP,
macid("{auth_authen}"),
xtextify(user, "<>\")"));
}
# if 0
sasl_getprop(conn, SASL_REALM, (void **) &data);
# endif
result = sasl_getprop(conn, SASL_SSF,
(void **) &ssf);
# endif
if (result != SASL_OK)
{
macdefine(&BlankEnvelope.e_macro,
A_PERM,
macid("{auth_ssf}"), "0");
ssf = NULL;
}
else
{
char pbuf[8];
(void) sm_snprintf(pbuf, sizeof(pbuf),
"%u", *ssf);
macdefine(&BlankEnvelope.e_macro,
A_TEMP,
macid("{auth_ssf}"), pbuf);
if (tTd(95, 8))
sm_dprintf("AUTH auth_ssf: %u\n",
*ssf);
}
if (ssf != NULL && *ssf > 0)
{
int tmo;
tmo = TimeOuts.to_datablock * 1000;
if (sfdcsasl(&InChannel, &OutChannel,
conn, tmo) == 0)
{
n_helo = 0;
# if PIPELINING
(void) sm_io_autoflush(InChannel,
OutChannel);
# endif
}
else
syserr("503 5.3.3 SASL TLS failed");
}
if (LogLevel > 8)
sm_syslog(LOG_INFO, NOQID,
"AUTH=server, relay=%s, authid=%.128s, mech=%.16s, bits=%d",
CurSmtpClient,
shortenstring(user, 128),
auth_type, *ssf);
}
else if (result == SASL_CONTINUE)
{
len = ENC64LEN(outlen);
out2 = xalloc(len);
result = sasl_encode64(out, outlen, out2, len,
&out2len);
if (result != SASL_OK)
{
message("454 4.5.4 Internal error: unable to encode64");
if (LogLevel > 5)
sm_syslog(LOG_WARNING, e->e_id,
"AUTH encode64 error [%d for \"%s\"], relay=%.100s",
result, out,
CurSmtpClient);
authenticating = SASL_NOT_AUTH;
}
else
{
message("334 %s", out2);
if (tTd(95, 2))
sm_dprintf("AUTH continue: msg='%s' len=%u\n",
out2, out2len);
}
# if SASL >= 20000
sm_free(out2);
# endif
}
else
{
message("535 5.7.0 authentication failed");
if (LogLevel > 9)
sm_syslog(LOG_WARNING, e->e_id,
"AUTH failure (%s): %s (%d) %s, relay=%.100s",
auth_type,
sasl_errstring(result, NULL,
NULL),
result,
# if SASL >= 20000
sasl_errdetail(conn),
# else
errstr == NULL ? "" : errstr,
# endif
CurSmtpClient);
RESET_SASLCONN;
authenticating = SASL_NOT_AUTH;
}
}
else
{
#endif
if (e->e_xfp != NULL)
(void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT,
"<<< %s\n", inp);
if (LogLevel > 14)
sm_syslog(LOG_INFO, e->e_id, "<-- %s", inp);
for (p = inp; isascii(*p) && isspace(*p); p++)
continue;
cmd = cmdbuf;
while (*p != '\0' &&
!(isascii(*p) && isspace(*p)) &&
cmd < &cmdbuf[sizeof(cmdbuf) - 2])
*cmd++ = *p++;
*cmd = '\0';
SKIP_SPACE(p);
for (c = CmdTab; c->cmd_name != NULL; c++)
{
if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0)
break;
}
errno = 0;
switch (c->cmd_code)
{
#if SASL
case CMDAUTH:
q = "AUTH";
break;
#endif
case CMDMAIL:
case CMDEXPN:
case CMDVRFY:
case CMDETRN:
lognullconnection = false;
default:
q = inp;
break;
}
if (e->e_id == NULL)
sm_setproctitle(true, e, "%s: %.80s",
CurSmtpClient, q);
else
sm_setproctitle(true, e, "%s %s: %.80s",
qid_printname(e),
CurSmtpClient, q);
if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags))
{
switch (c->cmd_code)
{
case CMDQUIT:
case CMDHELO:
case CMDEHLO:
case CMDNOOP:
case CMDRSET:
case CMDERROR:
break;
case CMDETRN:
if (bitnset(D_ETRNONLY, d_flags) &&
nullserver == NULL)
break;
DELAY_CONN("ETRN");
default:
#if MAXBADCOMMANDS > 0
if (nullserver != NULL &&
++n_badcmds > MAXBADCOMMANDS)
{
message("421 4.7.0 %s Too many bad commands; closing connection",
MyHostName);
e->e_sendqueue = NULL;
goto doquit;
}
#endif
if (nullserver != NULL)
{
if (ISSMTPREPLY(nullserver))
usrerr(nullserver);
else
usrerr("550 5.0.0 %s",
nullserver);
}
else
usrerr("452 4.4.5 Insufficient disk space; try again later");
continue;
}
}
switch (c->cmd_code)
{
#if SASL
case CMDAUTH:
DELAY_CONN("AUTH");
if (!sasl_ok || n_mechs <= 0)
{
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)
sm_syslog(LOG_INFO, e->e_id,
"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;
STOP_IF_ATTACK(checksmtpattack(&n_auth, n_mechs + 1,
true, "AUTH", e));
for (q = p; *q != '\0' && isascii(*q); q++)
{
if (isspace(*q))
{
*q = '\0';
while (*++q != '\0' &&
isascii(*q) && isspace(*q))
continue;
*(q - 1) = '\0';
ismore = (*q != '\0');
break;
}
}
if (*p == '\0')
{
message("501 5.5.2 AUTH mechanism must be specified");
break;
}
if (iteminlist(p, mechlist, " ") == NULL)
{
message("504 5.3.3 AUTH mechanism %.32s not available",
p);
break;
}
if (ismore && *q == '=' && *(q + 1) == '\0')
{
in = xalloc(1);
*in = '\0';
inlen = 0;
}
else if (ismore)
{
# if SASL >= 20000
in = xalloc(strlen(q) + 1);
result = sasl_decode64(q, strlen(q), in,
strlen(q), &inlen);
# else
in = sm_rpool_malloc(e->e_rpool, strlen(q));
result = sasl_decode64(q, strlen(q), in,
&inlen);
# endif
if (result != SASL_OK)
{
message("501 5.5.4 cannot BASE64 decode '%s'",
q);
if (LogLevel > 5)
sm_syslog(LOG_WARNING, e->e_id,
"AUTH decode64 error [%d for \"%s\"], relay=%.100s",
result, q,
CurSmtpClient);
authenticating = SASL_NOT_AUTH;
# if SASL >= 20000
sm_free(in);
# endif
in = NULL;
inlen = 0;
break;
}
}
else
{
in = NULL;
inlen = 0;
}
# if SASL >= 20000
result = sasl_server_start(conn, p, in, inlen,
&out, &outlen);
if (in != NULL)
sm_free(in);
# else
result = sasl_server_start(conn, p, in, inlen,
&out, &outlen, &errstr);
# endif
if (result != SASL_OK && result != SASL_CONTINUE)
{
message("535 5.7.0 authentication failed");
if (LogLevel > 9)
sm_syslog(LOG_ERR, e->e_id,
"AUTH failure (%s): %s (%d) %s, relay=%.100s",
p,
sasl_errstring(result, NULL,
NULL),
result,
# if SASL >= 20000
sasl_errdetail(conn),
# else
errstr,
# endif
CurSmtpClient);
RESET_SASLCONN;
break;
}
auth_type = newstr(p);
if (result == SASL_OK)
{
goto authenticated;
}
len = ENC64LEN(outlen);
out2 = xalloc(len);
result = sasl_encode64(out, outlen, out2, len,
&out2len);
if (result != SASL_OK)
{
message("454 4.5.4 Temporary authentication failure");
if (LogLevel > 5)
sm_syslog(LOG_WARNING, e->e_id,
"AUTH encode64 error [%d for \"%s\"]",
result, out);
authenticating = SASL_NOT_AUTH;
RESET_SASLCONN;
}
else
{
message("334 %s", out2);
authenticating = SASL_PROC_AUTH;
}
# if SASL >= 20000
sm_free(out2);
# endif
break;
#endif
#if STARTTLS
case CMDSTLS:
DELAY_CONN("STARTTLS");
if (*p != '\0')
{
message("501 5.5.2 Syntax error (no parameters allowed)");
break;
}
if (!bitset(SRV_OFFER_TLS, features))
{
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)
sm_syslog(LOG_INFO, e->e_id,
"SMTP STARTTLS command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
usrerr("454 4.7.0 Please try again later");
break;
}
starttls:
# if TLS_NO_RSA
# else
# endif
# if TLS_VRFY_PER_CTX
TLS_VERIFY_CLIENT();
# endif
if (srv_ssl != NULL)
SSL_clear(srv_ssl);
else if ((srv_ssl = SSL_new(srv_ctx)) == NULL)
{
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
TLS_VERIFY_CLIENT();
# endif
rfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
wfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
if (rfd < 0 || wfd < 0 ||
SSL_set_rfd(srv_ssl, rfd) <= 0 ||
SSL_set_wfd(srv_ssl, wfd) <= 0)
{
message("454 4.3.3 TLS not available: error set fd");
SSL_free(srv_ssl);
srv_ssl = NULL;
goto tls_done;
}
if (!smtps)
message("220 2.0.0 Ready to start TLS");
# if PIPELINING
(void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
# endif
SSL_set_accept_state(srv_ssl);
# define SSL_ACC(s) SSL_accept(s)
tlsstart = curtime();
ssl_retry:
if ((r = SSL_ACC(srv_ssl)) <= 0)
{
int i, ssl_err;
ssl_err = SSL_get_error(srv_ssl, r);
i = tls_retry(srv_ssl, rfd, wfd, tlsstart,
TimeOuts.to_starttls, ssl_err,
"server");
if (i > 0)
goto ssl_retry;
if (LogLevel > 5)
{
sm_syslog(LOG_WARNING, NOQID,
"STARTTLS=server, error: accept failed=%d, SSL_error=%d, errno=%d, retry=%d, relay=%.100s",
r, ssl_err, errno, i,
CurSmtpClient);
if (LogLevel > 8)
tlslogerr("server");
}
tls_ok_srv = false;
SSL_free(srv_ssl);
srv_ssl = NULL;
e->e_sendqueue = NULL;
goto doquit;
}
(void) tls_get_info(srv_ssl, true,
CurSmtpClient,
&BlankEnvelope.e_macro,
bitset(SRV_VRFY_CLT, features));
saveQuickAbort = QuickAbort;
saveSuprErrs = SuprErrs;
SuprErrs = true;
QuickAbort = false;
if (rscheck("tls_client",
macvalue(macid("{verify}"), e),
"STARTTLS", e,
RSF_RMCOMM|RSF_COUNT,
5, NULL, NOQID, NULL) != EX_OK ||
Errors > 0)
{
extern char MsgBuf[];
if (MsgBuf[0] != '\0' && ISSMTPREPLY(MsgBuf))
nullserver = newstr(MsgBuf);
else
nullserver = "503 5.7.0 Authentication required.";
}
QuickAbort = saveQuickAbort;
SuprErrs = saveSuprErrs;
tls_ok_srv = false;
n_helo = 0;
# if SASL
if (sasl_ok)
{
int cipher_bits;
bool verified;
char *s, *v, *c;
s = macvalue(macid("{cipher_bits}"), e);
v = macvalue(macid("{verify}"), e);
c = macvalue(macid("{cert_subject}"), e);
verified = (v != NULL && strcmp(v, "OK") == 0);
if (s != NULL && (cipher_bits = atoi(s)) > 0)
{
# if SASL >= 20000
ext_ssf = cipher_bits;
auth_id = verified ? c : NULL;
sasl_ok = ((sasl_setprop(conn,
SASL_SSF_EXTERNAL,
&ext_ssf) == SASL_OK) &&
(sasl_setprop(conn,
SASL_AUTH_EXTERNAL,
auth_id) == SASL_OK));
# else
ext_ssf.ssf = cipher_bits;
ext_ssf.auth_id = verified ? c : NULL;
sasl_ok = sasl_setprop(conn,
SASL_SSF_EXTERNAL,
&ext_ssf) == SASL_OK;
# endif
mechlist = NULL;
if (sasl_ok)
n_mechs = saslmechs(conn,
&mechlist);
}
}
# endif
if (sfdctls(&InChannel, &OutChannel, srv_ssl) == 0)
{
tls_active = true;
# if PIPELINING
(void) sm_io_autoflush(InChannel, OutChannel);
# endif
}
else
{
nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer";
syserr("STARTTLS: can't switch to encrypted layer");
}
tls_done:
if (smtps)
{
if (tls_active)
goto greeting;
else
goto doquit;
}
break;
#endif
case CMDHELO:
case CMDEHLO:
DELAY_CONN("EHLO");
if (c->cmd_code == CMDEHLO)
{
protocol = "ESMTP";
SmtpPhase = "server EHLO";
}
else
{
protocol = "SMTP";
SmtpPhase = "server HELO";
}
STOP_IF_ATTACK(checksmtpattack(&n_helo, MAXHELOCOMMANDS,
true, "HELO/EHLO", e));
#if 0
if (gothello)
{
usrerr("503 %s Duplicate HELO/EHLO",
MyHostName);
break;
}
#endif
if (*p == '\0' && !AllowBogusHELO)
{
usrerr("501 %s requires domain address",
cmdbuf);
break;
}
if (strlen(p) > MAXNAME)
{
usrerr("501 Invalid domain name");
if (LogLevel > 9)
sm_syslog(LOG_INFO, CurEnv->e_id,
"invalid domain name (too long) from %s",
CurSmtpClient);
break;
}
ok = true;
for (q = p; *q != '\0'; q++)
{
if (!isascii(*q))
break;
if (isalnum(*q))
continue;
if (isspace(*q))
{
*q = '\0';
ok = AllowBogusHELO;
while (!ok && *++q != '\0' &&
isspace(*q))
;
if (*q == '\0')
ok = true;
break;
}
if (strchr("[].-_#:", *q) == NULL)
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)
sm_syslog(LOG_INFO, CurEnv->e_id,
"invalid domain name (%s) from %.100s",
p, CurSmtpClient);
break;
}
else
{
q = "accepting invalid domain name";
}
if (gothello || smtp.sm_gotmail)
CLEAR_STATE(cmdbuf);
#if MILTER
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
response = milter_helo(p, e, &state);
switch (state)
{
case SMFIR_REJECT:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: helo=%s, reject=Command rejected",
p);
nullserver = "Command rejected";
smtp.sm_milterize = false;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: helo=%s, reject=%s",
p, MSG_TEMPFAIL);
tempfail = true;
smtp.sm_milterize = false;
break;
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: helo=%s, reject=%s",
p, response);
if (strncmp(response, "421 ", 4) != 0
&& strncmp(response, "421-", 4) != 0)
{
nullserver = newstr(response);
smtp.sm_milterize = false;
break;
}
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3 &&
response == NULL)
sm_syslog(LOG_INFO, e->e_id,
"Milter: helo=%s, reject=421 4.7.0 %s closing connection",
p, MyHostName);
tempfail = true;
smtp.sm_milterize = false;
if (response != NULL)
usrerr(response);
else
message("421 4.7.0 %s closing connection",
MyHostName);
e->e_sendqueue = NULL;
lognullconnection = false;
goto doquit;
}
if (response != NULL)
sm_free(response);
if (smtp.sm_quarmsg == NULL &&
e->e_quarmsg != NULL)
smtp.sm_quarmsg = newstr(e->e_quarmsg);
}
#endif
gothello = true;
if (c->cmd_code != CMDEHLO)
{
message("250 %s Hello %s, %s",
MyHostName, CurSmtpClient, q);
break;
}
message("250-%s Hello %s, %s",
MyHostName, CurSmtpClient, q);
if (nullserver != NULL)
{
message("250 ENHANCEDSTATUSCODES");
break;
}
message("250-ENHANCEDSTATUSCODES");
#if PIPELINING
if (bitset(SRV_OFFER_PIPE, features))
message("250-PIPELINING");
#endif
if (bitset(SRV_OFFER_EXPN, features))
{
message("250-EXPN");
if (bitset(SRV_OFFER_VERB, features))
message("250-VERB");
}
#if MIME8TO7
message("250-8BITMIME");
#endif
if (MaxMessageSize > 0)
message("250-SIZE %ld", MaxMessageSize);
else
message("250-SIZE");
#if DSN
if (SendMIMEErrors && bitset(SRV_OFFER_DSN, features))
message("250-DSN");
#endif
if (bitset(SRV_OFFER_ETRN, features))
message("250-ETRN");
#if SASL
if (sasl_ok && mechlist != NULL && *mechlist != '\0')
message("250-AUTH %s", mechlist);
#endif
#if STARTTLS
if (tls_ok_srv &&
bitset(SRV_OFFER_TLS, features))
message("250-STARTTLS");
#endif
if (DeliverByMin > 0)
message("250-DELIVERBY %ld",
(long) DeliverByMin);
else if (DeliverByMin == 0)
message("250-DELIVERBY");
message("250 HELP");
break;
case CMDMAIL:
SmtpPhase = "server MAIL";
DELAY_CONN("MAIL");
if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags))
{
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
if (bitset(SRV_REQ_AUTH, features) &&
authenticating != SASL_IS_AUTH)
{
usrerr("530 5.7.0 Authentication required");
break;
}
#endif
p = skipword(p, "from");
if (p == NULL)
break;
if (tempfail)
{
if (LogLevel > 9)
sm_syslog(LOG_INFO, e->e_id,
"SMTP MAIL command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
usrerr(MSG_TEMPFAIL);
break;
}
if (sendinghost == NULL)
sendinghost = peerhostname;
#if SM_HEAP_CHECK
if (sm_debug_active(&DebugLeakSmtp, 1))
{
sm_heap_newgroup();
sm_dprintf("smtp() heap group #%d\n",
sm_heap_group());
}
#endif
if (Errors > 0)
goto undo_no_pm;
if (!gothello)
{
auth_warning(e, "%s didn't use HELO protocol",
CurSmtpClient);
}
#ifdef PICKY_HELO_CHECK
if (sm_strcasecmp(sendinghost, peerhostname) != 0 &&
(sm_strcasecmp(peerhostname, "localhost") != 0 ||
sm_strcasecmp(sendinghost, MyHostName) != 0))
{
auth_warning(e, "Host %s claimed to be %s",
CurSmtpClient, sendinghost);
}
#endif
if (protocol == NULL)
protocol = "SMTP";
macdefine(&e->e_macro, A_PERM, 'r', protocol);
macdefine(&e->e_macro, A_PERM, 's', sendinghost);
if (Errors > 0)
goto undo_no_pm;
smtp.sm_nrcpts = 0;
n_badrcpts = 0;
macdefine(&e->e_macro, A_PERM, macid("{ntries}"), "0");
macdefine(&e->e_macro, A_PERM, macid("{nrcpts}"), "0");
macdefine(&e->e_macro, A_PERM, macid("{nbadrcpts}"),
"0");
e->e_flags |= EF_CLRQUEUE;
sm_setproctitle(true, e, "%s %s: %.80s",
qid_printname(e),
CurSmtpClient, inp);
SM_TRY
{
extern char *FullName;
QuickAbort = true;
SM_FREE_CLR(FullName);
delimptr = NULL;
setsender(p, e, &delimptr, ' ', false);
if (delimptr != NULL && *delimptr != '\0')
*delimptr++ = '\0';
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
e->e_flags |= EF_LOGSENDER;
if (e->e_from.q_mailer != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{mail_mailer}"),
e->e_from.q_mailer->m_name);
else
macdefine(&e->e_macro, A_PERM,
macid("{mail_mailer}"), NULL);
if (e->e_from.q_host != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{mail_host}"),
e->e_from.q_host);
else
macdefine(&e->e_macro, A_PERM,
macid("{mail_host}"), "localhost");
if (e->e_from.q_user != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{mail_addr}"),
e->e_from.q_user);
else
macdefine(&e->e_macro, A_PERM,
macid("{mail_addr}"), NULL);
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
if (RealUid != 0 && OpMode == MD_SMTP &&
!wordinclass(RealUserName, 't') &&
(!bitnset(M_LOCALMAILER,
e->e_from.q_mailer->m_flags) ||
strcmp(e->e_from.q_user, RealUserName) != 0))
{
auth_warning(e, "%s owned process doing -bs",
RealUserName);
}
SevenBitInput = SevenBitInput_Saved;
e->e_msgsize = 0;
addr = p;
parse_esmtp_args(e, NULL, p, delimptr, "MAIL", args,
mail_esmtp_args);
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
#if SASL
# if _FFR_AUTH_PASSING
if (e->e_auth_param == NULL)
{
e->e_auth_param = macvalue(macid("{auth_authen}"),
e);
if (e->e_auth_param == NULL)
e->e_auth_param = "<>";
}
# endif
#endif
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), "e s");
#if _FFR_MAIL_MACRO
macdefine(&e->e_macro, A_TEMP, macid("{mail_from}"),
e->e_from.q_paddr);
#endif
if (rscheck("check_mail", addr,
NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
NULL, e->e_id, NULL) != EX_OK ||
Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), NULL);
if (MaxMessageSize > 0 &&
(e->e_msgsize > MaxMessageSize ||
e->e_msgsize < 0))
{
usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)",
MaxMessageSize);
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
}
if ((NumFileSys == 1 || NumQueue == 1) &&
!enoughdiskspace(e->e_msgsize, e)
#if _FFR_ANY_FREE_FS
&& !filesys_free(e->e_msgsize)
#endif
)
{
usrerr("452 4.4.5 Insufficient disk space; try again later");
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
}
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
LogUsrErrs = true;
#if MILTER
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
response = milter_envfrom(args, e, &state);
MILTER_REPLY("from");
}
#endif
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
message("250 2.1.0 Sender ok");
smtp.sm_gotmail = true;
}
SM_EXCEPT(exc, "[!F]*")
{
sm_exc_free(exc);
goto undo_no_pm;
}
SM_END_TRY
break;
undo_no_pm:
e->e_flags &= ~EF_PM_NOTIFY;
undo:
break;
case CMDRCPT:
DELAY_CONN("RCPT");
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"), NULL);
#if MILTER
(void) memset(&addr_st, '\0', sizeof(addr_st));
a = NULL;
milter_rcpt_added = false;
smtp.sm_e_nrcpts_orig = e->e_nrcpts;
#endif
#if _FFR_BADRCPT_SHUTDOWN
n_badrcpts_adj = (BadRcptThrottle > 0 &&
n_badrcpts > BadRcptThrottle &&
LogLevel > 5)
? n_badrcpts - 1 : n_badrcpts;
if (BadRcptShutdown > 0 &&
n_badrcpts_adj >= BadRcptShutdown &&
(BadRcptShutdownGood == 0 ||
smtp.sm_nrcpts == 0 ||
(n_badrcpts_adj * 100 /
(smtp.sm_nrcpts + n_badrcpts) >=
BadRcptShutdownGood)))
{
if (LogLevel > 5)
sm_syslog(LOG_INFO, e->e_id,
"%s: Possible SMTP RCPT flood, shutting down connection.",
CurSmtpClient);
message("421 4.7.0 %s Too many bad recipients; closing connection",
MyHostName);
e->e_sendqueue = NULL;
goto doquit;
}
#endif
if (BadRcptThrottle > 0 &&
n_badrcpts >= BadRcptThrottle)
{
if (LogLevel > 5 &&
n_badrcpts == BadRcptThrottle)
{
sm_syslog(LOG_INFO, e->e_id,
"%s: Possible SMTP RCPT flood, throttling.",
CurSmtpClient);
n_badrcpts++;
}
NBADRCPTS;
(void) sleep(BadRcptThrottleDelay);
}
if (!smtp.sm_gotmail)
{
usrerr("503 5.0.0 Need MAIL before RCPT");
break;
}
SmtpPhase = "server RCPT";
SM_TRY
{
QuickAbort = true;
LogUsrErrs = true;
if (MaxRcptPerMsg > 0 &&
smtp.sm_nrcpts >= MaxRcptPerMsg)
{
usrerr("452 4.5.3 Too many recipients");
goto rcpt_done;
}
if (e->e_sendmode != SM_DELIVER
#if _FFR_DM_ONE
&& (NotFirstDelivery || SM_DM_ONE != e->e_sendmode)
#endif
)
e->e_flags |= EF_VRFYONLY;
#if MILTER
if (!(smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags)) &&
(smtp.sm_milters.mis_flags &
(MIS_FL_DEL_RCPT|MIS_FL_REJ_RCPT)) != 0)
e->e_flags |= EF_VRFYONLY;
milter_cmd_done = false;
milter_cmd_safe = false;
#endif
p = skipword(p, "to");
if (p == NULL)
goto rcpt_done;
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), "e r");
a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr,
e, true);
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), NULL);
if (Errors > 0)
goto rcpt_done;
if (a == NULL)
{
usrerr("501 5.0.0 Missing recipient");
goto rcpt_done;
}
if (delimptr != NULL && *delimptr != '\0')
*delimptr++ = '\0';
if (a->q_mailer != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"),
a->q_mailer->m_name);
else
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"), NULL);
if (a->q_host != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"), a->q_host);
else
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"), "localhost");
if (a->q_user != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"), a->q_user);
else
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"), NULL);
if (Errors > 0)
goto rcpt_done;
addr = p;
parse_esmtp_args(e, a, p, delimptr, "RCPT", args,
rcpt_esmtp_args);
if (Errors > 0)
goto rcpt_done;
#if MILTER
# if _FFR_MILTER_CHECK_REJECTIONS_TOO
milter_cmd_safe = true;
# endif
#endif
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), "e r");
if (rscheck("check_rcpt", addr,
NULL, e, RSF_RMCOMM|RSF_COUNT, 3,
NULL, e->e_id, p_addr_st) != EX_OK ||
Errors > 0)
goto rcpt_done;
macdefine(&e->e_macro, A_PERM,
macid("{addr_type}"), NULL);
if (bitset(EF_DISCARD, e->e_flags))
a->q_state = QS_VERIFIED;
#if MILTER
milter_cmd_safe = true;
#endif
a = recipient(a, &e->e_sendqueue, 0, e);
#if MILTER
milter_rcpt_added = true;
#endif
if(!(Errors > 0) && QS_IS_BADADDR(a->q_state))
{
usrerr("550 5.1.1 Addressee unknown");
}
#if MILTER
rcpt_done:
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
if (Errors > 0)
{
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"),
"error");
if (a != NULL &&
a->q_status != NULL &&
a->q_rstatus != NULL)
{
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"),
a->q_status);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"),
a->q_rstatus);
}
else
{
if (addr_st.q_host != NULL)
macdefine(&e->e_macro,
A_PERM,
macid("{rcpt_host}"),
addr_st.q_host);
if (addr_st.q_user != NULL)
macdefine(&e->e_macro,
A_PERM,
macid("{rcpt_addr}"),
addr_st.q_user);
}
}
response = milter_envrcpt(args, e, &state,
Errors > 0);
milter_cmd_done = true;
MILTER_REPLY("to");
}
#endif
e->e_to = a->q_paddr;
if (!(Errors > 0) && !QS_IS_BADADDR(a->q_state))
{
if (smtp.sm_nrcpts == 0)
initsys(e);
message("250 2.1.5 Recipient ok%s",
QS_IS_QUEUEUP(a->q_state) ?
" (will queue)" : "");
smtp.sm_nrcpts++;
}
#if !MILTER
rcpt_done:
#endif
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{dsn_notify}"), NULL);
if (Errors > 0)
{
++n_badrcpts;
NBADRCPTS;
}
}
SM_EXCEPT(exc, "[!F]*")
{
e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY);
++n_badrcpts;
NBADRCPTS;
#if MILTER
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags) &&
!milter_cmd_done && milter_cmd_safe)
{
char state;
char *response;
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"), "error");
if (addr_st.q_host != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"),
addr_st.q_host);
else if (a != NULL && a->q_status != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"),
a->q_status);
if (addr_st.q_user != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"),
addr_st.q_user);
else if (a != NULL && a->q_rstatus != NULL)
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"),
a->q_rstatus);
response = milter_envrcpt(args, e, &state,
true);
milter_cmd_done = true;
MILTER_REPLY("to");
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_mailer}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_host}"), NULL);
macdefine(&e->e_macro, A_PERM,
macid("{rcpt_addr}"), NULL);
}
if (smtp.sm_milterlist && smtp.sm_milterize &&
milter_rcpt_added && milter_cmd_done &&
milter_cmd_fail)
{
(void) removefromlist(addr, &e->e_sendqueue, e);
milter_cmd_fail = false;
if (smtp.sm_e_nrcpts_orig < e->e_nrcpts)
e->e_nrcpts = smtp.sm_e_nrcpts_orig;
}
#endif
}
SM_END_TRY
break;
case CMDDATA:
DELAY_CONN("DATA");
if (!smtp_data(&smtp, e))
goto doquit;
break;
case CMDRSET:
if (tTd(94, 100))
message("451 4.0.0 Test failure");
else
message("250 2.0.0 Reset state");
CLEAR_STATE(cmdbuf);
break;
case CMDVRFY:
case CMDEXPN:
vrfy = c->cmd_code == CMDVRFY;
DELAY_CONN(vrfy ? "VRFY" : "EXPN");
if (tempfail)
{
if (LogLevel > 9)
sm_syslog(LOG_INFO, e->e_id,
"SMTP %s command (%.100s) from %s tempfailed (due to previous checks)",
vrfy ? "VRFY" : "EXPN",
p, CurSmtpClient);
usrerr("550 5.7.1 Please try again later");
break;
}
wt = checksmtpattack(&n_verifies, MAXVRFYCOMMANDS,
false, vrfy ? "VRFY" : "EXPN", e);
STOP_IF_ATTACK(wt);
previous = curtime();
if ((vrfy && bitset(PRIV_NOVRFY, PrivacyFlags)) ||
(!vrfy && !bitset(SRV_OFFER_EXPN, features)))
{
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)
sm_syslog(LOG_INFO, e->e_id,
"%s: %s [rejected]",
CurSmtpClient,
shortenstring(inp, MAXSHORTSTR));
break;
}
else if (!gothello &&
bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO,
PrivacyFlags))
{
usrerr("503 5.0.0 I demand that you introduce yourself first");
break;
}
if (Errors > 0)
break;
if (LogLevel > 5)
sm_syslog(LOG_INFO, e->e_id, "%s: %s",
CurSmtpClient,
shortenstring(inp, MAXSHORTSTR));
SM_TRY
{
QuickAbort = true;
vrfyqueue = NULL;
if (vrfy)
e->e_flags |= EF_VRFYONLY;
while (*p != '\0' && isascii(*p) && isspace(*p))
p++;
if (*p == '\0')
{
usrerr("501 5.5.2 Argument required");
}
else
{
if (rscheck(vrfy ? "check_vrfy" : "check_expn",
p, NULL, e, RSF_RMCOMM,
3, NULL, NOQID, NULL) != EX_OK ||
Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
(void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e);
}
if (wt > 0)
{
time_t t;
t = wt - (curtime() - previous);
if (t > 0)
(void) sleep(t);
}
if (Errors > 0)
sm_exc_raisenew_x(&EtypeQuickAbort, 1);
if (vrfyqueue == NULL)
{
usrerr("554 5.5.2 Nothing to %s", vrfy ? "VRFY" : "EXPN");
}
while (vrfyqueue != NULL)
{
if (!QS_IS_UNDELIVERED(vrfyqueue->q_state))
{
vrfyqueue = vrfyqueue->q_next;
continue;
}
a = vrfyqueue;
while ((a = a->q_next) != NULL &&
(!QS_IS_UNDELIVERED(a->q_state)))
continue;
printvrfyaddr(vrfyqueue, a == NULL, vrfy);
vrfyqueue = a;
}
}
SM_EXCEPT(exc, "[!F]*")
{
sm_exc_free(exc);
goto undo;
}
SM_END_TRY
break;
case CMDETRN:
DELAY_CONN("ETRN");
if (!bitset(SRV_OFFER_ETRN, features) || UseMSP ||
(RealUid != 0 && RealUid != TrustedUid &&
OpMode == MD_SMTP))
{
message("502 5.7.0 Sorry, we do not allow this operation");
if (LogLevel > 5)
sm_syslog(LOG_INFO, e->e_id,
"%s: %s [rejected]",
CurSmtpClient,
shortenstring(inp, MAXSHORTSTR));
break;
}
if (tempfail)
{
if (LogLevel > 9)
sm_syslog(LOG_INFO, e->e_id,
"SMTP ETRN command (%.100s) from %s tempfailed (due to previous checks)",
p, CurSmtpClient);
usrerr(MSG_TEMPFAIL);
break;
}
if (strlen(p) <= 0)
{
usrerr("500 5.5.2 Parameter required");
break;
}
STOP_IF_ATTACK(checksmtpattack(&n_etrn, MAXETRNCOMMANDS,
true, "ETRN", e));
if (rscheck("check_etrn", p, NULL, e,
RSF_RMCOMM, 3, NULL, NOQID, NULL)
!= EX_OK ||
Errors > 0)
break;
if (LogLevel > 5)
sm_syslog(LOG_INFO, e->e_id,
"%s: ETRN %s", CurSmtpClient,
shortenstring(p, MAXSHORTSTR));
id = p;
if (*id == '#')
{
int i, qgrp;
id++;
qgrp = name2qid(id);
if (!ISVALIDQGRP(qgrp))
{
usrerr("459 4.5.4 Queue %s unknown",
id);
break;
}
for (i = 0; i < NumQueue && Queue[i] != NULL;
i++)
Queue[i]->qg_nextrun = (time_t) -1;
Queue[qgrp]->qg_nextrun = 0;
ok = run_work_group(Queue[qgrp]->qg_wgrp,
RWG_FORK|RWG_FORCE);
if (ok && Errors == 0)
message("250 2.0.0 Queuing for queue group %s started", id);
break;
}
if (*id == '@')
id++;
else
*--id = '@';
new = (QUEUE_CHAR *) sm_malloc(sizeof(QUEUE_CHAR));
if (new == NULL)
{
syserr("500 5.5.0 ETRN out of memory");
break;
}
new->queue_match = id;
new->queue_negate = false;
new->queue_next = NULL;
QueueLimitRecipient = new;
ok = runqueue(true, false, false, true);
sm_free(QueueLimitRecipient);
QueueLimitRecipient = NULL;
if (ok && Errors == 0)
message("250 2.0.0 Queuing for node %s started", p);
break;
case CMDHELP:
DELAY_CONN("HELP");
help(p, e);
break;
case CMDNOOP:
DELAY_CONN("NOOP");
STOP_IF_ATTACK(checksmtpattack(&n_noop, MaxNOOPCommands,
true, "NOOP", e));
message("250 2.0.0 OK");
break;
case CMDQUIT:
message("221 2.0.0 %s closing connection", MyHostName);
#if PIPELINING
(void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
#endif
if (smtp.sm_nrcpts > 0)
logundelrcpts(e, "aborted by sender", 9, false);
e->e_sendqueue = NULL;
#if STARTTLS
if (tls_active)
{
(void) endtls(srv_ssl, "server");
tls_active = false;
}
#endif
#if SASL
if (authenticating == SASL_IS_AUTH)
{
sasl_dispose(&conn);
authenticating = SASL_NOT_AUTH;
}
#endif
doquit:
disconnect(1, e);
#if MILTER
milter_quit(e);
#endif
if (tTd(92, 2))
sm_dprintf("QUIT: e_id=%s, EF_LOGSENDER=%d, LogLevel=%d\n",
e->e_id,
bitset(EF_LOGSENDER, e->e_flags),
LogLevel);
if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
logsender(e, NULL);
e->e_flags &= ~EF_LOGSENDER;
if (lognullconnection && LogLevel > 5 &&
nullserver == NULL)
{
char *d;
d = macvalue(macid("{daemon_name}"), e);
if (d == NULL)
d = "stdin";
sm_syslog(LOG_INFO, e->e_id,
"%s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s",
CurSmtpClient, d);
}
if (tTd(93, 100))
{
return;
}
finis(true, true, ExitStat);
exit(EX_OSERR);
case CMDVERB:
DELAY_CONN("VERB");
if (!bitset(SRV_OFFER_EXPN, features) ||
!bitset(SRV_OFFER_VERB, features))
{
message("502 5.7.0 Verbose unavailable");
break;
}
STOP_IF_ATTACK(checksmtpattack(&n_noop, MaxNOOPCommands,
true, "VERB", e));
Verbose = 1;
set_delivery_mode(SM_DELIVER, e);
message("250 2.0.0 Verbose mode");
break;
#if SMTPDEBUG
case CMDDBGQSHOW:
(void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
"Send Queue=");
printaddr(smioout, e->e_sendqueue, true);
break;
case CMDDBGDEBUG:
tTsetup(tTdvect, sizeof(tTdvect), "0-99.1");
tTflag(p);
message("200 2.0.0 Debug set");
break;
#else
case CMDDBGQSHOW:
case CMDDBGDEBUG:
#endif
case CMDLOGBOGUS:
DELAY_CONN("Bogus");
if (LogLevel > 0)
sm_syslog(LOG_CRIT, e->e_id,
"\"%s\" command from %s (%.100s)",
c->cmd_name, CurSmtpClient,
anynet_ntoa(&RealHostAddr));
case CMDERROR:
#if MAXBADCOMMANDS > 0
if (++n_badcmds > MAXBADCOMMANDS)
{
stopattack:
message("421 4.7.0 %s Too many bad commands; closing connection",
MyHostName);
e->e_sendqueue = NULL;
goto doquit;
}
#endif
#if MILTER && SMFI_VERSION > 2
if (smtp.sm_milterlist && smtp.sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
if (MilterLogLevel > 9)
sm_syslog(LOG_INFO, e->e_id,
"Sending \"%s\" to Milter", inp);
response = milter_unknown(inp, e, &state);
MILTER_REPLY("unknown");
if (state == SMFIR_REPLYCODE ||
state == SMFIR_REJECT ||
state == SMFIR_TEMPFAIL ||
state == SMFIR_SHUTDOWN)
{
break;
}
}
#endif
usrerr("500 5.5.1 Command unrecognized: \"%s\"",
shortenstring(inp, MAXSHORTSTR));
break;
case CMDUNIMPL:
DELAY_CONN("Unimpl");
usrerr("502 5.5.1 Command not implemented: \"%s\"",
shortenstring(inp, MAXSHORTSTR));
break;
default:
DELAY_CONN("default");
errno = 0;
syserr("500 5.5.0 smtp: unknown code %d", c->cmd_code);
break;
}
#if SASL
}
#endif
}
SM_EXCEPT(exc, "[!F]*")
{
}
SM_END_TRY
}
}
static bool
smtp_data(smtp, e)
SMTP_T *smtp;
ENVELOPE *e;
{
#if MILTER
bool milteraccept;
#endif
bool aborting;
bool doublequeue;
bool rv = true;
ADDRESS *a;
ENVELOPE *ee;
char *id;
char *oldid;
unsigned int features;
char buf[32];
SmtpPhase = "server DATA";
if (!smtp->sm_gotmail)
{
usrerr("503 5.0.0 Need MAIL command");
return true;
}
else if (smtp->sm_nrcpts <= 0)
{
usrerr("503 5.0.0 Need RCPT (recipient)");
return true;
}
(void) sm_snprintf(buf, sizeof(buf), "%u", smtp->sm_nrcpts);
if (rscheck("check_data", buf, NULL, e,
RSF_RMCOMM|RSF_UNSTRUCTURED|RSF_COUNT, 3, NULL,
e->e_id, NULL) != EX_OK)
return true;
#if MILTER && SMFI_VERSION > 3
if (smtp->sm_milterlist && smtp->sm_milterize &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
int savelogusrerrs = LogUsrErrs;
response = milter_data_cmd(e, &state);
switch (state)
{
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
{
sm_syslog(LOG_INFO, e->e_id,
"Milter: cmd=data, reject=%s",
response);
LogUsrErrs = false;
}
#if _FFR_MILTER_ENHSC
if (ISSMTPCODE(response))
(void) extenhsc(response + 4, ' ', e->e_enhsc);
#endif
usrerr(response);
if (strncmp(response, "421 ", 4) == 0
|| strncmp(response, "421-", 4) == 0)
{
e->e_sendqueue = NULL;
return false;
}
return true;
case SMFIR_REJECT:
if (MilterLogLevel > 3)
{
sm_syslog(LOG_INFO, e->e_id,
"Milter: cmd=data, reject=550 5.7.1 Command rejected");
LogUsrErrs = false;
}
#if _FFR_MILTER_ENHSC
(void) sm_strlcpy(e->e_enhsc, "5.7.1",
sizeof(e->e_enhsc));
#endif
usrerr("550 5.7.1 Command rejected");
return true;
case SMFIR_DISCARD:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: cmd=data, discard");
e->e_flags |= EF_DISCARD;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
{
sm_syslog(LOG_INFO, e->e_id,
"Milter: cmd=data, reject=%s",
MSG_TEMPFAIL);
LogUsrErrs = false;
}
#if _FFR_MILTER_ENHSC
(void) extenhsc(MSG_TEMPFAIL + 4, ' ', e->e_enhsc);
#endif
usrerr(MSG_TEMPFAIL);
return true;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
{
sm_syslog(LOG_INFO, e->e_id,
"Milter: cmd=data, reject=421 4.7.0 %s closing connection",
MyHostName);
LogUsrErrs = false;
}
usrerr("421 4.7.0 %s closing connection", MyHostName);
e->e_sendqueue = NULL;
return false;
}
LogUsrErrs = savelogusrerrs;
if (response != NULL)
sm_free(response);
}
#endif
if (smtp->sm_discard)
e->e_flags |= EF_DISCARD;
doublequeue = false;
for (a = e->e_sendqueue; a != NULL; a = a->q_next)
{
if (QS_IS_VERIFIED(a->q_state) &&
!bitset(EF_DISCARD, e->e_flags))
{
doublequeue = true;
}
if (QS_IS_BADADDR(a->q_state))
{
a->q_state = QS_DONTSEND;
}
}
SmtpPhase = "collect";
buffer_errors();
collect(InChannel, true, NULL, e, true);
(void) sm_snprintf(buf, sizeof(buf), "%ld", e->e_msgsize);
macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
(void) rscheck("check_eom", buf, NULL, e, RSF_UNSTRUCTURED|RSF_COUNT,
3, NULL, e->e_id, NULL);
#if MILTER
milteraccept = true;
if (smtp->sm_milterlist && smtp->sm_milterize &&
Errors <= 0 &&
!bitset(EF_DISCARD, e->e_flags))
{
char state;
char *response;
response = milter_data(e, &state);
switch (state)
{
case SMFIR_REPLYCODE:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: data, reject=%s",
response);
milteraccept = false;
#if _FFR_MILTER_ENHSC
if (ISSMTPCODE(response))
(void) extenhsc(response + 4, ' ', e->e_enhsc);
#endif
usrerr(response);
if (strncmp(response, "421 ", 4) == 0
|| strncmp(response, "421-", 4) == 0)
rv = false;
break;
case SMFIR_REJECT:
milteraccept = false;
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: data, reject=554 5.7.1 Command rejected");
usrerr("554 5.7.1 Command rejected");
break;
case SMFIR_DISCARD:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: data, discard");
milteraccept = false;
e->e_flags |= EF_DISCARD;
break;
case SMFIR_TEMPFAIL:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: data, reject=%s",
MSG_TEMPFAIL);
milteraccept = false;
#if _FFR_MILTER_ENHSC
(void) extenhsc(MSG_TEMPFAIL + 4, ' ', e->e_enhsc);
#endif
usrerr(MSG_TEMPFAIL);
break;
case SMFIR_SHUTDOWN:
if (MilterLogLevel > 3)
sm_syslog(LOG_INFO, e->e_id,
"Milter: data, reject=421 4.7.0 %s closing connection",
MyHostName);
milteraccept = false;
usrerr("421 4.7.0 %s closing connection", MyHostName);
rv = false;
break;
}
if (response != NULL)
sm_free(response);
}
(void) sm_snprintf(buf, sizeof(buf), "%ld", e->e_msgsize);
macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf);
if (smtp->sm_milterlist && smtp->sm_milterize)
{
milter_abort(e);
if (milteraccept && MilterLogLevel > 9)
sm_syslog(LOG_INFO, e->e_id, "Milter accept: message");
}
if (milteraccept && SuperSafe == SAFE_REALLY_POSTMILTER)
{
int afd;
SM_FILE_T *volatile df;
char *dfname;
df = e->e_dfp;
dfname = queuename(e, DATAFL_LETTER);
if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0
&& errno != EINVAL)
{
int save_errno;
save_errno = errno;
if (save_errno == EEXIST)
{
struct stat st;
int dfd;
if (stat(dfname, &st) < 0)
st.st_size = -1;
errno = EEXIST;
syserr("@collect: bfcommit(%s): already on disk, size=%ld",
dfname, (long) st.st_size);
dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL);
if (dfd >= 0)
dumpfd(dfd, true, true);
}
errno = save_errno;
dferror(df, "bfcommit", e);
flush_errors(true);
finis(save_errno != EEXIST, true, ExitStat);
}
else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) < 0)
{
dferror(df, "sm_io_getinfo", e);
flush_errors(true);
finis(true, true, ExitStat);
}
else if (fsync(afd) < 0)
{
dferror(df, "fsync", e);
flush_errors(true);
finis(true, true, ExitStat);
}
else if (sm_io_close(df, SM_TIME_DEFAULT) < 0)
{
dferror(df, "sm_io_close", e);
flush_errors(true);
finis(true, true, ExitStat);
}
e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname,
SM_IO_RDONLY, NULL);
if (e->e_dfp == NULL)
{
syserr("@Cannot reopen %s", dfname);
finis(true, true, ExitStat);
}
}
#endif
if (e->e_quarmsg != NULL)
markstats(e, NULL, STATS_QUARANTINE);
if (bitset(EF_DISCARD, e->e_flags))
doublequeue = false;
aborting = Errors > 0;
if (!(aborting || bitset(EF_DISCARD, e->e_flags)) &&
(QueueMode == QM_QUARANTINE || e->e_quarmsg == NULL) &&
!split_by_recipient(e))
aborting = bitset(EF_FATALERRS, e->e_flags);
if (aborting)
{
ADDRESS *q;
logundelrcpts(e, e->e_message, 8, false);
for (q = e->e_sendqueue; q != NULL; q = q->q_next)
q->q_flags &= ~Q_PINGFLAGS;
flush_errors(true);
buffer_errors();
goto abortmessage;
}
buffer_errors();
#if 0
e->e_message = NULL;
#endif
SmtpPhase = "delivery";
(void) sm_io_setinfo(e->e_xfp, SM_BF_TRUNCATE, NULL);
id = e->e_id;
#if NAMED_BIND
_res.retry = TimeOuts.res_retry[RES_TO_FIRST];
_res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
#endif
for (ee = e; ee != NULL; ee = ee->e_sibling)
{
ee->e_flags &= ~EF_CLRQUEUE;
ee->e_errormode = EM_MAIL;
if (doublequeue)
{
queueup(ee, false, true);
}
else
{
int mode;
mode = SM_DEFAULT;
#if _FFR_DM_ONE
if (SM_DM_ONE == e->e_sendmode)
{
if (NotFirstDelivery)
{
mode = SM_QUEUE;
e->e_sendmode = SM_QUEUE;
}
else
{
mode = SM_FORK;
NotFirstDelivery = true;
}
}
#endif
sendall(ee, mode);
}
ee->e_to = NULL;
}
oldid = CurEnv->e_id;
CurEnv->e_id = id;
#if _FFR_MSG_ACCEPT
if (MessageAccept != NULL && *MessageAccept != '\0')
{
char msg[MAXLINE];
expand(MessageAccept, msg, sizeof(msg), e);
message("250 2.0.0 %s", msg);
}
else
#endif
message("250 2.0.0 %s Message accepted for delivery", id);
CurEnv->e_id = oldid;
if (doublequeue)
{
bool anything_to_send = false;
sm_getla();
for (ee = e; ee != NULL; ee = ee->e_sibling)
{
if (WILL_BE_QUEUED(ee->e_sendmode))
continue;
if (shouldqueue(ee->e_msgpriority, ee->e_ctime))
{
ee->e_sendmode = SM_QUEUE;
continue;
}
else if (QueueMode != QM_QUARANTINE &&
ee->e_quarmsg != NULL)
{
ee->e_sendmode = SM_QUEUE;
continue;
}
anything_to_send = true;
closexscript(ee);
if (ee->e_dfp != NULL)
{
(void) sm_io_close(ee->e_dfp, SM_TIME_DEFAULT);
ee->e_dfp = NULL;
}
unlockqueue(ee);
}
if (anything_to_send)
{
#if PIPELINING
(void) sm_io_flush(OutChannel, SM_TIME_DEFAULT);
#endif
(void) doworklist(e, true, true);
}
}
abortmessage:
if (tTd(92, 2))
sm_dprintf("abortmessage: e_id=%s, EF_LOGSENDER=%d, LogLevel=%d\n",
e->e_id, bitset(EF_LOGSENDER, e->e_flags), LogLevel);
if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags))
logsender(e, NULL);
e->e_flags &= ~EF_LOGSENDER;
smtp->sm_gotmail = false;
if (aborting || bitset(EF_DISCARD, e->e_flags))
(void) dropenvelope(e, true, false);
else
{
for (ee = e; ee != NULL; ee = ee->e_sibling)
{
if (!doublequeue &&
QueueMode != QM_QUARANTINE &&
ee->e_quarmsg != NULL)
{
(void) dropenvelope(ee, true, false);
continue;
}
if (WILL_BE_QUEUED(ee->e_sendmode))
(void) dropenvelope(ee, true, false);
}
}
CurEnv = e;
features = e->e_features;
sm_rpool_free(e->e_rpool);
newenvelope(e, e, sm_rpool_new_x(NULL));
e->e_flags = BlankEnvelope.e_flags;
e->e_features = features;
if (smtp->sm_quarmsg == NULL)
{
e->e_quarmsg = NULL;
macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), "");
}
else
{
e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, smtp->sm_quarmsg);
macdefine(&e->e_macro, A_PERM,
macid("{quarantine}"), e->e_quarmsg);
}
return rv;
}
void
logundelrcpts(e, msg, level, all)
ENVELOPE *e;
char *msg;
int level;
bool all;
{
ADDRESS *a;
if (LogLevel <= level || msg == NULL || *msg == '\0')
return;
macdefine(&e->e_macro, A_PERM, 'h', NULL);
for (a = e->e_sendqueue; a != NULL; a = a->q_next)
{
if (!QS_IS_UNDELIVERED(a->q_state) && !all)
continue;
e->e_to = a->q_paddr;
logdelivery(NULL, NULL,
#if _FFR_MILTER_ENHSC
(a->q_status == NULL && e->e_enhsc[0] != '\0')
? e->e_enhsc :
#endif
a->q_status,
msg, NULL, (time_t) 0, e);
}
e->e_to = NULL;
}
static time_t
checksmtpattack(pcounter, maxcount, waitnow, cname, e)
volatile unsigned int *pcounter;
unsigned int maxcount;
bool waitnow;
char *cname;
ENVELOPE *e;
{
if (maxcount <= 0)
return (time_t) 0;
if (++(*pcounter) >= maxcount)
{
unsigned int shift;
time_t s;
if (*pcounter == maxcount && LogLevel > 5)
{
sm_syslog(LOG_INFO, e->e_id,
"%s: possible SMTP attack: command=%.40s, count=%u",
CurSmtpClient, cname, *pcounter);
}
shift = *pcounter - maxcount;
s = 1 << shift;
if (shift > MAXSHIFT || s >= MAXTIMEOUT || s <= 0)
s = MAXTIMEOUT;
#define IS_ATTACK(s) ((MaxChildren > 0 && *pcounter >= maxcount * 2) \
? STOP_ATTACK : (time_t) s)
(void) sleep(*pcounter / maxcount);
s -= *pcounter / maxcount;
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;
}
static void
setup_smtpd_io()
{
int inchfd, outchfd, outfd;
inchfd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL);
outchfd = sm_io_getinfo(OutChannel, SM_IO_WHAT_FD, NULL);
outfd = sm_io_getinfo(smioout, SM_IO_WHAT_FD, NULL);
if (outchfd != outfd)
{
(void) dup2(outchfd, outfd);
}
if (inchfd == STDIN_FILENO && outchfd == STDOUT_FILENO &&
isatty(inchfd) && isatty(outchfd))
{
int inmode, outmode;
inmode = fcntl(inchfd, F_GETFL, 0);
if (inmode == -1)
{
if (LogLevel > 11)
sm_syslog(LOG_INFO, NOQID,
"fcntl(inchfd, F_GETFL) failed: %s",
sm_errstring(errno));
return;
}
outmode = fcntl(outchfd, F_GETFL, 0);
if (outmode == -1)
{
if (LogLevel > 11)
sm_syslog(LOG_INFO, NOQID,
"fcntl(outchfd, F_GETFL) failed: %s",
sm_errstring(errno));
return;
}
if (bitset(O_NONBLOCK, inmode) ||
bitset(O_NONBLOCK, outmode) ||
fcntl(inchfd, F_SETFL, inmode | O_NONBLOCK) == -1)
return;
outmode = fcntl(outchfd, F_GETFL, 0);
if (outmode != -1 && bitset(O_NONBLOCK, outmode))
{
sm_io_automode(OutChannel, InChannel);
if (tTd(97, 4) && LogLevel > 9)
sm_syslog(LOG_INFO, NOQID,
"set automode for I (%d)/O (%d) in SMTP server",
inchfd, outchfd);
}
(void) fcntl(inchfd, F_SETFL, inmode);
}
}
static char *
skipword(p, w)
register char *volatile p;
char *w;
{
register char *q;
char *firstp = p;
SKIP_SPACE(p);
q = p;
while (*p != '\0' && *p != ':' && !(isascii(*p) && isspace(*p)))
p++;
while (isascii(*p) && isspace(*p))
*p++ = '\0';
if (*p != ':')
{
syntax:
usrerr("501 5.5.2 Syntax error in parameters scanning \"%s\"",
shortenstring(firstp, MAXSHORTSTR));
return NULL;
}
*p++ = '\0';
SKIP_SPACE(p);
if (*p == '\0')
goto syntax;
if (sm_strcasecmp(q, w))
goto syntax;
return p;
}
void
reset_mail_esmtp_args(e)
ENVELOPE *e;
{
SevenBitInput = SevenBitInput_Saved;
e->e_bodytype = NULL;
e->e_envid = NULL;
macdefine(&e->e_macro, A_PERM, macid("{dsn_envid}"), NULL);
e->e_flags &= ~(EF_RET_PARAM|EF_NO_BODY_RETN);
macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), NULL);
#if SASL
macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), NULL);
e->e_auth_param = "";
# if _FFR_AUTH_PASSING
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{auth_author}"), NULL);
# endif
#endif
e->e_deliver_by = 0;
e->e_dlvr_flag = 0;
}
void
mail_esmtp_args(a, kp, vp, e)
ADDRESS *a;
char *kp;
char *vp;
ENVELOPE *e;
{
if (sm_strcasecmp(kp, "size") == 0)
{
if (vp == NULL)
{
usrerr("501 5.5.2 SIZE requires a value");
}
macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), vp);
errno = 0;
e->e_msgsize = strtol(vp, (char **) NULL, 10);
if (e->e_msgsize == LONG_MAX && errno == ERANGE)
{
usrerr("552 5.2.3 Message size exceeds maximum value");
}
if (e->e_msgsize < 0)
{
usrerr("552 5.2.3 Message size invalid");
}
}
else if (sm_strcasecmp(kp, "body") == 0)
{
if (vp == NULL)
{
usrerr("501 5.5.2 BODY requires a value");
}
else if (sm_strcasecmp(vp, "8bitmime") == 0)
{
SevenBitInput = false;
}
else if (sm_strcasecmp(vp, "7bit") == 0)
{
SevenBitInput = true;
}
else
{
usrerr("501 5.5.4 Unknown BODY type %s", vp);
}
e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, vp);
}
else if (sm_strcasecmp(kp, "envid") == 0)
{
if (!bitset(SRV_OFFER_DSN, e->e_features))
{
usrerr("504 5.7.0 Sorry, ENVID not supported, we do not allow DSN");
}
if (vp == NULL)
{
usrerr("501 5.5.2 ENVID requires a value");
}
if (!xtextok(vp))
{
usrerr("501 5.5.4 Syntax error in ENVID parameter value");
}
if (e->e_envid != NULL)
{
usrerr("501 5.5.0 Duplicate ENVID parameter");
}
e->e_envid = sm_rpool_strdup_x(e->e_rpool, vp);
macdefine(&e->e_macro, A_PERM,
macid("{dsn_envid}"), e->e_envid);
}
else if (sm_strcasecmp(kp, "ret") == 0)
{
if (!bitset(SRV_OFFER_DSN, e->e_features))
{
usrerr("504 5.7.0 Sorry, RET not supported, we do not allow DSN");
}
if (vp == NULL)
{
usrerr("501 5.5.2 RET requires a value");
}
if (bitset(EF_RET_PARAM, e->e_flags))
{
usrerr("501 5.5.0 Duplicate RET parameter");
}
e->e_flags |= EF_RET_PARAM;
if (sm_strcasecmp(vp, "hdrs") == 0)
e->e_flags |= EF_NO_BODY_RETN;
else if (sm_strcasecmp(vp, "full") != 0)
{
usrerr("501 5.5.2 Bad argument \"%s\" to RET", vp);
}
macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), vp);
}
#if SASL
else if (sm_strcasecmp(kp, "auth") == 0)
{
int len;
char *q;
char *auth_param;
bool saveQuickAbort = QuickAbort;
bool saveSuprErrs = SuprErrs;
bool saveExitStat = ExitStat;
if (vp == NULL)
{
usrerr("501 5.5.2 AUTH= requires a value");
}
if (e->e_auth_param != NULL)
{
usrerr("501 5.5.0 Duplicate AUTH parameter");
}
if ((q = strchr(vp, ' ')) != NULL)
len = q - vp + 1;
else
len = strlen(vp) + 1;
auth_param = xalloc(len);
(void) sm_strlcpy(auth_param, vp, len);
if (!xtextok(auth_param))
{
usrerr("501 5.5.4 Syntax error in AUTH parameter value");
}
macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"),
auth_param);
SuprErrs = true;
QuickAbort = false;
if (strcmp(auth_param, "<>") != 0 &&
(rscheck("trust_auth", auth_param, NULL, e, RSF_RMCOMM,
9, NULL, NOQID, NULL) != EX_OK || Errors > 0))
{
if (tTd(95, 8))
{
q = e->e_auth_param;
sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n",
auth_param, (q == NULL) ? "" : q);
}
e->e_auth_param = "<>";
# if _FFR_AUTH_PASSING
macdefine(&BlankEnvelope.e_macro, A_PERM,
macid("{auth_author}"), NULL);
# endif
}
else
{
if (tTd(95, 8))
sm_dprintf("auth=\"%.100s\" trusted\n", auth_param);
e->e_auth_param = sm_rpool_strdup_x(e->e_rpool,
auth_param);
}
sm_free(auth_param);
Errors = 0;
QuickAbort = saveQuickAbort;
SuprErrs = saveSuprErrs;
ExitStat = saveExitStat;
}
#endif
#define PRTCHAR(c) ((isascii(c) && isprint(c)) ? (c) : '?')
else if (sm_strcasecmp(kp, "by") == 0 && DeliverByMin >= 0)
{
char *s;
if (vp == NULL)
{
usrerr("501 5.5.2 BY= requires a value");
}
errno = 0;
e->e_deliver_by = strtol(vp, &s, 10);
if (e->e_deliver_by == LONG_MIN ||
e->e_deliver_by == LONG_MAX ||
e->e_deliver_by > 999999999l ||
e->e_deliver_by < -999999999l)
{
usrerr("501 5.5.2 BY=%s out of range", vp);
}
if (s == NULL || *s != ';')
{
usrerr("501 5.5.2 BY= missing ';'");
}
e->e_dlvr_flag = 0;
++s;
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");
}
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);
}
break;
default:
usrerr("501 5.5.2 illegal by-mode '%c'", PRTCHAR(*s));
}
++s;
SKIP_SPACE(s);
switch (tolower(*s))
{
case 't':
e->e_dlvr_flag |= DLVR_TRACE;
break;
case '\0':
break;
default:
usrerr("501 5.5.2 illegal by-trace '%c'", PRTCHAR(*s));
}
}
else
{
usrerr("555 5.5.4 %s parameter unrecognized", kp);
}
}
void
rcpt_esmtp_args(a, kp, vp, e)
ADDRESS *a;
char *kp;
char *vp;
ENVELOPE *e;
{
if (sm_strcasecmp(kp, "notify") == 0)
{
char *p;
if (!bitset(SRV_OFFER_DSN, e->e_features))
{
usrerr("504 5.7.0 Sorry, NOTIFY not supported, we do not allow DSN");
}
if (vp == NULL)
{
usrerr("501 5.5.2 NOTIFY requires a value");
}
a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY);
a->q_flags |= QHASNOTIFY;
macdefine(&e->e_macro, A_TEMP, macid("{dsn_notify}"), vp);
if (sm_strcasecmp(vp, "never") == 0)
return;
for (p = vp; p != NULL; vp = p)
{
char *s;
s = p = strchr(p, ',');
if (p != NULL)
*p++ = '\0';
if (sm_strcasecmp(vp, "success") == 0)
a->q_flags |= QPINGONSUCCESS;
else if (sm_strcasecmp(vp, "failure") == 0)
a->q_flags |= QPINGONFAILURE;
else if (sm_strcasecmp(vp, "delay") == 0)
a->q_flags |= QPINGONDELAY;
else
{
usrerr("501 5.5.4 Bad argument \"%s\" to NOTIFY",
vp);
}
if (s != NULL)
*s = ',';
}
}
else if (sm_strcasecmp(kp, "orcpt") == 0)
{
if (!bitset(SRV_OFFER_DSN, e->e_features))
{
usrerr("504 5.7.0 Sorry, ORCPT not supported, we do not allow DSN");
}
if (vp == NULL)
{
usrerr("501 5.5.2 ORCPT requires a value");
}
if (strchr(vp, ';') == NULL || !xtextok(vp))
{
usrerr("501 5.5.4 Syntax error in ORCPT parameter value");
}
if (a->q_orcpt != NULL)
{
usrerr("501 5.5.0 Duplicate ORCPT parameter");
}
a->q_orcpt = sm_rpool_strdup_x(e->e_rpool, vp);
}
else
{
usrerr("555 5.5.4 %s parameter unrecognized", kp);
}
}
#define OFFF (3 + 1 + 5 + 1)
static void
printvrfyaddr(a, last, vrfy)
register ADDRESS *a;
bool last;
bool vrfy;
{
char fmtbuf[30];
if (vrfy && a->q_mailer != NULL &&
!bitnset(M_VRFY250, a->q_mailer->m_flags))
(void) sm_strlcpy(fmtbuf, "252", sizeof(fmtbuf));
else
(void) sm_strlcpy(fmtbuf, "250", sizeof(fmtbuf));
fmtbuf[3] = last ? ' ' : '-';
(void) sm_strlcpy(&fmtbuf[4], "2.1.5 ", sizeof(fmtbuf) - 4);
if (a->q_fullname == NULL)
{
if ((a->q_mailer == NULL ||
a->q_mailer->m_addrtype == NULL ||
sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
strchr(a->q_user, '@') == NULL)
(void) sm_strlcpy(&fmtbuf[OFFF], "<%s@%s>",
sizeof(fmtbuf) - OFFF);
else
(void) sm_strlcpy(&fmtbuf[OFFF], "<%s>",
sizeof(fmtbuf) - OFFF);
message(fmtbuf, a->q_user, MyHostName);
}
else
{
if ((a->q_mailer == NULL ||
a->q_mailer->m_addrtype == NULL ||
sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) &&
strchr(a->q_user, '@') == NULL)
(void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s@%s>",
sizeof(fmtbuf) - OFFF);
else
(void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s>",
sizeof(fmtbuf) - OFFF);
message(fmtbuf, a->q_fullname, a->q_user, MyHostName);
}
}
#if SASL
static int
saslmechs(conn, mechlist)
sasl_conn_t *conn;
char **mechlist;
{
int len, num, result;
# if SASL >= 20000
result = sasl_listmech(conn, NULL,
"", " ", "", (const char **) mechlist,
(unsigned int *)&len, &num);
# else
result = sasl_listmech(conn, "user",
"", " ", "", mechlist,
(unsigned int *)&len, (unsigned int *)&num);
# endif
if (result != SASL_OK)
{
if (LogLevel > 9)
sm_syslog(LOG_WARNING, NOQID,
"AUTH error: listmech=%d, num=%d",
result, num);
num = 0;
}
if (num > 0)
{
if (LogLevel > 11)
sm_syslog(LOG_INFO, NOQID,
"AUTH: available mech=%s, allowed mech=%s",
*mechlist, AuthMechanisms);
*mechlist = intersect(AuthMechanisms, *mechlist, NULL);
}
else
{
*mechlist = NULL;
if (result == SASL_OK && LogLevel > 9)
sm_syslog(LOG_WARNING, NOQID,
"AUTH warning: no mechanisms");
}
return num;
}
# if SASL >= 20000
int
proxy_policy(conn, context, requested_user, rlen, auth_identity, alen,
def_realm, urlen, propctx)
sasl_conn_t *conn;
void *context;
const char *requested_user;
unsigned rlen;
const char *auth_identity;
unsigned alen;
const char *def_realm;
unsigned urlen;
struct propctx *propctx;
{
if (auth_identity == NULL)
return SASL_FAIL;
macdefine(&BlankEnvelope.e_macro, A_TEMP,
macid("{auth_authen}"), (char *) auth_identity);
return SASL_OK;
}
# else
int
proxy_policy(context, auth_identity, requested_user, user, errstr)
void *context;
const char *auth_identity;
const char *requested_user;
const char **user;
const char **errstr;
{
if (user == NULL || auth_identity == NULL)
return SASL_FAIL;
*user = newstr(auth_identity);
return SASL_OK;
}
# endif
#endif
#if STARTTLS
bool
initsrvtls(tls_ok)
bool tls_ok;
{
if (!tls_ok)
return false;
tls_ok_srv = inittls(&srv_ctx, TLS_Srv_Opts, Srv_SSL_Options, true,
SrvCertFile, SrvKeyFile,
CACertPath, CACertFile, DHParams);
return tls_ok_srv;
}
#endif
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
{ 'P', SRV_OFFER_PIPE },
#endif
{ 'R', SRV_VRFY_CLT },
{ 'S', SRV_OFFER_TLS },
{ 'V', SRV_VRFY_CLT },
{ 'X', SRV_OFFER_EXPN },
{ '\0', SRV_NONE }
};
static unsigned int
srvfeatures(e, clientname, features)
ENVELOPE *e;
char *clientname;
unsigned int features;
{
int r, i, j;
char **pvp, c, opt;
char pvpbuf[PSBUFSIZE];
pvp = NULL;
r = rscap("srv_features", clientname, "", e, &pvp, pvpbuf,
sizeof(pvpbuf));
if (r != EX_OK)
return features;
if (pvp == NULL || pvp[0] == NULL || (pvp[0][0] & 0377) != CANONNET)
return features;
if (pvp[1] != NULL && sm_strncasecmp(pvp[1], "temp", 4) == 0)
return SRV_TMP_FAIL;
for (i = 1; pvp[i] != NULL; i++)
{
c = pvp[i][0];
j = 0;
for (;;)
{
if ((opt = srv_feat_table[j].srvf_opt) == '\0')
{
if (LogLevel > 9)
sm_syslog(LOG_WARNING, e->e_id,
"srvfeatures: unknown feature %s",
pvp[i]);
break;
}
if (c == opt)
{
features &= ~(srv_feat_table[j].srvf_flag);
break;
}
if (c == tolower(opt))
{
features |= srv_feat_table[j].srvf_flag;
break;
}
++j;
}
}
return features;
}
#define HELPVSTR "#vers "
#define HELPVERSION 2
void
help(topic, e)
char *topic;
ENVELOPE *e;
{
register SM_FILE_T *hf;
register char *p;
int len;
bool noinfo;
bool first = true;
long sff = SFF_OPENASROOT|SFF_REGONLY;
char buf[MAXLINE];
char inp[MAXLINE];
static int foundvers = -1;
extern char Version[];
if (DontLockReadFiles)
sff |= SFF_NOLOCK;
if (!bitnset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail))
sff |= SFF_SAFEDIRPATH;
if (HelpFile == NULL ||
(hf = safefopen(HelpFile, O_RDONLY, 0444, sff)) == NULL)
{
errno = 0;
message("502 5.3.0 Sendmail %s -- HELP not implemented",
Version);
return;
}
if (topic == NULL || *topic == '\0')
{
topic = "smtp";
noinfo = false;
}
else
{
makelower(topic);
noinfo = true;
}
len = strlen(topic);
while (sm_io_fgets(hf, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
{
if (buf[0] == '#')
{
if (foundvers < 0 &&
strncmp(buf, HELPVSTR, strlen(HELPVSTR)) == 0)
{
int h;
if (sm_io_sscanf(buf + strlen(HELPVSTR), "%d",
&h) == 1)
foundvers = h;
}
continue;
}
if (strncmp(buf, topic, len) == 0)
{
if (first)
{
first = false;
if (foundvers < 2 && !noinfo)
message("214-2.0.0 This is Sendmail version %s", Version);
}
p = strpbrk(buf, " \t");
if (p == NULL)
p = buf + strlen(buf) - 1;
else
p++;
fixcrlf(p, true);
if (foundvers >= 2)
{
char *lbp;
int lbs = sizeof(buf) - (p - buf);
lbp = translate_dollars(p, p, &lbs);
expand(lbp, inp, sizeof(inp), e);
if (p != lbp)
sm_free(lbp);
p = inp;
}
message("214-2.0.0 %s", p);
noinfo = false;
}
}
if (noinfo)
message("504 5.3.0 HELP topic \"%.10s\" unknown", topic);
else
message("214 2.0.0 End of HELP info");
if (foundvers != 0 && foundvers < HELPVERSION)
{
if (LogLevel > 1)
sm_syslog(LOG_WARNING, e->e_id,
"%s too old (require version %d)",
HelpFile, HELPVERSION);
foundvers = 0;
}
(void) sm_io_close(hf, SM_TIME_DEFAULT);
}
#if SASL
static int
reset_saslconn(sasl_conn_t **conn, char *hostname,
# if SASL >= 20000
char *remoteip, char *localip,
char *auth_id, sasl_ssf_t * ext_ssf)
# else
struct sockaddr_in *saddr_r, struct sockaddr_in *saddr_l,
sasl_external_properties_t * ext_ssf)
# endif
{
int result;
sasl_dispose(conn);
# if SASL >= 20000
result = sasl_server_new("smtp", hostname, NULL, NULL, NULL,
NULL, 0, conn);
# elif SASL > 10505
result = sasl_server_new("smtp", hostname, "", NULL, 0, conn);
# else
result = sasl_server_new("smtp", hostname, NULL, NULL, 0,
conn);
# endif
if (result != SASL_OK)
return result;
# if SASL >= 20000
# if NETINET || NETINET6
if (remoteip != NULL && *remoteip != '\0')
result = sasl_setprop(*conn, SASL_IPREMOTEPORT, remoteip);
if (result != SASL_OK)
return result;
if (localip != NULL && *localip != '\0')
result = sasl_setprop(*conn, SASL_IPLOCALPORT, localip);
if (result != SASL_OK)
return result;
# endif
result = sasl_setprop(*conn, SASL_SSF_EXTERNAL, ext_ssf);
if (result != SASL_OK)
return result;
result = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, auth_id);
if (result != SASL_OK)
return result;
# else
# if NETINET
if (saddr_r != NULL)
result = sasl_setprop(*conn, SASL_IP_REMOTE, saddr_r);
if (result != SASL_OK)
return result;
if (saddr_l != NULL)
result = sasl_setprop(*conn, SASL_IP_LOCAL, saddr_l);
if (result != SASL_OK)
return result;
# endif
result = sasl_setprop(*conn, SASL_SSF_EXTERNAL, ext_ssf);
if (result != SASL_OK)
return result;
# endif
return SASL_OK;
}
#endif