diff options
Diffstat (limited to 'contrib/sendmail/src/srvrsmtp.c')
-rw-r--r-- | contrib/sendmail/src/srvrsmtp.c | 4974 |
1 files changed, 0 insertions, 4974 deletions
diff --git a/contrib/sendmail/src/srvrsmtp.c b/contrib/sendmail/src/srvrsmtp.c deleted file mode 100644 index 514a5e6..0000000 --- a/contrib/sendmail/src/srvrsmtp.c +++ /dev/null @@ -1,4974 +0,0 @@ -/* - * 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 */ |