diff options
Diffstat (limited to 'sendmail/src/srvrsmtp.c')
-rw-r--r-- | sendmail/src/srvrsmtp.c | 4974 |
1 files changed, 4974 insertions, 0 deletions
diff --git a/sendmail/src/srvrsmtp.c b/sendmail/src/srvrsmtp.c new file mode 100644 index 000000000000..514a5e6310b8 --- /dev/null +++ b/sendmail/src/srvrsmtp.c @@ -0,0 +1,4974 @@ +/* + * Copyright (c) 1998-2007 Sendmail, Inc. and its suppliers. + * All rights reserved. + * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * By using this file, you agree to the terms and conditions set + * forth in the LICENSE file which can be found at the top level of + * the sendmail distribution. + * + */ + +#include <sendmail.h> +#if MILTER +# include <libmilter/mfapi.h> +# include <libmilter/mfdef.h> +#endif /* MILTER */ + +SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.967 2007/10/01 16:22:14 ca Exp $") + +#include <sm/time.h> +#include <sm/fdset.h> + +#if SASL || STARTTLS +# include "sfsasl.h" +#endif /* SASL || STARTTLS */ +#if SASL +# define ENC64LEN(l) (((l) + 2) * 4 / 3 + 1) +static int saslmechs __P((sasl_conn_t *, char **)); +#endif /* SASL */ +#if STARTTLS +# include <sysexits.h> + +static SSL_CTX *srv_ctx = NULL; /* TLS server context */ +static SSL *srv_ssl = NULL; /* per connection context */ + +static bool tls_ok_srv = false; + +# define TLS_VERIFY_CLIENT() tls_set_verify(srv_ctx, srv_ssl, \ + bitset(SRV_VRFY_CLT, features)) +#endif /* STARTTLS */ + +#if _FFR_DM_ONE +static bool NotFirstDelivery = false; +#endif /* _FFR_DM_ONE */ + +/* server features */ +#define SRV_NONE 0x0000 /* none... */ +#define SRV_OFFER_TLS 0x0001 /* offer STARTTLS */ +#define SRV_VRFY_CLT 0x0002 /* request a cert */ +#define SRV_OFFER_AUTH 0x0004 /* offer AUTH */ +#define SRV_OFFER_ETRN 0x0008 /* offer ETRN */ +#define SRV_OFFER_VRFY 0x0010 /* offer VRFY (not yet used) */ +#define SRV_OFFER_EXPN 0x0020 /* offer EXPN */ +#define SRV_OFFER_VERB 0x0040 /* offer VERB */ +#define SRV_OFFER_DSN 0x0080 /* offer DSN */ +#if PIPELINING +# define SRV_OFFER_PIPE 0x0100 /* offer PIPELINING */ +# if _FFR_NO_PIPE +# define SRV_NO_PIPE 0x0200 /* disable PIPELINING, sleep if used */ +# endif /* _FFR_NO_PIPE */ +#endif /* PIPELINING */ +#define SRV_REQ_AUTH 0x0400 /* require AUTH */ +#define SRV_REQ_SEC 0x0800 /* require security - equiv to AuthOptions=p */ +#define SRV_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */ + +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 /* SASL >= 20000 */ +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 /* SASL >= 20000 */ +#endif /* SASL */ + +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)++ + +/* +** PARSE_ESMTP_ARGS -- parse EMSTP arguments (for MAIL, RCPT) +** +** Parameters: +** e -- the envelope +** addr_st -- address (RCPT only) +** p -- read buffer +** delimptr -- current position in read buffer +** which -- MAIL/RCPT +** args -- arguments (output) +** esmtp_args -- function to process a single ESMTP argument +** +** Returns: +** none +*/ + +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; + + /* locate the beginning of the keyword */ + SKIP_SPACE(p); + if (*p == '\0') + break; + kp = p; + + /* skip to the value portion */ + while ((isascii(*p) && isalnum(*p)) || *p == '-') + p++; + if (*p == '=') + { + equal = p; + *p++ = '\0'; + vp = p; + + /* skip to the end of the value */ + 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; +} + +/* +** 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 */ +#define CMDMAIL 1 /* mail -- designate sender */ +#define CMDRCPT 2 /* rcpt -- designate recipient */ +#define CMDDATA 3 /* data -- send message text */ +#define CMDRSET 4 /* rset -- reset state */ +#define CMDVRFY 5 /* vrfy -- verify address */ +#define CMDEXPN 6 /* expn -- expand address */ +#define CMDNOOP 7 /* noop -- do nothing */ +#define CMDQUIT 8 /* quit -- close connection and die */ +#define CMDHELO 9 /* helo -- be polite */ +#define CMDHELP 10 /* help -- give usage info */ +#define CMDEHLO 11 /* ehlo -- extended helo (RFC 1425) */ +#define CMDETRN 12 /* etrn -- flush queue */ +#if SASL +# define CMDAUTH 13 /* auth -- SASL authenticate */ +#endif /* SASL */ +#if STARTTLS +# define CMDSTLS 14 /* STARTTLS -- start TLS session */ +#endif /* STARTTLS */ +/* non-standard commands */ +#define CMDVERB 17 /* verb -- go into verbose mode */ +/* unimplemented commands from RFC 821 */ +#define CMDUNIMPL 19 /* unimplemented rfc821 commands */ +/* use this to catch and log "door handle" attempts on your system */ +#define CMDLOGBOGUS 23 /* bogus command that should be logged */ +/* debugging-only commands, only enabled if SMTPDEBUG is defined */ +#define CMDDBGQSHOW 24 /* showq -- show send queue */ +#define CMDDBGDEBUG 25 /* debug -- set debug mode */ + +/* +** Note: If you change this list, remember to update 'helpfile' +*/ + +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 /* 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 }, + + { NULL, CMDERROR } +}; + +static char *CurSmtpClient; /* who's at the other end of channel */ + +#ifndef MAXBADCOMMANDS +# define MAXBADCOMMANDS 25 /* maximum number of bad commands */ +#endif /* ! MAXBADCOMMANDS */ +#ifndef MAXHELOCOMMANDS +# define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */ +#endif /* ! MAXHELOCOMMANDS */ +#ifndef MAXVRFYCOMMANDS +# define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */ +#endif /* ! MAXVRFYCOMMANDS */ +#ifndef MAXETRNCOMMANDS +# define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */ +#endif /* ! MAXETRNCOMMANDS */ +#ifndef MAXTIMEOUT +# define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */ +#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 + ERROR _MAXSHIFT > 31 is invalid +#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 +static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp", + "@(#)$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? */ + milters_T sm_milters; + + /* e_nrcpts from envelope before recipient() call */ + unsigned int sm_e_nrcpts_orig; +#endif /* MILTER */ + char *sm_quarmsg; /* carry quarantining across messages */ +} 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); /* XXX */ \ + } + +#else /* MILTER */ +# define MILTER_ABORT(e) +#endif /* MILTER */ + +/* clear all SMTP state (for HELO/EHLO/RSET) */ +#define CLEAR_STATE(cmd) \ +do \ +{ \ + /* abort milter filters */ \ + 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 (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) \ + logsender(e, NULL); \ + 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); \ + e = newenvelope(e, CurEnv, sm_rpool_new_x(NULL)); \ + CurEnv = e; \ + e->e_features = features; \ + \ + /* put back discard bit */ \ + if (smtp.sm_discard) \ + e->e_flags |= EF_DISCARD; \ + \ + /* restore connection quarantining */ \ + 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) + +/* sleep to flatten out connection load */ +#define MIN_DELAY_LOG 15 /* wait before logging this again */ + +/* is it worth setting the process title for 1s? */ +#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; /* saved version of SevenBitInput */ + +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; /* 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 unsigned int n_noop = 0; /* count of NOOP/VERB/etc */ + volatile unsigned int n_helo = 0; /* count of HELO/EHLO */ + bool ok; + volatile bool first; + volatile bool tempfail = false; + volatile time_t wt; /* timeout after too many commands */ + volatile time_t previous; /* time after checksmtpattack() */ + volatile bool lognullconnection = true; + register char *q; + SMTP_T smtp; + char *addr; + char *greetcode = "220"; + char *hostname; /* my hostname ($j) */ + QUEUE_CHAR *new; + char *args[MAXSMTPARGS]; + char inp[MAXINPLINE]; +#if MAXINPLINE < MAXLINE + ERROR _MAXINPLINE must NOT be less than _MAXLINE: MAXINPLINE < MAXLINE +#endif /* MAXINPLINE < MAXLINE */ + char cmdbuf[MAXLINE]; +#if SASL + sasl_conn_t *conn; + volatile bool sasl_ok; + volatile unsigned int n_auth = 0; /* count of AUTH commands */ + 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 /* SASL >= 20000 */ + char *out; + const char *errstr; + sasl_external_properties_t ext_ssf; + struct sockaddr_in saddr_l; + struct sockaddr_in saddr_r; +# endif /* SASL >= 20000 */ + 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 /* SASL */ +#endif /* SASL */ + 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 /* STARTTLS */ + volatile unsigned int features; +#if PIPELINING +# if _FFR_NO_PIPE + int np_log = 0; +# endif /* _FFR_NO_PIPE */ +#endif /* PIPELINING */ + 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 /* MILTER */ +# define p_addr_st NULL +#endif /* MILTER */ + size_t inplen; + + SevenBitInput_Saved = SevenBitInput; + smtp.sm_nrcpts = 0; +#if MILTER + smtp.sm_milterize = (nullserver == NULL); + smtp.sm_milterlist = false; + addr = NULL; +#endif /* MILTER */ + + /* setup I/O fd correctly for the SMTP server */ + 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 /* SM_HEAP_CHECK */ + + /* XXX the rpool should be set when e is initialized in main() */ + 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; + + /* check_relay may have set discard bit, save for later */ + smtp.sm_discard = bitset(EF_DISCARD, e->e_flags); + +#if PIPELINING + /* auto-flush output when reading input */ + (void) sm_io_autoflush(InChannel, OutChannel); +#endif /* PIPELINING */ + + sm_setproctitle(true, e, "server %s startup", CurSmtpClient); + + /* Set default features for server. */ + 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 /* SASL */ +#if PIPELINING + | SRV_OFFER_PIPE +#endif /* PIPELINING */ +#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 /* STARTTLS */ + ; + 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)) + { + /* for consistency */ + features &= ~SRV_OFFER_PIPE; + } +# endif /* _FFR_NO_PIPE */ +#endif /* PIPELINING */ +#if SASL + if (bitset(SRV_REQ_SEC, features)) + SASLOpts |= SASL_SEC_NOPLAINTEXT; + else + SASLOpts &= ~SASL_SEC_NOPLAINTEXT; +#endif /* SASL */ + } + } + 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; + + /* SASL server new connection */ + if (sasl_ok) + { +# if SASL >= 20000 + result = sasl_server_new("smtp", AuthRealm, NULL, NULL, NULL, + NULL, 0, &conn); +# elif SASL > 10505 + /* use empty realm: only works in SASL > 1.5.5 */ + result = sasl_server_new("smtp", AuthRealm, "", NULL, 0, &conn); +# else /* SASL >= 20000 */ + /* use no realm -> realm is set to hostname by SASL lib */ + result = sasl_server_new("smtp", AuthRealm, NULL, NULL, 0, + &conn); +# endif /* SASL >= 20000 */ + 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) + { + /* + ** SASL set properties for sasl + ** set local/remote IP + ** XXX Cyrus SASL v1 only supports IPv4 + ** + ** XXX where exactly are these used/required? + ** Kerberos_v4 + */ + +# 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 /* NETINET6 */ + 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 /* NETINET || NETINET6 */ +# else /* SASL >= 20000 */ +# 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 /* NETINET */ +# endif /* SASL >= 20000 */ + + auth_type = NULL; + mechlist = NULL; + user = NULL; +# if 0 + macdefine(&BlankEnvelope.e_macro, A_PERM, + macid("{auth_author}"), NULL); +# endif /* 0 */ + + /* set properties */ + (void) memset(&ssp, '\0', sizeof(ssp)); + + /* XXX should these be options settable via .cf ? */ + /* ssp.min_ssf = 0; is default due to memset() */ + { + 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) + { + /* + ** external security strength factor; + ** currently we have none so zero + */ + +# 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 /* SASL >= 20000 */ + ext_ssf.ssf = 0; + ext_ssf.auth_id = NULL; + sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL, + &ext_ssf) == SASL_OK; +# endif /* SASL >= 20000 */ + } + if (sasl_ok) + n_mechs = saslmechs(conn, &mechlist); + } +#endif /* SASL */ + +#if STARTTLS +#endif /* STARTTLS */ + +#if MILTER + if (smtp.sm_milterize) + { + char state; + + /* initialize mail filter connection */ + 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); + + /* arrange to ignore send list */ + e->e_sendqueue = NULL; + 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: /* REPLYCODE shouldn't happen */ + 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); + + /* arrange to ignore send list */ + e->e_sendqueue = NULL; + goto doquit; + } + if (response != NULL) + sm_free(response); /* XXX */ + } +#endif /* MILTER */ + + /* + ** Broken proxies and SMTP slammers + ** push data without waiting, catch them + */ + + if ( +#if STARTTLS + !smtps && +#endif /* STARTTLS */ + *greetcode == '2' && nullserver == NULL) + { + time_t msecs = 0; + char **pvp; + char pvpbuf[PSBUFSIZE]; + + /* Ask the rulesets how long to pause */ + 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; /* {begin,end,total}pause */ + int eoftest; + + /* pause for a moment */ + timeout.tv_sec = msecs / 1000; + timeout.tv_usec = (msecs % 1000) * 1000; + + /* Obey RFC 2821: 4.3.5.2: 220 timeout of 5 minutes */ + if (timeout.tv_sec >= 300) + { + timeout.tv_sec = 300; + timeout.tv_usec = 0; + } + + /* check if data is on the socket during the pause */ + 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 this an smtps connection, start TLS now */ + if (smtps) + { + Errors = 0; + goto starttls; + } + + greeting: + +#endif /* STARTTLS */ + + /* output the first line, inserting "ESMTP" as second word */ + 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); + + /* output remaining lines */ + 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 quarantining by a connect/ehlo action, save between messages */ + if (e->e_quarmsg == NULL) + smtp.sm_quarmsg = NULL; + else + smtp.sm_quarmsg = newstr(e->e_quarmsg); + + /* sendinghost's storage must outlive the current envelope */ + 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 /* MILTER */ + + /* setup for the read */ + e->e_to = NULL; + Errors = 0; + FileName = NULL; + (void) sm_io_flush(smioout, SM_TIME_DEFAULT); + + /* read the input line */ + SmtpPhase = "server cmd read"; + sm_setproctitle(true, e, "server %s cmd read", CurSmtpClient); + + /* handle errors */ + 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"; + /* 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", + 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 have not accepted mail (DATA), do not bounce + ** bad addresses back to sender. + */ + + if (bitset(EF_CLRQUEUE, e->e_flags)) + e->e_sendqueue = NULL; + goto doquit; + } + + /* also used by "proxy" check below */ + inplen = strlen(inp); +#if SASL + /* + ** SMTP AUTH requires accepting any length, + ** at least for challenge/response. However, not imposing + ** a limit is a bad idea (denial of service). + */ + + 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 /* SASL */ + + 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])) + { + /* Open proxy, drop it */ + 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; + } + + /* clean up end of line */ + fixcrlf(inp, true); + +#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 (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"); + sleep(1); + } + +# endif /* _FFR_NO_PIPE */ +#endif /* PIPELINING */ + +#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 /* 0 */ + if (*inp == '*' && *(inp + 1) == '\0') + { + authenticating = SASL_NOT_AUTH; + + /* RFC 2554 4. */ + message("501 5.0.0 AUTH aborted"); + RESET_SASLCONN; + continue; + } + + /* could this be shorter? XXX */ +# if SASL >= 20000 + in = xalloc(strlen(inp) + 1); + result = sasl_decode64(inp, strlen(inp), in, + strlen(inp), &inlen); +# else /* SASL >= 20000 */ + out = xalloc(strlen(inp)); + result = sasl_decode64(inp, strlen(inp), out, &outlen); +# endif /* SASL >= 20000 */ + if (result != SASL_OK) + { + authenticating = SASL_NOT_AUTH; + + /* RFC 2554 4. */ + message("501 5.5.4 cannot decode AUTH parameter %s", + inp); +# if SASL >= 20000 + sm_free(in); +# endif /* SASL >= 20000 */ + RESET_SASLCONN; + continue; + } + +# if SASL >= 20000 + result = sasl_server_step(conn, in, inlen, + &out, &outlen); + sm_free(in); +# else /* SASL >= 20000 */ + result = sasl_server_step(conn, out, outlen, + &out, &outlen, &errstr); +# endif /* SASL >= 20000 */ + + /* get an OK if we're done */ + 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); + + /* get security strength (features) */ + result = sasl_getprop(conn, SASL_SSF, + (const void **) &ssf); +# else /* SASL >= 20000 */ + 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 + /* get realm? */ + sasl_getprop(conn, SASL_REALM, (void **) &data); +# endif /* 0 */ + + /* get security strength (features) */ + result = sasl_getprop(conn, SASL_SSF, + (void **) &ssf); +# endif /* SASL >= 20000 */ + 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); + } + + /* + ** Only switch to encrypted connection + ** if a security layer has been negotiated + */ + + if (ssf != NULL && *ssf > 0) + { + int tmo; + + /* + ** Convert I/O layer to use SASL. + ** If the call fails, the connection + ** is aborted. + */ + + tmo = TimeOuts.to_datablock * 1000; + if (sfdcsasl(&InChannel, &OutChannel, + conn, tmo) == 0) + { + /* restart dialogue */ + n_helo = 0; +# if PIPELINING + (void) sm_io_autoflush(InChannel, + OutChannel); +# endif /* PIPELINING */ + } + else + syserr("503 5.3.3 SASL TLS failed"); + } + + /* NULL pointer ok since it's our function */ + 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) + { + /* correct code? XXX */ + /* 454 Temp. authentication failure */ + 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\"]", + result, out); + /* start over? */ + 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 /* SASL >= 20000 */ + } + else + { + /* not SASL_OK or SASL_CONT */ + message("535 5.7.0 authentication failed"); + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "AUTH failure (%s): %s (%d) %s", + auth_type, + sasl_errstring(result, NULL, + NULL), + result, +# if SASL >= 20000 + sasl_errdetail(conn)); +# else /* SASL >= 20000 */ + errstr == NULL ? "" : errstr); +# endif /* SASL >= 20000 */ + RESET_SASLCONN; + authenticating = SASL_NOT_AUTH; + } + } + else + { + /* don't want to do any of this if authenticating */ +#endif /* SASL */ + + /* echo command to transcript */ + 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); + + /* break off command */ + 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'; + + /* throw away leading whitespace */ + SKIP_SPACE(p); + + /* decode command */ + for (c = CmdTab; c->cmd_name != NULL; c++) + { + if (sm_strcasecmp(c->cmd_name, cmdbuf) == 0) + 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; + } + + 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); + + /* + ** Process command. + ** + ** If we are running as a null server, return 550 + ** to almost everything. + */ + + if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags)) + { + switch (c->cmd_code) + { + case CMDQUIT: + case CMDHELO: + case CMDEHLO: + case CMDNOOP: + case CMDRSET: + case CMDERROR: + /* process normally */ + break; + + case CMDETRN: + if (bitnset(D_ETRNONLY, d_flags) && + 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", + MyHostName); + + /* arrange to ignore send list */ + e->e_sendqueue = NULL; + goto doquit; + } +#endif /* MAXBADCOMMANDS > 0 */ + 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: /* sasl */ + 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; + + /* crude way to avoid crack attempts */ + STOP_IF_ATTACK(checksmtpattack(&n_auth, n_mechs + 1, + 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' && + 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; + } + + /* check whether mechanism is available */ + if (iteminlist(p, mechlist, " ") == NULL) + { + message("504 5.3.3 AUTH mechanism %.32s not available", + p); + break; + } + + /* + ** RFC 2554 4. + ** Unlike a zero-length client answer to a + ** 334 reply, a zero- length initial response + ** is sent as a single equals sign ("="). + */ + + if (ismore && *q == '=' && *(q + 1) == '\0') + { + /* will be free()d, don't use in=""; */ + in = xalloc(1); + *in = '\0'; + inlen = 0; + } + else if (ismore) + { + /* could this be shorter? XXX */ +# if SASL >= 20000 + in = xalloc(strlen(q) + 1); + result = sasl_decode64(q, strlen(q), in, + strlen(q), &inlen); +# else /* SASL >= 20000 */ + in = sm_rpool_malloc(e->e_rpool, strlen(q)); + result = sasl_decode64(q, strlen(q), in, + &inlen); +# endif /* SASL >= 20000 */ + 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\"]", + result, q); + /* start over? */ + authenticating = SASL_NOT_AUTH; +# if SASL >= 20000 + sm_free(in); +# endif /* SASL >= 20000 */ + in = NULL; + inlen = 0; + break; + } + } + else + { + in = NULL; + inlen = 0; + } + + /* see if that auth type exists */ +# if SASL >= 20000 + result = sasl_server_start(conn, p, in, inlen, + &out, &outlen); + if (in != NULL) + sm_free(in); +# else /* SASL >= 20000 */ + result = sasl_server_start(conn, p, in, inlen, + &out, &outlen, &errstr); +# endif /* SASL >= 20000 */ + + 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", + p, + sasl_errstring(result, NULL, + NULL), + result, +# if SASL >= 20000 + sasl_errdetail(conn)); +# else /* SASL >= 20000 */ + errstr); +# endif /* SASL >= 20000 */ + RESET_SASLCONN; + break; + } + auth_type = newstr(p); + + if (result == SASL_OK) + { + /* ugly, but same code */ + goto authenticated; + /* authenticated by the initial response */ + } + + /* len is at least 2 */ + 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); + + /* start over? */ + authenticating = SASL_NOT_AUTH; + RESET_SASLCONN; + } + else + { + message("334 %s", out2); + authenticating = SASL_PROC_AUTH; + } +# if SASL >= 20000 + sm_free(out2); +# 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; + } + 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 + /* + ** 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 + */ + + TLS_VERIFY_CLIENT(); +# endif /* TLS_VRFY_PER_CTX */ + + 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 + /* + ** this could be used if it were possible to set + ** verification per SSL (connection) + ** not just per SSL_CTX (global) + */ + + TLS_VERIFY_CLIENT(); +# endif /* !TLS_VRFY_PER_CTX */ + + 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 /* PIPELINING */ + + 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", + r, ssl_err, errno, i); + if (LogLevel > 8) + tlslogerr("server"); + } + tls_ok_srv = false; + SSL_free(srv_ssl); + srv_ssl = NULL; + + /* + ** 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, + CurSmtpClient, + &BlankEnvelope.e_macro, + bitset(SRV_VRFY_CLT, features)); + + /* + ** call Stls_client to find out whether + ** to accept the connection from the client + */ + + 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; /* don't offer STARTTLS again */ + 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 /* SASL >= 20000 */ + 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 /* SASL >= 20000 */ + mechlist = NULL; + if (sasl_ok) + n_mechs = saslmechs(conn, + &mechlist); + } + } +# endif /* SASL */ + + /* switch to secure connection */ + if (sfdctls(&InChannel, &OutChannel, srv_ssl) == 0) + { + tls_active = true; +# if PIPELINING + (void) sm_io_autoflush(InChannel, OutChannel); +# 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"); + } + tls_done: + 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"); + if (c->cmd_code == CMDEHLO) + { + protocol = "ESMTP"; + SmtpPhase = "server EHLO"; + } + else + { + protocol = "SMTP"; + SmtpPhase = "server HELO"; + } + + /* avoid denial-of-service */ + STOP_IF_ATTACK(checksmtpattack(&n_helo, MAXHELOCOMMANDS, + true, "HELO/EHLO", e)); + +#if 0 + /* RFC2821 4.1.4 allows duplicate HELO/EHLO */ + /* check for duplicate HELO/EHLO per RFC 1651 4.2 */ + if (gothello) + { + usrerr("503 %s Duplicate HELO/EHLO", + MyHostName); + 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) */ + 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'; + + /* only complain if strict check */ + ok = AllowBogusHELO; + + /* allow trailing whitespace */ + 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; + } + /* FALLTHROUGH */ + + 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); + /* arrange to ignore send list */ + e->e_sendqueue = NULL; + lognullconnection = false; + goto doquit; + } + if (response != NULL) + sm_free(response); + + /* + ** If quarantining by a connect/ehlo action, + ** save between messages + */ + + if (smtp.sm_quarmsg == NULL && + e->e_quarmsg != NULL) + smtp.sm_quarmsg = newstr(e->e_quarmsg); + } +#endif /* MILTER */ + gothello = true; + + /* print HELO response message */ + if (c->cmd_code != CMDEHLO) + { + 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 + if (bitset(SRV_OFFER_PIPE, features)) + message("250-PIPELINING"); +#endif /* PIPELINING */ + if (bitset(SRV_OFFER_EXPN, features)) + { + message("250-EXPN"); + if (bitset(SRV_OFFER_VERB, features)) + message("250-VERB"); + } +#if MIME8TO7 + message("250-8BITMIME"); +#endif /* MIME8TO7 */ + 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 /* DSN */ + if (bitset(SRV_OFFER_ETRN, features)) + message("250-ETRN"); +#if SASL + if (sasl_ok && mechlist != NULL && *mechlist != '\0') + message("250-AUTH %s", mechlist); +#endif /* SASL */ +#if STARTTLS + if (tls_ok_srv && + bitset(SRV_OFFER_TLS, features)) + 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 */ + 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 /* SASL */ + + 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; + } + + /* make sure we know who the sending host is */ + 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 /* SM_HEAP_CHECK */ + + 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 /* PICKY_HELO_CHECK */ + + 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); + + /* do the processing */ + SM_TRY + { + extern char *FullName; + + QuickAbort = true; + SM_FREE_CLR(FullName); + + /* must parse sender first */ + delimptr = NULL; + setsender(p, e, &delimptr, ' ', false); + if (delimptr != NULL && *delimptr != '\0') + *delimptr++ = '\0'; + if (Errors > 0) + sm_exc_raisenew_x(&EtypeQuickAbort, 1); + + /* Successfully set e_from, allow logging */ + e->e_flags |= EF_LOGSENDER; + + /* put resulting triple from parseaddr() into macros */ + 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); + + /* check for possible spoofing */ + 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); + } + + /* reset to default value */ + SevenBitInput = SevenBitInput_Saved; + + /* now parse ESMTP arguments */ + 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 + /* set the default AUTH= if the sender didn't */ + if (e->e_auth_param == NULL) + { + /* XXX only do this for an MSA? */ + e->e_auth_param = macvalue(macid("{auth_authen}"), + 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 */ + macdefine(&e->e_macro, A_PERM, + macid("{addr_type}"), "e s"); +#if _FFR_MAIL_MACRO + /* make the "real" sender address available */ + macdefine(&e->e_macro, A_TEMP, macid("{mail_from}"), + e->e_from.q_paddr); +#endif /* _FFR_MAIL_MACRO */ + 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); + } + + /* + ** 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. + */ + + if ((NumFileSys == 1 || NumQueue == 1) && + !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"); + 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 /* MILTER */ + if (Errors > 0) + sm_exc_raisenew_x(&EtypeQuickAbort, 1); + + message("250 2.1.0 Sender ok"); + smtp.sm_gotmail = true; + } + SM_EXCEPT(exc, "[!F]*") + { + /* + ** An error occurred while processing a MAIL command. + ** Jump to the common error handling code. + */ + + 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: /* rcpt -- designate recipient */ + 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 (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); + + /* To avoid duplicated message */ + n_badrcpts++; + } + NBADRCPTS; + + /* + ** 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"; + SM_TRY + { + QuickAbort = true; + LogUsrErrs = true; + + /* limit flooding of our machine */ + if (MaxRcptPerMsg > 0 && + smtp.sm_nrcpts >= MaxRcptPerMsg) + { + /* 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 + && (NotFirstDelivery || SM_DM_ONE != e->e_sendmode) +#endif /* _FFR_DM_ONE */ + ) + e->e_flags |= EF_VRFYONLY; + +#if MILTER + /* + ** Do not expand recipients at RCPT time (in the call + ** to recipient()) if a milter can delete or reject + ** a RCPT. 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 (!(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 /* MILTER */ + + 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'; + + /* put resulting triple from parseaddr() into macros */ + 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; + + /* now parse ESMTP arguments */ + addr = p; + parse_esmtp_args(e, a, p, delimptr, "RCPT", args, + rcpt_esmtp_args); + if (Errors > 0) + goto rcpt_done; + +#if MILTER + /* + ** rscheck() can trigger an "exception" + ** in which case the execution continues at + ** SM_EXCEPT(exc, "[!F]*") + ** This means milter_cmd_safe is not set + ** and hence milter is not invoked. + ** Would it be "safe" to change that, i.e., use + ** milter_cmd_safe = true; + ** here so a milter is informed (if requested) + ** about RCPTs that are rejected by check_rcpt? + */ +# if _FFR_MILTER_CHECK_REJECTIONS_TOO + milter_cmd_safe = true; +# endif +#endif + + /* do config file checking of the recipient */ + 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 discarding, don't bother to verify user */ + if (bitset(EF_DISCARD, e->e_flags)) + a->q_state = QS_VERIFIED; +#if MILTER + milter_cmd_safe = true; +#endif + + /* save in recipient list after ESMTP mods */ + a = recipient(a, &e->e_sendqueue, 0, e); + /* may trigger exception... */ + +#if MILTER + milter_rcpt_added = true; +#endif + + if(!(Errors > 0) && QS_IS_BADADDR(a->q_state)) + { + /* punt -- should keep message in ADDRESS.... */ + 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; + + /* how to get the error codes? */ + 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 /* MILTER */ + + /* no errors during parsing, but might be a duplicate */ + 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++; + } + + /* Is this needed? */ +#if !MILTER + rcpt_done: +#endif /* !MILTER */ + 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]*") + { + /* An exception occurred while processing RCPT */ + 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"); + + /* how to get the error codes? */ + 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 /* MILTER */ + } + SM_END_TRY + break; + + case CMDDATA: /* data -- text of mail */ + DELAY_CONN("DATA"); + if (!smtp_data(&smtp, e)) + goto doquit; + break; + + case CMDRSET: /* rset -- reset state */ + 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: /* vrfy -- verify address */ + case CMDEXPN: /* expn -- expand address */ + 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); + + /* RFC 821 doesn't allow 4xy reply code */ + 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 + { + /* do config file checking of the address */ + 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; + } + + /* see if there is more in the vrfy list */ + 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]*") + { + /* + ** An exception occurred while processing VRFY/EXPN + */ + + sm_exc_free(exc); + goto undo; + } + SM_END_TRY + break; + + case CMDETRN: /* etrn -- force queue flush */ + DELAY_CONN("ETRN"); + + /* Don't leak queue information via debug flags */ + if (!bitset(SRV_OFFER_ETRN, features) || UseMSP || + (RealUid != 0 && RealUid != TrustedUid && + OpMode == MD_SMTP)) + { + /* different message for MSA ? */ + 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; + } + + /* crude way to avoid denial-of-service attacks */ + STOP_IF_ATTACK(checksmtpattack(&n_etrn, MAXETRNCOMMANDS, + 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. + */ + + 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); /* XXX */ + QueueLimitRecipient = NULL; + if (ok && Errors == 0) + 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"); + STOP_IF_ATTACK(checksmtpattack(&n_noop, MaxNOOPCommands, + true, "NOOP", e)); + message("250 2.0.0 OK"); + break; + + case CMDQUIT: /* quit -- leave mail */ + message("221 2.0.0 %s closing connection", MyHostName); +#if PIPELINING + (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT); +#endif /* PIPELINING */ + + if (smtp.sm_nrcpts > 0) + logundelrcpts(e, "aborted by sender", 9, false); + + /* arrange to ignore any current send list */ + e->e_sendqueue = NULL; + +#if STARTTLS + /* shutdown TLS connection */ + if (tls_active) + { + (void) endtls(srv_ssl, "server"); + tls_active = false; + } +#endif /* STARTTLS */ +#if SASL + if (authenticating == SASL_IS_AUTH) + { + sasl_dispose(&conn); + authenticating = SASL_NOT_AUTH; + /* XXX sasl_done(); this is a child */ + } +#endif /* SASL */ + +doquit: + /* avoid future 050 messages */ + disconnect(1, e); + +#if MILTER + /* close out milter filters */ + milter_quit(e); +#endif /* MILTER */ + + 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"; + + /* + ** even though this id is "bogus", it makes + ** it simpler to "grep" related events, e.g., + ** timeouts for the same connection. + */ + + 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 to handle next connection */ + return; + } + finis(true, true, ExitStat); + /* NOTREACHED */ + + /* just to avoid bogus warning from some compilers */ + exit(EX_OSERR); + + case CMDVERB: /* set verbose mode */ + DELAY_CONN("VERB"); + if (!bitset(SRV_OFFER_EXPN, features) || + !bitset(SRV_OFFER_VERB, features)) + { + /* this would give out the same info */ + 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: /* show queues */ + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, + "Send Queue="); + printaddr(smioout, e->e_sendqueue, true); + break; + + case CMDDBGDEBUG: /* set debug mode */ + tTsetup(tTdvect, sizeof(tTdvect), "0-99.1"); + 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) + sm_syslog(LOG_CRIT, e->e_id, + "\"%s\" command from %s (%.100s)", + c->cmd_name, CurSmtpClient, + anynet_ntoa(&RealHostAddr)); + /* FALLTHROUGH */ + + case CMDERROR: /* unknown command */ +#if MAXBADCOMMANDS > 0 + if (++n_badcmds > MAXBADCOMMANDS) + { + stopattack: + message("421 4.7.0 %s Too many bad commands; closing connection", + MyHostName); + + /* arrange to ignore any current send list */ + e->e_sendqueue = NULL; + goto doquit; + } +#endif /* MAXBADCOMMANDS > 0 */ + +#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) + { + /* MILTER_REPLY already gave an error */ + break; + } + } +#endif /* MILTER && SMFI_VERSION > 2 */ + + 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 /* SASL */ + } + SM_EXCEPT(exc, "[!F]*") + { + /* + ** The only possible exception is "E:mta.quickabort". + ** There is nothing to do except fall through and loop. + */ + } + SM_END_TRY + } +} +/* +** 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 +smtp_data(smtp, e) + SMTP_T *smtp; + ENVELOPE *e; +{ +#if MILTER + bool milteraccept; +#endif /* MILTER */ + 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; + } + 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; + } + 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; + } + 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); /* XXX */ + } +#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; + for (a = e->e_sendqueue; a != NULL; a = a->q_next) + { + if (QS_IS_VERIFIED(a->q_state) && + !bitset(EF_DISCARD, e->e_flags)) + { + /* 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"; + buffer_errors(); + + collect(InChannel, true, NULL, e, true); + + /* redefine message size */ + (void) sm_snprintf(buf, sizeof(buf), "%ld", e->e_msgsize); + macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf); + + /* rscheck() will set Errors or EF_DISCARD if it trips */ + (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; + usrerr(response); + 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; + 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); + } + + /* Milter may have changed message size */ + (void) sm_snprintf(buf, sizeof(buf), "%ld", e->e_msgsize); + macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf); + + /* abort message filters that didn't get the body & log msg is OK */ + 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 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? + */ + + 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); + /* NOTREACHED */ + } + else if (fsync(afd) < 0) + { + dferror(df, "fsync", e); + flush_errors(true); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + else if (sm_io_close(df, SM_TIME_DEFAULT) < 0) + { + dferror(df, "sm_io_close", e); + flush_errors(true); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + + /* Now reopen the df file */ + e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname, + SM_IO_RDONLY, NULL); + if (e->e_dfp == NULL) + { + /* we haven't acked receipt yet, so just chuck this */ + syserr("@Cannot reopen %s", dfname); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + } +#endif /* MILTER */ + + /* Check if quarantining stats should be updated */ + if (e->e_quarmsg != NULL) + markstats(e, NULL, STATS_QUARANTINE); + + /* + ** If a header/body check (header checks or milter) + ** set EF_DISCARD, don't queueup the message -- + ** that would lose the EF_DISCARD bit and deliver + ** the message. + */ + + 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; + + /* Log who the mail would have gone to */ + logundelrcpts(e, e->e_message, 8, false); + + /* + ** If something above refused the message, we still haven't + ** accepted responsibility for it. Don't send DSNs. + */ + + for (q = e->e_sendqueue; q != NULL; q = q->q_next) + q->q_flags &= ~Q_PINGFLAGS; + + flush_errors(true); + buffer_errors(); + goto abortmessage; + } + + /* from now on, we have to operate silently */ + buffer_errors(); + +#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. + */ + + e->e_message = NULL; +#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"; + (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 /* NAMED_BIND */ + + for (ee = e; ee != NULL; ee = ee->e_sibling) + { + /* make sure we actually do delivery */ + ee->e_flags &= ~EF_CLRQUEUE; + + /* from now on, operate silently */ + ee->e_errormode = EM_MAIL; + + if (doublequeue) + { + /* make sure it is in the queue */ + queueup(ee, false, true); + } + else + { + int mode; + + /* send to all recipients */ + 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 /* _FFR_DM_ONE */ + sendall(ee, mode); + } + ee->e_to = NULL; + } + + /* put back id for SMTP logging in putoutmsg() */ + oldid = CurEnv->e_id; + CurEnv->e_id = id; + + /* issue success message */ +#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 /* _FFR_MSG_ACCEPT */ + message("250 2.0.0 %s Message accepted for delivery", id); + CurEnv->e_id = oldid; + + /* if we just queued, poke it */ + 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; + + /* close all the queue files */ + 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 + /* + ** XXX if we don't do this, we get 250 twice + ** because it is also flushed in the child. + */ + + (void) sm_io_flush(OutChannel, SM_TIME_DEFAULT); +#endif /* PIPELINING */ + (void) doworklist(e, true, true); + } + } + + abortmessage: + if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) + logsender(e, NULL); + 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(). + */ + + if (aborting || bitset(EF_DISCARD, e->e_flags)) + dropenvelope(e, true, false); + else + { + for (ee = e; ee != NULL; ee = ee->e_sibling) + { + if (!doublequeue && + QueueMode != QM_QUARANTINE && + ee->e_quarmsg != NULL) + { + dropenvelope(ee, true, false); + continue; + } + if (WILL_BE_QUEUED(ee->e_sendmode)) + 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; + features = e->e_features; + newenvelope(e, e, sm_rpool_new_x(NULL)); + e->e_flags = BlankEnvelope.e_flags; + e->e_features = features; + + /* restore connection quarantining */ + 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; +} +/* +** 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 +logundelrcpts(e, msg, level, all) + ENVELOPE *e; + char *msg; + int level; + bool all; +{ + ADDRESS *a; + + if (LogLevel <= level || msg == NULL || *msg == '\0') + return; + + /* Clear $h so relay= doesn't get mislogged by logdelivery() */ + macdefine(&e->e_macro, A_PERM, 'h', NULL); + + /* Log who the mail would have gone to */ + 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, a->q_status, msg, NULL, + (time_t) 0, e); + } + e->e_to = NULL; +} +/* +** 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 +checksmtpattack(pcounter, maxcount, waitnow, cname, e) + volatile unsigned int *pcounter; + unsigned int maxcount; + bool waitnow; + char *cname; + ENVELOPE *e; +{ + if (maxcount <= 0) /* no limit */ + 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) + + /* sleep at least 1 second before returning */ + (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; +} +/* +** 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 +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) + { + /* arrange for debugging output to go to remote host */ + (void) dup2(outchfd, outfd); + } + + /* + ** if InChannel and OutChannel are stdin/stdout + ** and connected to ttys + ** and fcntl(STDIN, F_SETFL, O_NONBLOCKING) also changes STDOUT, + ** then "chain" them together. + */ + + 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)) + { + /* changing InChannel also changes OutChannel */ + 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); + } + + /* undo change of inchfd */ + (void) fcntl(inchfd, F_SETFL, inmode); + } +} +/* +** 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 */ + 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; + + /* see if the input word matches desired word */ + if (sm_strcasecmp(q, w)) + goto syntax; + + return p; +} + +/* +** RESET_MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line +** +** Parameters: +** e -- the envelope. +** +** Returns: +** none. +*/ + +void +reset_mail_esmtp_args(e) + ENVELOPE *e; +{ + /* "size": no reset */ + + /* "body" */ + SevenBitInput = SevenBitInput_Saved; + e->e_bodytype = NULL; + + /* "envid" */ + e->e_envid = NULL; + macdefine(&e->e_macro, A_PERM, macid("{dsn_envid}"), NULL); + + /* "ret" */ + e->e_flags &= ~(EF_RET_PARAM|EF_NO_BODY_RETN); + macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), NULL); + +#if SASL + /* "auth" */ + 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 /* _FFR_AUTH_PASSING */ +#endif /* SASL */ + + /* "by" */ + e->e_deliver_by = 0; + e->e_dlvr_flag = 0; +} + +/* +** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line +** +** Parameters: +** a -- address (unused, for compatibility with rcpt_esmtp_args) +** kp -- the parameter key. +** vp -- the value of that parameter. +** e -- the envelope. +** +** Returns: +** none. +*/ + +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"); + /* NOTREACHED */ + } + 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"); + /* NOTREACHED */ + } + if (e->e_msgsize < 0) + { + usrerr("552 5.2.3 Message size invalid"); + /* NOTREACHED */ + } + } + else if (sm_strcasecmp(kp, "body") == 0) + { + if (vp == NULL) + { + usrerr("501 5.5.2 BODY requires a value"); + /* NOTREACHED */ + } + 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); + /* NOTREACHED */ + } + 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"); + /* NOTREACHED */ + } + if (vp == NULL) + { + usrerr("501 5.5.2 ENVID requires a value"); + /* NOTREACHED */ + } + if (!xtextok(vp)) + { + usrerr("501 5.5.4 Syntax error in ENVID parameter value"); + /* NOTREACHED */ + } + if (e->e_envid != NULL) + { + usrerr("501 5.5.0 Duplicate ENVID parameter"); + /* NOTREACHED */ + } + 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"); + /* NOTREACHED */ + } + if (vp == NULL) + { + usrerr("501 5.5.2 RET requires a value"); + /* NOTREACHED */ + } + if (bitset(EF_RET_PARAM, e->e_flags)) + { + usrerr("501 5.5.0 Duplicate RET parameter"); + /* NOTREACHED */ + } + 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); + /* NOTREACHED */ + } + 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; /* the value of the AUTH=x */ + bool saveQuickAbort = QuickAbort; + bool saveSuprErrs = SuprErrs; + bool saveExitStat = ExitStat; + + if (vp == NULL) + { + 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 */ + } + 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"); + /* just a warning? */ + /* NOTREACHED */ + } + + /* XXX define this always or only if trusted? */ + macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), + auth_param); + + /* + ** 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; + 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); + } + + /* not trusted */ + e->e_auth_param = "<>"; +# if _FFR_AUTH_PASSING + macdefine(&BlankEnvelope.e_macro, A_PERM, + macid("{auth_author}"), NULL); +# endif /* _FFR_AUTH_PASSING */ + } + 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); /* XXX */ + + /* reset values */ + Errors = 0; + QuickAbort = saveQuickAbort; + SuprErrs = saveSuprErrs; + ExitStat = saveExitStat; + } +#endif /* SASL */ +#define PRTCHAR(c) ((isascii(c) && isprint(c)) ? (c) : '?') + + /* + ** "by" is only accepted if DeliverByMin >= 0. + ** We maybe could add this to the list of server_features. + */ + + else if (sm_strcasecmp(kp, "by") == 0 && DeliverByMin >= 0) + { + char *s; + + if (vp == NULL) + { + usrerr("501 5.5.2 BY= requires a value"); + /* NOTREACHED */ + } + 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); + /* 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: + usrerr("501 5.5.2 illegal by-mode '%c'", PRTCHAR(*s)); + /* NOTREACHED */ + } + ++s; /* XXX: spaces allowed? */ + 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)); + /* NOTREACHED */ + } + + /* XXX: check whether more characters follow? */ + } + else + { + usrerr("555 5.5.4 %s parameter unrecognized", kp); + /* 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. +** +** Returns: +** none. +*/ + +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"); + /* NOTREACHED */ + } + if (vp == NULL) + { + usrerr("501 5.5.2 NOTIFY requires a value"); + /* NOTREACHED */ + } + 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); + /* NOTREACHED */ + } + 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"); + /* NOTREACHED */ + } + if (vp == NULL) + { + usrerr("501 5.5.2 ORCPT requires a value"); + /* NOTREACHED */ + } + if (strchr(vp, ';') == NULL || !xtextok(vp)) + { + usrerr("501 5.5.4 Syntax error in ORCPT parameter value"); + /* NOTREACHED */ + } + if (a->q_orcpt != NULL) + { + usrerr("501 5.5.0 Duplicate ORCPT parameter"); + /* NOTREACHED */ + } + a->q_orcpt = sm_rpool_strdup_x(e->e_rpool, vp); + } + else + { + usrerr("555 5.5.4 %s parameter unrecognized", kp); + /* 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. +*/ +#define OFFF (3 + 1 + 5 + 1) /* offset in fmt: SMTP reply + enh. code */ + +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 +/* +** 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 +saslmechs(conn, mechlist) + sasl_conn_t *conn; + char **mechlist; +{ + int len, num, result; + + /* "user" is currently unused */ +# if SASL >= 20000 + result = sasl_listmech(conn, NULL, + "", " ", "", (const char **) mechlist, + (unsigned int *)&len, &num); +# else /* SASL >= 20000 */ + result = sasl_listmech(conn, "user", /* XXX */ + "", " ", "", mechlist, + (unsigned int *)&len, (unsigned int *)&num); +# endif /* SASL >= 20000 */ + 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; /* be paranoid... */ + if (result == SASL_OK && LogLevel > 9) + sm_syslog(LOG_WARNING, NOQID, + "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 +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 /* 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 +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 /* 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 +initsrvtls(tls_ok) + bool tls_ok; +{ + if (!tls_ok) + return false; + + /* do NOT remove assignment */ + tls_ok_srv = inittls(&srv_ctx, TLS_Srv_Opts, true, SrvCertFile, + SrvKeyFile, CACertPath, CACertFile, DHParams); + 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 */ + { 'R', SRV_VRFY_CLT }, /* same as V; not documented */ + { '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 +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; + + /* + ** General rule (see sendmail.h, d_flags): + ** lower case: required/offered, upper case: Not required/available + ** + ** Since we can change some features per daemon, we have both + ** cases here: turn on/off a feature. + */ + + 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; +} + +/* +** 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 +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) + { + /* no help */ + 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; + + /* print version if no/old vers# in file */ + 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); + + /* avoid log next time */ + foundvers = 0; + } + + (void) sm_io_close(hf, SM_TIME_DEFAULT); +} + +#if SASL +/* +** RESET_SASLCONN -- reset SASL connection data +** +** Parameters: +** conn -- SASL connection context +** hostname -- host name +** various connection data +** +** Returns: +** SASL result +*/ + +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 /* SASL >= 20000 */ + struct sockaddr_in *saddr_r, struct sockaddr_in *saddr_l, + sasl_external_properties_t * ext_ssf) +# endif /* SASL >= 20000 */ +{ + int result; + + sasl_dispose(conn); +# if SASL >= 20000 + result = sasl_server_new("smtp", hostname, NULL, NULL, NULL, + NULL, 0, conn); +# elif SASL > 10505 + /* use empty realm: only works in SASL > 1.5.5 */ + result = sasl_server_new("smtp", hostname, "", NULL, 0, conn); +# else /* SASL >= 20000 */ + /* use no realm -> realm is set to hostname by SASL lib */ + result = sasl_server_new("smtp", hostname, NULL, NULL, 0, + conn); +# endif /* SASL >= 20000 */ + 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 /* NETINET || NETINET6 */ + + 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 /* SASL >= 20000 */ +# 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 /* NETINET */ + + result = sasl_setprop(*conn, SASL_SSF_EXTERNAL, ext_ssf); + if (result != SASL_OK) + return result; +# endif /* SASL >= 20000 */ + return SASL_OK; +} +#endif /* SASL */ |