diff options
author | gshapiro <gshapiro@FreeBSD.org> | 2002-02-17 21:56:45 +0000 |
---|---|---|
committer | gshapiro <gshapiro@FreeBSD.org> | 2002-02-17 21:56:45 +0000 |
commit | 8449595fe97f4474b9b9a7e4edee1ef35dcff393 (patch) | |
tree | e7a33b132264d449a512ddf4a8685df097669c1d /contrib/sendmail/src/srvrsmtp.c | |
parent | 289b381b31415647269c7520d881017e2dcb27f1 (diff) | |
download | FreeBSD-src-8449595fe97f4474b9b9a7e4edee1ef35dcff393.zip FreeBSD-src-8449595fe97f4474b9b9a7e4edee1ef35dcff393.tar.gz |
Import sendmail 8.12.2
Diffstat (limited to 'contrib/sendmail/src/srvrsmtp.c')
-rw-r--r-- | contrib/sendmail/src/srvrsmtp.c | 4361 |
1 files changed, 2036 insertions, 2325 deletions
diff --git a/contrib/sendmail/src/srvrsmtp.c b/contrib/sendmail/src/srvrsmtp.c index 89bcb0c..ab5d4ee 100644 --- a/contrib/sendmail/src/srvrsmtp.c +++ b/contrib/sendmail/src/srvrsmtp.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers. + * Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers. * All rights reserved. * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 @@ -11,69 +11,88 @@ * */ - #include <sendmail.h> - -#ifndef lint -# if SMTP -static char id[] = "@(#)$Id: srvrsmtp.c,v 8.471.2.2.2.78 2001/06/26 18:52:21 gshapiro Exp $ (with SMTP)"; -# else /* SMTP */ -static char id[] = "@(#)$Id: srvrsmtp.c,v 8.471.2.2.2.78 2001/06/26 18:52:21 gshapiro Exp $ (without SMTP)"; -# endif /* SMTP */ -#endif /* ! lint */ - -#if SMTP -# if SASL || STARTTLS -# include "sfsasl.h" -# endif /* SASL || STARTTLS */ -# if SASL -# define ENC64LEN(l) (((l) + 2) * 4 / 3 + 1) +#if MILTER +# include <libmilter/mfdef.h> +#endif /* MILTER */ + +SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.814 2002/01/08 00:56:22 ca Exp $") + +#if SASL || STARTTLS +# include <sys/time.h> +# 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> -# include <openssl/err.h> -# include <openssl/bio.h> -# include <openssl/pem.h> -# ifndef HASURANDOMDEV -# include <openssl/rand.h> -# endif /* !HASURANDOMDEV */ - -static SSL *srv_ssl = NULL; -static SSL_CTX *srv_ctx = NULL; -# if !TLS_NO_RSA -static RSA *rsa = NULL; -# endif /* !TLS_NO_RSA */ -static bool tls_ok_srv = FALSE; -static int tls_verify_cb __P((X509_STORE_CTX *)); -# if !TLS_NO_RSA -# define RSA_KEYLENGTH 512 -# endif /* !TLS_NO_RSA */ -# endif /* STARTTLS */ - -static time_t checksmtpattack __P((volatile int *, int, bool, +#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; + +extern void tls_set_verify __P((SSL_CTX *, SSL *, bool)); +# define TLS_VERIFY_CLIENT() tls_set_verify(srv_ctx, srv_ssl, \ + bitset(SRV_VRFY_CLT, features)) +#endif /* STARTTLS */ + +/* 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_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */ + +static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int)); + +static time_t checksmtpattack __P((volatile unsigned int *, int, bool, char *, ENVELOPE *)); static void mail_esmtp_args __P((char *, char *, ENVELOPE *)); static void printvrfyaddr __P((ADDRESS *, bool, bool)); static void rcpt_esmtp_args __P((ADDRESS *, char *, char *, ENVELOPE *)); -static int runinchild __P((char *, ENVELOPE *)); static char *skipword __P((char *volatile, char *)); +static void setup_smtpd_io __P((void)); extern ENVELOPE BlankEnvelope; +#define SKIP_SPACE(s) while (isascii(*s) && isspace(*s)) \ + (s)++ + /* ** SMTP -- run the SMTP protocol. ** ** Parameters: ** nullserver -- if non-NULL, rejection message for -** all SMTP commands. +** (almost) all SMTP commands. +** d_flags -- daemon flags ** e -- the envelope. ** ** Returns: ** never. ** ** Side Effects: -** Reads commands from the input channel and processes -** them. +** 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 @@ -83,40 +102,37 @@ struct cmd }; /* 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 */ +#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 CMDONEX 16 /* onex -- sending one transaction only */ -# define CMDVERB 17 /* verb -- go into verbose mode */ -# define CMDXUSR 18 /* xusr -- initial (user) submission */ +#define CMDVERB 17 /* verb -- go into verbose mode */ /* unimplemented commands from RFC 821 */ -# define CMDUNIMPL 19 /* unimplemented rfc821 commands */ +#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 */ +#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 */ +#define CMDDBGQSHOW 24 /* showq -- show send queue */ +#define CMDDBGDEBUG 25 /* debug -- set debug mode */ /* -** Note: If you change this list, -** remember to update 'helpfile' +** Note: If you change this list, remember to update 'helpfile' */ static struct cmd CmdTab[] = @@ -134,18 +150,16 @@ static struct cmd CmdTab[] = { "ehlo", CMDEHLO }, { "etrn", CMDETRN }, { "verb", CMDVERB }, - { "onex", CMDONEX }, - { "xusr", CMDXUSR }, { "send", CMDUNIMPL }, { "saml", CMDUNIMPL }, { "soml", CMDUNIMPL }, { "turn", CMDUNIMPL }, -# if SASL +#if SASL { "auth", CMDAUTH, }, -# endif /* SASL */ -# if STARTTLS +#endif /* SASL */ +#if STARTTLS { "starttls", CMDSTLS, }, -# endif /* STARTTLS */ +#endif /* STARTTLS */ /* remaining commands are here only to trap and log attempts to use them */ { "showq", CMDDBGQSHOW }, { "debug", CMDDBGDEBUG }, @@ -154,20 +168,167 @@ static struct cmd CmdTab[] = { NULL, CMDERROR } }; -static bool OneXact = FALSE; /* one xaction only this run */ static char *CurSmtpClient; /* who's at the other end of channel */ -# define MAXBADCOMMANDS 25 /* maximum number of bad commands */ -# define MAXNOOPCOMMANDS 20 /* max "noise" commands before slowdown */ -# define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */ -# define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */ -# define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */ -# define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */ +#ifndef MAXBADCOMMANDS +# define MAXBADCOMMANDS 25 /* maximum number of bad commands */ +#endif +#ifndef MAXNOOPCOMMANDS +# define MAXNOOPCOMMANDS 20 /* max "noise" commands before slowdown */ +#endif +#ifndef MAXHELOCOMMANDS +# define MAXHELOCOMMANDS 3 /* max HELO/EHLO commands before slowdown */ +#endif +#ifndef MAXVRFYCOMMANDS +# define MAXVRFYCOMMANDS 6 /* max VRFY/EXPN commands before slowdown */ +#endif +#ifndef MAXETRNCOMMANDS +# define MAXETRNCOMMANDS 8 /* max ETRN commands before slowdown */ +#endif +#ifndef MAXTIMEOUT +# define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */ +#endif + +#if SM_HEAP_CHECK +static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp", + "@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $"); +#endif /* SM_HEAP_CHECK */ + +typedef struct +{ + bool sm_gotmail; /* mail command received */ + unsigned int sm_nrcpts; /* number of successful RCPT commands */ +#if _FFR_ADAPTIVE_EOL +WARNING: do NOT use this FFR, it is most likely broken + bool sm_crlf; /* input in CRLF form? */ +#endif /* _FFR_ADAPTIVE_EOL */ + bool sm_discard; +#if MILTER + bool sm_milterize; + bool sm_milterlist; /* any filters in the list? */ +#endif /* MILTER */ +#if _FFR_QUARANTINE + char *sm_quarmsg; /* carry quarantining across messages */ +#endif /* _FFR_QUARANTINE */ +} SMTP_T; + +static void smtp_data __P((SMTP_T *, ENVELOPE *)); + +#define MSG_TEMPFAIL "451 4.7.1 Please try again later" + +#if MILTER +# define MILTER_ABORT(e) milter_abort((e)) +# define MILTER_REPLY(str) \ + { \ + int savelogusrerrs = LogUsrErrs; \ + \ + switch (state) \ + { \ + case SMFIR_REPLYCODE: \ + if (MilterLogLevel > 3) \ + { \ + sm_syslog(LOG_INFO, e->e_id, \ + "Milter: %s=%s, reject=%s", \ + str, addr, response); \ + LogUsrErrs = false; \ + } \ + 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; \ + 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; \ + } \ + 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) \ +{ \ + /* 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; \ +} + +/* 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); \ + } -/* runinchild() returns */ -# define RIC_INCHILD 0 /* in a child process */ -# define RIC_INPARENT 1 /* still in parent process */ -# define RIC_TEMPFAIL 2 /* temporary failure occurred */ void smtp(nullserver, d_flags, e) @@ -180,7 +341,6 @@ smtp(nullserver, d_flags, e) char *cmd; auto ADDRESS *vrfyqueue; ADDRESS *a; - volatile bool gotmail; /* mail command received */ volatile bool gothello; /* helo command received */ bool vrfy; /* set if this is a vrfy command */ char *volatile protocol; /* sending protocol */ @@ -188,72 +348,92 @@ smtp(nullserver, d_flags, e) char *volatile peerhostname; /* name of SMTP peer or "localhost" */ auto char *delimptr; char *id; - volatile int nrcpts = 0; /* number of RCPT commands */ - int ric; - bool doublequeue; - volatile bool discard; - volatile int badcommands = 0; /* count of bad commands */ - volatile int nverifies = 0; /* count of VRFY/EXPN commands */ - volatile int n_etrn = 0; /* count of ETRN commands */ - volatile int n_noop = 0; /* count of NOOP/VERB/ONEX etc cmds */ - volatile int n_helo = 0; /* count of HELO/EHLO commands */ - volatile int delay = 1; /* timeout for bad commands */ + 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 tempfail = FALSE; -# if _FFR_MILTER - volatile bool milterize = (nullserver == NULL); -# endif /* _FFR_MILTER */ +#if _FFR_ADAPTIVE_EOL + volatile bool first; +#endif /* _FFR_ADAPTIVE_EOL */ + volatile bool tempfail = false; volatile time_t wt; /* timeout after too many commands */ volatile time_t previous; /* time after checksmtpattack() */ - volatile bool lognullconnection = TRUE; + volatile bool lognullconnection = true; register char *q; + SMTP_T smtp; char *addr; char *greetcode = "220"; + char *hostname; /* my hostname ($j) */ QUEUE_CHAR *new; int argno; char *args[MAXSMTPARGS]; char inp[MAXLINE]; char cmdbuf[MAXLINE]; -# if SASL +#if SASL sasl_conn_t *conn; volatile bool sasl_ok; - volatile int n_auth = 0; /* count of AUTH commands */ + volatile unsigned int n_auth = 0; /* count of AUTH commands */ bool ismore; int result; volatile int authenticating; - char *hostname; char *user; char *in, *out, *out2; const char *errstr; - int inlen, out2len; + unsigned int inlen, out2len; unsigned int outlen; char *volatile auth_type; char *mechlist; - volatile int n_mechs; - int len; + volatile unsigned int n_mechs; + unsigned int len; sasl_security_properties_t ssp; sasl_external_properties_t ext_ssf; -# if SFIO sasl_ssf_t *ssf; -# endif /* SFIO */ -# endif /* SASL */ -# if STARTTLS +#endif /* SASL */ +#if STARTTLS int r; int rfd, wfd; - volatile bool usetls = TRUE; - volatile bool tls_active = FALSE; + volatile bool tls_active = false; +# if _FFR_SMTP_SSL + volatile bool smtps = false; +# endif /* _FFR_SMTP_SSL */ bool saveQuickAbort; bool saveSuprErrs; -# endif /* STARTTLS */ - - if (fileno(OutChannel) != fileno(stdout)) + 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; + + smtp.sm_nrcpts = 0; +#if MILTER + smtp.sm_milterize = (nullserver == NULL); + smtp.sm_milterlist = false; +#endif /* MILTER */ + + /* setup I/O fd correctly for the SMTP server */ + setup_smtpd_io(); + +#if SM_HEAP_CHECK + if (sm_debug_active(&DebugLeakSmtp, 1)) { - /* arrange for debugging output to go to remote host */ - (void) dup2(fileno(OutChannel), fileno(stdout)); + 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); - (void)sm_getla(e); + sm_getla(); peerhostname = RealHostName; if (peerhostname == NULL) peerhostname = "localhost"; @@ -263,27 +443,89 @@ smtp(nullserver, d_flags, e) CurSmtpClient = CurHostName; /* check_relay may have set discard bit, save for later */ - discard = bitset(EF_DISCARD, e->e_flags); + 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) ? SRV_NONE + : SRV_OFFER_DSN) +#if SASL + | (bitnset(D_NOAUTH, d_flags) ? SRV_NONE : SRV_OFFER_AUTH) +#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."; + } +#if PIPELINING +# if _FFR_NO_PIPE + else if (bitset(SRV_NO_PIPE, features)) + { + /* for consistency */ + features &= ~SRV_OFFER_PIPE; + } +# endif /* _FFR_NO_PIPE */ +#endif /* PIPELINING */ + } - sm_setproctitle(TRUE, e, "server %s startup", CurSmtpClient); + hostname = macvalue('j', e); -# if SASL - sasl_ok = FALSE; /* SASL can't be used (yet) */ + +#if SASL + sasl_ok = bitset(SRV_OFFER_AUTH, features); n_mechs = 0; + authenticating = SASL_NOT_AUTH; /* SASL server new connection */ - hostname = macvalue('j', e); -# if SASL > 10505 - /* use empty realm: only works in SASL > 1.5.5 */ - result = sasl_server_new("smtp", hostname, "", NULL, 0, &conn); -# else /* SASL > 10505 */ - /* use no realm -> realm is set to hostname by SASL lib */ - result = sasl_server_new("smtp", hostname, NULL, NULL, 0, &conn); -# endif /* SASL > 10505 */ - if (result == SASL_OK) + if (sasl_ok) + { +# if SASL > 10505 + /* use empty realm: only works in SASL > 1.5.5 */ + result = sasl_server_new("smtp", hostname, "", NULL, 0, &conn); +# else /* SASL > 10505 */ + /* use no realm -> realm is set to hostname by SASL lib */ + result = sasl_server_new("smtp", hostname, NULL, NULL, 0, + &conn); +# endif /* SASL > 10505 */ + 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_ok = TRUE; - /* ** SASL set properties for sasl ** set local/remote IP @@ -293,8 +535,8 @@ smtp(nullserver, d_flags, e) ** Kerberos_v4 */ -# if NETINET - in = macvalue(macid("{daemon_family}", NULL), e); +#if NETINET + in = macvalue(macid("{daemon_family}"), e); if (in != NULL && strcmp(in, "inet") == 0) { SOCKADDR_LEN_T addrsize; @@ -302,135 +544,148 @@ smtp(nullserver, d_flags, e) struct sockaddr_in saddr_r; addrsize = sizeof(struct sockaddr_in); - if (getpeername(fileno(InChannel), + 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(fileno(InChannel), + 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 /* NETINET */ - authenticating = SASL_NOT_AUTH; auth_type = NULL; mechlist = NULL; user = NULL; -# if 0 - define(macid("{auth_author}", NULL), NULL, &BlankEnvelope); -# endif /* 0 */ +# if 0 + macdefine(&BlankEnvelope.e_macro, A_PERM, + macid("{auth_author}"), NULL); +# endif /* 0 */ /* set properties */ (void) memset(&ssp, '\0', sizeof ssp); -# if SFIO + /* XXX should these be options settable via .cf ? */ /* ssp.min_ssf = 0; is default due to memset() */ +# if STARTTLS +# endif /* STARTTLS */ { - ssp.max_ssf = INT_MAX; + ssp.max_ssf = MaxSLBits; ssp.maxbufsize = MAXOUTLEN; } -# endif /* SFIO */ -# if _FFR_SASL_OPTS ssp.security_flags = SASLOpts & SASL_SEC_MASK; -# endif /* _FFR_SASL_OPTS */ sasl_ok = sasl_setprop(conn, SASL_SEC_PROPS, &ssp) == SASL_OK; if (sasl_ok) { /* ** external security strength factor; - ** we have none so zero -# if STARTTLS - ** we may have to change this for STARTTLS - ** (dynamically) -# endif + ** currently we have none so zero */ + ext_ssf.ssf = 0; ext_ssf.auth_id = NULL; sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL, &ext_ssf) == SASL_OK; } if (sasl_ok) - { n_mechs = saslmechs(conn, &mechlist); - sasl_ok = n_mechs > 0; - } } - else - { - if (LogLevel > 9) - sm_syslog(LOG_WARNING, NOQID, - "SASL error: sasl_server_new failed=%d", - result); - } -# endif /* SASL */ +#endif /* SASL */ -# if STARTTLS -# if _FFR_TLS_O_T - saveQuickAbort = QuickAbort; - saveSuprErrs = SuprErrs; - SuprErrs = TRUE; - QuickAbort = FALSE; - if (rscheck("offer_tls", CurSmtpClient, "", e, TRUE, FALSE, 8, - NULL) != EX_OK || Errors > 0) - usetls = FALSE; - QuickAbort = saveQuickAbort; - SuprErrs = saveSuprErrs; -# endif /* _FFR_TLS_O_T */ -# endif /* STARTTLS */ - -# if _FFR_MILTER - if (milterize) +#if MILTER + if (smtp.sm_milterize) { char state; /* initialize mail filter connection */ - milter_init(e, &state); + smtp.sm_milterlist = milter_init(e, &state); switch (state) { case SMFIR_REJECT: + if (MilterLogLevel > 3) + sm_syslog(LOG_INFO, e->e_id, + "Milter: inititalization failed, rejecting commands"); greetcode = "554"; nullserver = "Command rejected"; - milterize = FALSE; + smtp.sm_milterize = false; break; case SMFIR_TEMPFAIL: - tempfail = TRUE; - milterize = FALSE; + if (MilterLogLevel > 3) + sm_syslog(LOG_INFO, e->e_id, + "Milter: inititalization failed, temp failing commands"); + tempfail = true; + smtp.sm_milterize = false; break; } } - if (milterize && !bitset(EF_DISCARD, e->e_flags)) + if (smtp.sm_milterlist && smtp.sm_milterize && + !bitset(EF_DISCARD, e->e_flags)) { char state; + char *response; - (void) milter_connect(peerhostname, RealHostAddr, - e, &state); + response = milter_connect(peerhostname, 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"; - milterize = FALSE; + smtp.sm_milterize = false; break; case SMFIR_TEMPFAIL: - tempfail = TRUE; - milterize = FALSE; + 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; } + if (response != NULL) + + sm_free(response); /* XXX */ } -# endif /* _FFR_MILTER */ +#endif /* MILTER */ + +#if STARTTLS +# if _FFR_SMTP_SSL + /* If this an smtps connection, start TLS now */ + smtps = bitnset(D_SMTPS, d_flags); + if (smtps) + goto starttls; + + greeting: + +# endif /* _FFR_SMTP_SSL */ +#endif /* STARTTLS */ /* output the first line, inserting "ESMTP" as second word */ - expand(SmtpGreeting, inp, sizeof inp, e); + 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'; @@ -438,10 +693,10 @@ smtp(nullserver, d_flags, e) if (id == NULL) id = &inp[strlen(inp)]; if (p == NULL) - snprintf(cmdbuf, sizeof cmdbuf, + (void) sm_snprintf(cmdbuf, sizeof cmdbuf, "%s %%.*s ESMTP%%s", greetcode); else - snprintf(cmdbuf, sizeof cmdbuf, + (void) sm_snprintf(cmdbuf, sizeof cmdbuf, "%s-%%.*s ESMTP%%s", greetcode); message(cmdbuf, (int) (id - inp), inp, id); @@ -451,70 +706,84 @@ smtp(nullserver, d_flags, e) *p++ = '\0'; if (isascii(*id) && isspace(*id)) id++; - (void) snprintf(cmdbuf, sizeof cmdbuf, "%s-%%s", greetcode); + (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, "-%s"); message(cmdbuf, id); } if (id != NULL) { if (isascii(*id) && isspace(*id)) id++; - (void) snprintf(cmdbuf, sizeof cmdbuf, "%s %%s", greetcode); + (void) sm_strlcpyn(cmdbuf, sizeof cmdbuf, 2, greetcode, " %s"); message(cmdbuf, id); } protocol = NULL; sendinghost = macvalue('s', e); - gothello = FALSE; - gotmail = FALSE; + +#if _FFR_QUARANTINE + /* 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); +#endif /* _FFR_QUARANTINE */ + + /* sendinghost's storage must outlive the current envelope */ + if (sendinghost != NULL) + sendinghost = sm_strdup_x(sendinghost); +#if _FFR_ADAPTIVE_EOL + first = true; +#endif /* _FFR_ADAPTIVE_EOL */ + gothello = false; + smtp.sm_gotmail = false; for (;;) { - /* arrange for backout */ - (void) setjmp(TopFrame); - QuickAbort = FALSE; - HoldErrs = FALSE; - SuprErrs = FALSE; - LogUsrErrs = FALSE; - OnlyOneError = TRUE; + SM_TRY + { + QuickAbort = false; + HoldErrs = false; + SuprErrs = false; + LogUsrErrs = false; + OnlyOneError = true; e->e_flags &= ~(EF_VRFYONLY|EF_GLOBALERRS); /* setup for the read */ e->e_to = NULL; Errors = 0; FileName = NULL; - (void) fflush(stdout); + (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); -# if SASL + sm_setproctitle(true, e, "server %s cmd read", CurSmtpClient); +#if SASL /* - ** SMTP AUTH requires accepting any length, - ** at least for challenge/response - ** XXX + ** XXX SMTP AUTH requires accepting any length, + ** at least for challenge/response */ -# endif /* SASL */ +#endif /* SASL */ /* handle errors */ - if (ferror(OutChannel) || + if (sm_io_error(OutChannel) || (p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand, SmtpPhase)) == NULL) { char *d; - d = macvalue(macid("{daemon_name}", NULL), e); + d = macvalue(macid("{daemon_name}"), e); if (d == NULL) d = "stdin"; /* end of file, just die */ disconnect(1, e); -# if _FFR_MILTER +#if MILTER /* close out milter filters */ milter_quit(e); -# endif /* _FFR_MILTER */ +#endif /* MILTER */ message("421 4.4.1 %s Lost input channel from %s", MyHostName, CurSmtpClient); - if (LogLevel > (gotmail ? 1 : 19)) + if (LogLevel > (smtp.sm_gotmail ? 1 : 19)) sm_syslog(LOG_NOTICE, e->e_id, "lost input channel from %.100s to %s after %s", CurSmtpClient, d, @@ -529,20 +798,64 @@ smtp(nullserver, d_flags, e) goto doquit; } +#if _FFR_ADAPTIVE_EOL + if (first) + { + char *p; + + smtp.sm_crlf = true; + p = strchr(inp, '\n'); + if (p == NULL || p <= inp || p[-1] != '\r') + { + smtp.sm_crlf = false; + if (tTd(66, 1) && LogLevel > 8) + { + /* how many bad guys are there? */ + sm_syslog(LOG_INFO, NOQID, + "%.100s did not use CRLF", + CurSmtpClient); + } + } + first = false; + } +#endif /* _FFR_ADAPTIVE_EOL */ + /* clean up end of line */ - fixcrlf(inp, TRUE); + fixcrlf(inp, true); -# if SASL +#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)) + { + 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 0 if (*inp == '\0') { authenticating = SASL_NOT_AUTH; message("501 5.5.2 missing input"); continue; } -# endif /* 0 */ +# endif /* 0 */ if (*inp == '*' && *(inp + 1) == '\0') { authenticating = SASL_NOT_AUTH; @@ -574,97 +887,95 @@ smtp(nullserver, d_flags, e) authenticated: message("235 2.0.0 OK Authenticated"); authenticating = SASL_IS_AUTH; - define(macid("{auth_type}", NULL), - newstr(auth_type), &BlankEnvelope); + macdefine(&BlankEnvelope.e_macro, A_TEMP, + macid("{auth_type}"), auth_type); result = sasl_getprop(conn, SASL_USERNAME, (void **)&user); if (result != SASL_OK) { user = ""; - define(macid("{auth_authen}", NULL), - NULL, &BlankEnvelope); + macdefine(&BlankEnvelope.e_macro, + A_PERM, + macid("{auth_authen}"), NULL); } else { - define(macid("{auth_authen}", NULL), - newstr(user), &BlankEnvelope); + macdefine(&BlankEnvelope.e_macro, + A_TEMP, + macid("{auth_authen}"), user); } -# if 0 +# if 0 /* get realm? */ sasl_getprop(conn, SASL_REALM, (void **) &data); -# endif /* 0 */ - +# endif /* 0 */ -# if SFIO /* get security strength (features) */ result = sasl_getprop(conn, SASL_SSF, (void **) &ssf); if (result != SASL_OK) { - define(macid("{auth_ssf}", NULL), - "0", &BlankEnvelope); + macdefine(&BlankEnvelope.e_macro, + A_PERM, + macid("{auth_ssf}"), "0"); ssf = NULL; } else { char pbuf[8]; - snprintf(pbuf, sizeof pbuf, "%u", *ssf); - define(macid("{auth_ssf}", NULL), - newstr(pbuf), &BlankEnvelope); + (void) sm_snprintf(pbuf, sizeof pbuf, + "%u", *ssf); + macdefine(&BlankEnvelope.e_macro, + A_TEMP, + macid("{auth_ssf}"), pbuf); if (tTd(95, 8)) - dprintf("SASL auth_ssf: %u\n", - *ssf); + sm_dprintf("AUTH auth_ssf: %u\n", + *ssf); } + /* - ** only switch to encrypted connection + ** Only switch to encrypted connection ** if a security layer has been negotiated */ + if (ssf != NULL && *ssf > 0) { /* - ** convert sfio stuff to use SASL - ** check return values - ** if the call fails, - ** fall back to unencrypted version - ** unless some cf option requires - ** encryption then the connection must - ** be aborted + ** Convert I/O layer to use SASL. + ** If the call fails, the connection + ** is aborted. */ - if (sfdcsasl(InChannel, OutChannel, - conn) == 0) + + if (sfdcsasl(&InChannel, &OutChannel, + conn) == 0) { /* restart dialogue */ - gothello = FALSE; - OneXact = TRUE; n_helo = 0; +#if PIPELINING + (void) sm_io_autoflush(InChannel, + OutChannel); +#endif /* PIPELINING */ } else syserr("503 5.3.3 SASL TLS failed"); - if (LogLevel > 9) - sm_syslog(LOG_INFO, - NOQID, - "SASL: connection from %.64s: mech=%.16s, id=%.64s, bits=%d", - CurSmtpClient, - auth_type, user, - *ssf); } -# else /* SFIO */ - if (LogLevel > 9) + + /* NULL pointer ok since it's our function */ + if (LogLevel > 8) sm_syslog(LOG_INFO, NOQID, - "SASL: connection from %.64s: mech=%.16s, id=%.64s", - CurSmtpClient, auth_type, - user); -# endif /* SFIO */ + "AUTH=server, relay=%.100s, 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, - (u_int *)&out2len); + &out2len); if (result != SASL_OK) { /* correct code? XXX */ @@ -672,7 +983,7 @@ smtp(nullserver, d_flags, e) message("454 4.5.4 Internal error: unable to encode64"); if (LogLevel > 5) sm_syslog(LOG_WARNING, e->e_id, - "SASL encode64 error [%d for \"%s\"]", + "AUTH encode64 error [%d for \"%s\"]", result, out); /* start over? */ authenticating = SASL_NOT_AUTH; @@ -681,8 +992,8 @@ smtp(nullserver, d_flags, e) { message("334 %s", out2); if (tTd(95, 2)) - dprintf("SASL continue: msg='%s' len=%d\n", - out2, out2len); + sm_dprintf("AUTH continue: msg='%s' len=%u\n", + out2, out2len); } } else @@ -691,35 +1002,27 @@ smtp(nullserver, d_flags, e) message("500 5.7.0 authentication failed"); if (LogLevel > 9) sm_syslog(LOG_WARNING, e->e_id, - "AUTH failure (%s): %s (%d)", + "AUTH failure (%s): %s (%d) %s", auth_type, sasl_errstring(result, NULL, NULL), - result); + result, + errstr == NULL ? "" : errstr); authenticating = SASL_NOT_AUTH; } } else { /* don't want to do any of this if authenticating */ -# endif /* SASL */ +#endif /* SASL */ /* echo command to transcript */ if (e->e_xfp != NULL) - fprintf(e->e_xfp, "<<< %s\n", inp); - - if (LogLevel >= 15) - sm_syslog(LOG_INFO, e->e_id, - "<-- %s", - inp); + (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, + "<<< %s\n", inp); - if (e->e_id == NULL) - sm_setproctitle(TRUE, e, "%s: %.80s", - CurSmtpClient, inp); - else - sm_setproctitle(TRUE, e, "%s %s: %.80s", - qid_printname(e), - CurSmtpClient, inp); + if (LogLevel > 14) + sm_syslog(LOG_INFO, e->e_id, "<-- %s", inp); /* break off command */ for (p = inp; isascii(*p) && isspace(*p); p++) @@ -732,24 +1035,52 @@ smtp(nullserver, d_flags, e) *cmd = '\0'; /* throw away leading whitespace */ - while (isascii(*p) && isspace(*p)) - p++; + SKIP_SPACE(p); /* decode command */ for (c = CmdTab; c->cmd_name != NULL; c++) { - if (strcasecmp(c->cmd_name, cmdbuf) == 0) + 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 everything. + ** to almost everything. */ if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags)) @@ -768,22 +1099,30 @@ smtp(nullserver, d_flags, e) if (bitnset(D_ETRNONLY, d_flags) && nullserver == NULL) break; + DELAY_CONN("ETRN"); /* FALLTHROUGH */ default: - if (++badcommands > MAXBADCOMMANDS) +#if MAXBADCOMMANDS > 0 + /* theoretically this could overflow */ + if (nullserver != NULL && + ++n_badcmds > MAXBADCOMMANDS) { - delay *= 2; - if (delay >= MAXTIMEOUT) - delay = MAXTIMEOUT; - (void) sleep(delay); + 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); + usrerr("550 5.0.0 %s", + nullserver); } else usrerr("452 4.4.5 Insufficient disk space; try again later"); @@ -791,21 +1130,12 @@ smtp(nullserver, d_flags, e) } } - /* non-null server */ - switch (c->cmd_code) - { - case CMDMAIL: - case CMDEXPN: - case CMDVRFY: - case CMDETRN: - lognullconnection = FALSE; - } - switch (c->cmd_code) { -# if SASL +#if SASL case CMDAUTH: /* sasl */ - if (!sasl_ok) + DELAY_CONN("AUTH"); + if (!sasl_ok || n_mechs <= 0) { message("503 5.3.3 AUTH not available"); break; @@ -815,7 +1145,7 @@ smtp(nullserver, d_flags, e) message("503 5.5.0 Already Authenticated"); break; } - if (gotmail) + if (smtp.sm_gotmail) { message("503 5.5.0 AUTH not permitted during a mail transaction"); break; @@ -830,13 +1160,13 @@ smtp(nullserver, d_flags, e) break; } - ismore = FALSE; + ismore = false; /* crude way to avoid crack attempts */ - (void) checksmtpattack(&n_auth, n_mechs + 1, TRUE, + (void) checksmtpattack(&n_auth, n_mechs + 1, true, "AUTH", e); - /* make sure it's a valid string */ + /* make sure mechanism (p) is a valid string */ for (q = p; *q != '\0' && isascii(*q); q++) { if (isspace(*q)) @@ -854,7 +1184,7 @@ smtp(nullserver, d_flags, e) /* check whether mechanism is available */ if (iteminlist(p, mechlist, " ") == NULL) { - message("503 5.3.3 AUTH mechanism %s not available", + message("503 5.3.3 AUTH mechanism %.32s not available", p); break; } @@ -862,16 +1192,16 @@ smtp(nullserver, d_flags, e) if (ismore) { /* could this be shorter? XXX */ - in = xalloc(strlen(q)); + in = sm_rpool_malloc(e->e_rpool, strlen(q)); result = sasl_decode64(q, strlen(q), in, - (u_int *)&inlen); + &inlen); if (result != SASL_OK) { message("501 5.5.4 cannot BASE64 decode '%s'", q); if (LogLevel > 5) sm_syslog(LOG_WARNING, e->e_id, - "SASL decode64 error [%d for \"%s\"]", + "AUTH decode64 error [%d for \"%s\"]", result, q); /* start over? */ authenticating = SASL_NOT_AUTH; @@ -879,23 +1209,6 @@ smtp(nullserver, d_flags, e) inlen = 0; break; } -# if 0 - if (tTd(95, 99)) - { - int i; - - dprintf("AUTH: more \""); - for (i = 0; i < inlen; i++) - { - if (isascii(in[i]) && - isprint(in[i])) - dprintf("%c", in[i]); - else - dprintf("_"); - } - dprintf("\"\n"); - } -# endif /* 0 */ } else { @@ -912,11 +1225,12 @@ smtp(nullserver, d_flags, e) message("500 5.7.0 authentication failed"); if (LogLevel > 9) sm_syslog(LOG_ERR, e->e_id, - "AUTH failure (%s): %s (%d)", + "AUTH failure (%s): %s (%d) %s", p, sasl_errstring(result, NULL, NULL), - result); + result, + errstr); break; } auth_type = newstr(p); @@ -932,14 +1246,14 @@ smtp(nullserver, d_flags, e) len = ENC64LEN(outlen); out2 = xalloc(len); result = sasl_encode64(out, outlen, out2, len, - (u_int *)&out2len); + &out2len); if (result != SASL_OK) { message("454 4.5.4 Temporary authentication failure"); if (LogLevel > 5) sm_syslog(LOG_WARNING, e->e_id, - "SASL encode64 error [%d for \"%s\"]", + "AUTH encode64 error [%d for \"%s\"]", result, out); /* start over? */ @@ -950,18 +1264,18 @@ smtp(nullserver, d_flags, e) message("334 %s", out2); authenticating = SASL_PROC_AUTH; } - break; -# endif /* SASL */ +#endif /* SASL */ -# if STARTTLS +#if STARTTLS case CMDSTLS: /* starttls */ + DELAY_CONN("STARTTLS"); if (*p != '\0') { message("501 5.5.2 Syntax error (no parameters allowed)"); break; } - if (!usetls) + if (!bitset(SRV_OFFER_TLS, features)) { message("503 5.5.0 TLS not available"); break; @@ -971,7 +1285,7 @@ smtp(nullserver, d_flags, e) message("454 4.3.3 TLS not available after start"); break; } - if (gotmail) + if (smtp.sm_gotmail) { message("503 5.5.0 TLS not permitted during a mail transaction"); break; @@ -985,32 +1299,51 @@ smtp(nullserver, d_flags, e) usrerr("454 4.7.1 Please try again later"); break; } +# if _FFR_SMTP_SSL + starttls: +# endif /* _FFR_SMTP_SSL */ # if TLS_NO_RSA /* ** XXX do we need a temp key ? */ # else /* TLS_NO_RSA */ - if (SSL_CTX_need_tmp_RSA(srv_ctx) && - !SSL_CTX_set_tmp_rsa(srv_ctx, - (rsa = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, - NULL, NULL))) - ) - { - message("454 4.3.3 TLS not available: error generating RSA temp key"); - if (rsa != NULL) - RSA_free(rsa); - break; - } # 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 _FFR_SMTP_SSL + goto tls_done; +# else /* _FFR_SMTP_SSL */ break; +# endif /* _FFR_SMTP_SSL */ } - rfd = fileno(InChannel); - wfd = fileno(OutChannel); + +# 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) @@ -1018,27 +1351,97 @@ smtp(nullserver, d_flags, e) message("454 4.3.3 TLS not available: error set fd"); SSL_free(srv_ssl); srv_ssl = NULL; +# if _FFR_SMTP_SSL + goto tls_done; +# else /* _FFR_SMTP_SSL */ break; +# endif /* _FFR_SMTP_SSL */ } - message("220 2.0.0 Ready to start TLS"); +# if _FFR_SMTP_SSL + if (!smtps) +# endif /* _FFR_SMTP_SSL */ + 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; + bool timedout; + time_t left; + time_t now = curtime(); + struct timeval tv; /* what to do in this case? */ i = SSL_get_error(srv_ssl, r); + + /* + ** For SSL_ERROR_WANT_{READ,WRITE}: + ** There is no SSL record available yet + ** or there is only a partial SSL record + ** removed from the network (socket) buffer + ** into the SSL buffer. The SSL_accept will + ** only succeed when a full SSL record is + ** available (assuming a "real" error + ** doesn't happen). To handle when a "real" + ** error does happen the select is set for + ** exceptions too. + ** The connection may be re-negotiated + ** during this time so both read and write + ** "want errors" need to be handled. + ** A select() exception loops back so that + ** a proper SSL error message can be gotten. + */ + + left = TimeOuts.to_starttls - (now - tlsstart); + timedout = left <= 0; + if (!timedout) + { + tv.tv_sec = left; + tv.tv_usec = 0; + } + + /* XXX what about SSL_pending() ? */ + if (!timedout && i == SSL_ERROR_WANT_READ) + { + fd_set ssl_maskr, ssl_maskx; + + FD_ZERO(&ssl_maskr); + FD_SET(rfd, &ssl_maskr); + FD_ZERO(&ssl_maskx); + FD_SET(rfd, &ssl_maskx); + if (select(rfd + 1, &ssl_maskr, NULL, + &ssl_maskx, &tv) > 0) + goto ssl_retry; + } + if (!timedout && i == SSL_ERROR_WANT_WRITE) + { + fd_set ssl_maskw, ssl_maskx; + + FD_ZERO(&ssl_maskw); + FD_SET(wfd, &ssl_maskw); + FD_ZERO(&ssl_maskx); + FD_SET(rfd, &ssl_maskx); + if (select(wfd + 1, NULL, &ssl_maskw, + &ssl_maskx, &tv) > 0) + goto ssl_retry; + } if (LogLevel > 5) { - sm_syslog(LOG_WARNING, e->e_id, - "TLS: error: accept failed=%d (%d)", - r, i); - if (LogLevel > 9) - tlslogerr(); + sm_syslog(LOG_WARNING, NOQID, + "STARTTLS=server, error: accept failed=%d, SSL_error=%d, timedout=%d", + r, i, (int) timedout); + if (LogLevel > 8) + tlslogerr("server"); } - tls_ok_srv = FALSE; + tls_ok_srv = false; SSL_free(srv_ssl); srv_ssl = NULL; @@ -1053,8 +1456,10 @@ smtp(nullserver, d_flags, e) } /* ignore return code for now, it's in {verify} */ - (void) tls_get_info(srv_ssl, &BlankEnvelope, TRUE, - CurSmtpClient, TRUE); + (void) tls_get_info(srv_ssl, true, + CurSmtpClient, + &BlankEnvelope.e_macro, + bitset(SRV_VRFY_CLT, features)); /* ** call Stls_client to find out whether @@ -1063,12 +1468,13 @@ smtp(nullserver, d_flags, e) saveQuickAbort = QuickAbort; saveSuprErrs = SuprErrs; - SuprErrs = TRUE; - QuickAbort = FALSE; + SuprErrs = true; + QuickAbort = false; if (rscheck("tls_client", - macvalue(macid("{verify}", NULL), e), - "STARTTLS", e, TRUE, TRUE, 6, NULL) != - EX_OK || Errors > 0) + macvalue(macid("{verify}"), e), + "STARTTLS", e, true, true, 5, + NULL, NOQID) != EX_OK || + Errors > 0) { extern char MsgBuf[]; @@ -1080,48 +1486,36 @@ smtp(nullserver, d_flags, e) QuickAbort = saveQuickAbort; SuprErrs = saveSuprErrs; - tls_ok_srv = FALSE; /* don't offer STARTTLS again */ - gothello = FALSE; /* discard info */ + tls_ok_srv = false; /* don't offer STARTTLS again */ n_helo = 0; - OneXact = TRUE; /* only one xaction this run */ -# if SASL +# if SASL if (sasl_ok) { char *s; - if ((s = macvalue(macid("{cipher_bits}", NULL), e)) != NULL && - (ext_ssf.ssf = atoi(s)) > 0) + s = macvalue(macid("{cipher_bits}"), e); + if (s != NULL && (ext_ssf.ssf = atoi(s)) > 0) { -# if _FFR_EXT_MECH - ext_ssf.auth_id = macvalue(macid("{cert_subject}", - NULL), + ext_ssf.auth_id = macvalue(macid("{cert_subject}"), e); -# endif /* _FFR_EXT_MECH */ sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL, &ext_ssf) == SASL_OK; - if (mechlist != NULL) - sm_free(mechlist); mechlist = NULL; if (sasl_ok) - { n_mechs = saslmechs(conn, &mechlist); - sasl_ok = n_mechs > 0; - } } } -# endif /* SASL */ +# endif /* SASL */ /* switch to secure connection */ -#if SFIO - r = sfdctls(InChannel, OutChannel, srv_ssl); -#else /* SFIO */ -# if _FFR_TLS_TOREK - r = sfdctls(&InChannel, &OutChannel, srv_ssl); -# endif /* _FFR_TLS_TOREK */ -#endif /* SFIO */ - if (r == 0) - tls_active = TRUE; + if (sfdctls(&InChannel, &OutChannel, srv_ssl) == 0) + { + tls_active = true; +# if PIPELINING + (void) sm_io_autoflush(InChannel, OutChannel); +# endif /* PIPELINING */ + } else { /* @@ -1132,14 +1526,26 @@ smtp(nullserver, d_flags, e) ** encrypted layer, but we could not... ** just "hang up"? */ + nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer"; - syserr("TLS: can't switch to encrypted layer"); + syserr("STARTTLS: can't switch to encrypted layer"); } +# if _FFR_SMTP_SSL + tls_done: + if (smtps) + { + if (tls_active) + goto greeting; + else + goto doquit; + } +# endif /* _FFR_SMTP_SSL */ break; -# endif /* STARTTLS */ +#endif /* STARTTLS */ case CMDHELO: /* hello -- introduce yourself */ case CMDEHLO: /* extended hello */ + DELAY_CONN("EHLO"); if (c->cmd_code == CMDEHLO) { protocol = "ESMTP"; @@ -1152,9 +1558,11 @@ smtp(nullserver, d_flags, e) } /* avoid denial-of-service */ - (void) checksmtpattack(&n_helo, MAXHELOCOMMANDS, TRUE, + (void) 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) { @@ -1162,6 +1570,7 @@ smtp(nullserver, d_flags, e) MyHostName); break; } +#endif /* 0 */ /* check for valid domain name (re 1123 5.2.5) */ if (*p == '\0' && !AllowBogusHELO) @@ -1200,7 +1609,7 @@ smtp(nullserver, d_flags, e) if (*q == '\0') { q = "pleased to meet you"; - sendinghost = newstr(p); + sendinghost = sm_strdup_x(p); } else if (!AllowBogusHELO) { @@ -1216,10 +1625,32 @@ smtp(nullserver, d_flags, e) q = "accepting invalid domain name"; } - gothello = TRUE; + if (gothello) + { + CLEAR_STATE(cmdbuf); -# if _FFR_MILTER - if (milterize && !bitset(EF_DISCARD, e->e_flags)) +#if _FFR_QUARANTINE + /* 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); + } +#endif /* _FFR_QUARANTINE */ + } + +#if MILTER + if (smtp.sm_milterlist && smtp.sm_milterize && + !bitset(EF_DISCARD, e->e_flags)) { char state; char *response; @@ -1228,22 +1659,48 @@ smtp(nullserver, d_flags, e) switch (state) { case SMFIR_REPLYCODE: - nullserver = response; - milterize = FALSE; + if (MilterLogLevel > 3) + sm_syslog(LOG_INFO, e->e_id, + "Milter: helo=%s, reject=%s", + p, response); + nullserver = newstr(response); + smtp.sm_milterize = false; break; case SMFIR_REJECT: + if (MilterLogLevel > 3) + sm_syslog(LOG_INFO, e->e_id, + "Milter: helo=%s, reject=Command rejected", + p); nullserver = "Command rejected"; - milterize = FALSE; + smtp.sm_milterize = false; break; case SMFIR_TEMPFAIL: - tempfail = TRUE; - milterize = FALSE; + 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; } + if (response != NULL) + sm_free(response); + +#if _FFR_QUARANTINE + /* + ** 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 /* _FFR_QUARANTINE */ } -# endif /* _FFR_MILTER */ +#endif /* MILTER */ + gothello = true; /* print HELO response message */ if (c->cmd_code != CMDEHLO) @@ -1267,48 +1724,56 @@ smtp(nullserver, d_flags, e) ** print EHLO features list ** ** Note: If you change this list, - ** remember to update 'helpfile' + ** remember to update 'helpfile' */ message("250-ENHANCEDSTATUSCODES"); - if (!bitset(PRIV_NOEXPN, PrivacyFlags)) +#if PIPELINING + if (bitset(SRV_OFFER_PIPE, features)) + message("250-PIPELINING"); +#endif /* PIPELINING */ + if (bitset(SRV_OFFER_EXPN, features)) { message("250-EXPN"); - if (!bitset(PRIV_NOVERB, PrivacyFlags)) + if (bitset(SRV_OFFER_VERB, features)) message("250-VERB"); } -# if MIME8TO7 +#if MIME8TO7 message("250-8BITMIME"); -# endif /* MIME8TO7 */ +#endif /* MIME8TO7 */ if (MaxMessageSize > 0) message("250-SIZE %ld", MaxMessageSize); else message("250-SIZE"); -# if DSN - if (SendMIMEErrors && - !bitset(PRIV_NORECEIPTS, PrivacyFlags)) +#if DSN + if (SendMIMEErrors && bitset(SRV_OFFER_DSN, features)) message("250-DSN"); -# endif /* DSN */ - message("250-ONEX"); - if (!bitset(PRIV_NOETRN, PrivacyFlags) && - !bitnset(D_NOETRN, d_flags)) +#endif /* DSN */ + if (bitset(SRV_OFFER_ETRN, features)) message("250-ETRN"); - message("250-XUSR"); - -# if SASL +#if SASL if (sasl_ok && mechlist != NULL && *mechlist != '\0') message("250-AUTH %s", mechlist); -# endif /* SASL */ -# if STARTTLS - if (tls_ok_srv && usetls) +#endif /* SASL */ +#if STARTTLS + if (tls_ok_srv && bitset(SRV_OFFER_TLS, features)) message("250-STARTTLS"); -# endif /* 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)) @@ -1316,25 +1781,19 @@ smtp(nullserver, d_flags, e) usrerr("503 5.0.0 Polite people say HELO first"); break; } - if (gotmail) + if (smtp.sm_gotmail) { usrerr("503 5.5.0 Sender already specified"); break; } - if (InChild) - { - errno = 0; - syserr("503 5.5.0 Nested MAIL command: MAIL %s", p); - finis(TRUE, ExitStat); - } -# if SASL - if (bitnset(D_AUTHREQ, d_flags) && +#if SASL + if (bitset(SRV_REQ_AUTH, features) && authenticating != SASL_IS_AUTH) { usrerr("530 5.7.0 Authentication required"); break; } -# endif /* SASL */ +#endif /* SASL */ p = skipword(p, "from"); if (p == NULL) @@ -1345,7 +1804,7 @@ smtp(nullserver, d_flags, e) sm_syslog(LOG_INFO, e->e_id, "SMTP MAIL command (%.100s) from %.100s tempfailed (due to previous checks)", p, CurSmtpClient); - usrerr("451 4.7.1 Please try again later"); + usrerr(MSG_TEMPFAIL); break; } @@ -1354,103 +1813,88 @@ smtp(nullserver, d_flags, e) sendinghost = peerhostname; - /* fork a subprocess to process this command */ - ric = runinchild("SMTP-MAIL", e); - - /* Catch a problem and stop processing */ - if (ric == RIC_TEMPFAIL && nullserver == NULL) - nullserver = "452 4.3.0 Internal software error"; - if (ric != RIC_INCHILD) - break; +#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_subproc_no_pm; + goto undo_no_pm; if (!gothello) { - auth_warning(e, - "%s didn't use HELO protocol", - CurSmtpClient); + auth_warning(e, "%s didn't use HELO protocol", + CurSmtpClient); } -# ifdef PICKY_HELO_CHECK - if (strcasecmp(sendinghost, peerhostname) != 0 && - (strcasecmp(peerhostname, "localhost") != 0 || - strcasecmp(sendinghost, MyHostName) != 0)) +#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); + CurSmtpClient, sendinghost); } -# endif /* PICKY_HELO_CHECK */ +#endif /* PICKY_HELO_CHECK */ if (protocol == NULL) protocol = "SMTP"; - define('r', protocol, e); - define('s', sendinghost, e); + macdefine(&e->e_macro, A_PERM, 'r', protocol); + macdefine(&e->e_macro, A_PERM, 's', sendinghost); if (Errors > 0) - goto undo_subproc_no_pm; - nrcpts = 0; - define(macid("{ntries}", NULL), "0", e); + 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"); e->e_flags |= EF_CLRQUEUE; - sm_setproctitle(TRUE, e, "%s %s: %.80s", + sm_setproctitle(true, e, "%s %s: %.80s", qid_printname(e), CurSmtpClient, inp); - /* child -- go do the processing */ - if (setjmp(TopFrame) > 0) - { - /* this failed -- undo work */ - undo_subproc_no_pm: - e->e_flags &= ~EF_PM_NOTIFY; - undo_subproc: - if (InChild) - { - QuickAbort = FALSE; - SuprErrs = TRUE; - e->e_flags &= ~EF_FATALERRS; - - if (LogLevel > 4 && - bitset(EF_LOGSENDER, e->e_flags)) - logsender(e, NULL); - e->e_flags &= ~EF_LOGSENDER; - - finis(TRUE, ExitStat); - } - break; - } - QuickAbort = TRUE; + /* do the processing */ + SM_TRY + { + QuickAbort = true; /* must parse sender first */ delimptr = NULL; - setsender(p, e, &delimptr, ' ', FALSE); + setsender(p, e, &delimptr, ' ', false); if (delimptr != NULL && *delimptr != '\0') *delimptr++ = '\0'; if (Errors > 0) - goto undo_subproc_no_pm; + 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) - define(macid("{mail_mailer}", NULL), - e->e_from.q_mailer->m_name, e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_mailer}"), + e->e_from.q_mailer->m_name); else - define(macid("{mail_mailer}", NULL), - NULL, e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_mailer}"), NULL); if (e->e_from.q_host != NULL) - define(macid("{mail_host}", NULL), - e->e_from.q_host, e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_host}"), + e->e_from.q_host); else - define(macid("{mail_host}", NULL), - "localhost", e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_host}"), "localhost"); if (e->e_from.q_user != NULL) - define(macid("{mail_addr}", NULL), - e->e_from.q_user, e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_addr}"), + e->e_from.q_user); else - define(macid("{mail_addr}", NULL), - NULL, e); + macdefine(&e->e_macro, A_PERM, + macid("{mail_addr}"), NULL); if (Errors > 0) - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); /* check for possible spoofing */ if (RealUid != 0 && OpMode == MD_SMTP && @@ -1476,8 +1920,7 @@ smtp(nullserver, d_flags, e) char *equal = NULL; /* locate the beginning of the keyword */ - while (isascii(*p) && isspace(*p)) - p++; + SKIP_SPACE(p); if (*p == '\0') break; kp = p; @@ -1502,7 +1945,7 @@ smtp(nullserver, d_flags, e) *p++ = '\0'; if (tTd(19, 1)) - dprintf("MAIL: got arg %s=\"%s\"\n", kp, + sm_dprintf("MAIL: got arg %s=\"%s\"\n", kp, vp == NULL ? "<null>" : vp); mail_esmtp_args(kp, vp, e); @@ -1512,17 +1955,47 @@ smtp(nullserver, d_flags, e) if (argno >= MAXSMTPARGS - 1) usrerr("501 5.5.4 Too many parameters"); if (Errors > 0) - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); } args[argno] = NULL; if (Errors > 0) - goto undo_subproc_no_pm; + 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, TRUE, TRUE, 4, NULL) != EX_OK || + NULL, e, true, true, 3, NULL, + e->e_id) != EX_OK || Errors > 0) - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); + macdefine(&e->e_macro, A_PERM, + macid("{addr_type}"), NULL); if (MaxMessageSize > 0 && (e->e_msgsize > MaxMessageSize || @@ -1530,80 +2003,97 @@ smtp(nullserver, d_flags, e) { usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)", MaxMessageSize); - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); } - if (!enoughdiskspace(e->e_msgsize, TRUE)) + /* + ** 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"); - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); } if (Errors > 0) - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); -# if _FFR_MILTER - LogUsrErrs = TRUE; - if (milterize && !bitset(EF_DISCARD, e->e_flags)) + 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); - switch (state) - { - case SMFIR_REPLYCODE: - usrerr(response); - break; - - case SMFIR_REJECT: - usrerr("550 5.7.1 Command rejected"); - break; - - case SMFIR_DISCARD: - e->e_flags |= EF_DISCARD; - break; - - case SMFIR_TEMPFAIL: - usrerr("451 4.7.1 Please try again later"); - break; - } - if (response != NULL) - sm_free(response); + MILTER_REPLY("from"); } -# endif /* _FFR_MILTER */ +#endif /* MILTER */ if (Errors > 0) - goto undo_subproc_no_pm; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); message("250 2.1.0 Sender ok"); - gotmail = TRUE; + 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 */ - if (!gotmail) + DELAY_CONN("RCPT"); + if (!smtp.sm_gotmail) { usrerr("503 5.0.0 Need MAIL before RCPT"); break; } SmtpPhase = "server RCPT"; - if (setjmp(TopFrame) > 0) - { - e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY); - break; - } - QuickAbort = TRUE; - LogUsrErrs = TRUE; + SM_TRY + { + QuickAbort = true; + LogUsrErrs = true; /* limit flooding of our machine */ - if (MaxRcptPerMsg > 0 && nrcpts >= MaxRcptPerMsg) + if (MaxRcptPerMsg > 0 && + smtp.sm_nrcpts >= MaxRcptPerMsg) { + /* sleep(1); / * slow down? */ usrerr("452 4.5.3 Too many recipients"); - break; + goto rcpt_done; } if (e->e_sendmode != SM_DELIVER) e->e_flags |= EF_VRFYONLY; -# if _FFR_MILTER +#if MILTER /* ** If the filter will be deleting recipients, ** don't expand them at RCPT time (in the call @@ -1615,24 +2105,46 @@ smtp(nullserver, d_flags, e) if (milter_can_delrcpts()) e->e_flags |= EF_VRFYONLY; -# endif /* _FFR_MILTER */ +#endif /* MILTER */ p = skipword(p, "to"); if (p == NULL) - break; -# if _FFR_ADDR_TYPE - define(macid("{addr_type}", NULL), "e r", e); -# endif /* _FFR_ADDR_TYPE */ - a = parseaddr(p, NULLADDR, RF_COPYALL, ' ', &delimptr, e); -#if _FFR_ADDR_TYPE - define(macid("{addr_type}", NULL), NULL, e); -#endif /* _FFR_ADDR_TYPE */ + 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 (BadRcptThrottle > 0 && + n_badrcpts >= BadRcptThrottle) + { + if (LogLevel > 5 && + n_badrcpts == BadRcptThrottle) + { + sm_syslog(LOG_INFO, e->e_id, + "%.100s: Possible SMTP RCPT flood, throttling.", + CurSmtpClient); + + /* To avoid duplicated message */ + n_badrcpts++; + } + + /* + ** Don't use exponential backoff for now. + ** Some servers will open more connections + ** and actually overload the receiver even + ** more. + */ + + (void) sleep(1); + } if (Errors > 0) - break; + goto rcpt_done; if (a == NULL) { usrerr("501 5.0.0 Missing recipient"); - break; + goto rcpt_done; } if (delimptr != NULL && *delimptr != '\0') @@ -1640,25 +2152,26 @@ smtp(nullserver, d_flags, e) /* put resulting triple from parseaddr() into macros */ if (a->q_mailer != NULL) - define(macid("{rcpt_mailer}", NULL), - a->q_mailer->m_name, e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_mailer}"), + a->q_mailer->m_name); else - define(macid("{rcpt_mailer}", NULL), - NULL, e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_mailer}"), NULL); if (a->q_host != NULL) - define(macid("{rcpt_host}", NULL), - a->q_host, e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_host}"), a->q_host); else - define(macid("{rcpt_host}", NULL), - "localhost", e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_host}"), "localhost"); if (a->q_user != NULL) - define(macid("{rcpt_addr}", NULL), - a->q_user, e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_addr}"), a->q_user); else - define(macid("{rcpt_addr}", NULL), - NULL, e); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_addr}"), NULL); if (Errors > 0) - break; + goto rcpt_done; /* now parse ESMTP arguments */ addr = p; @@ -1672,8 +2185,7 @@ smtp(nullserver, d_flags, e) char *equal = NULL; /* locate the beginning of the keyword */ - while (isascii(*p) && isspace(*p)) - p++; + SKIP_SPACE(p); if (*p == '\0') break; kp = p; @@ -1698,7 +2210,7 @@ smtp(nullserver, d_flags, e) *p++ = '\0'; if (tTd(19, 1)) - dprintf("RCPT: got arg %s=\"%s\"\n", kp, + sm_dprintf("RCPT: got arg %s=\"%s\"\n", kp, vp == NULL ? "<null>" : vp); rcpt_esmtp_args(a, kp, vp, e); @@ -1712,325 +2224,126 @@ smtp(nullserver, d_flags, e) } args[argno] = NULL; if (Errors > 0) - break; + goto rcpt_done; /* 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, TRUE, TRUE, 4, NULL) != EX_OK || + NULL, e, true, true, 3, NULL, + e->e_id) != EX_OK || Errors > 0) - break; + goto rcpt_done; + macdefine(&e->e_macro, A_PERM, + macid("{addr_type}"), NULL); -# if _FFR_MILTER - if (milterize && !bitset(EF_DISCARD, e->e_flags)) +#if MILTER + if (smtp.sm_milterlist && smtp.sm_milterize && + !bitset(EF_DISCARD, e->e_flags)) { char state; char *response; response = milter_envrcpt(args, e, &state); - switch (state) - { - case SMFIR_REPLYCODE: - usrerr(response); - break; - - case SMFIR_REJECT: - usrerr("550 5.7.1 Command rejected"); - break; - - case SMFIR_DISCARD: - e->e_flags |= EF_DISCARD; - break; - - case SMFIR_TEMPFAIL: - usrerr("451 4.7.1 Please try again later"); - break; - } - if (response != NULL) - sm_free(response); - } -# endif /* _FFR_MILTER */ - - define(macid("{rcpt_mailer}", NULL), NULL, e); - define(macid("{rcpt_relay}", NULL), NULL, e); - define(macid("{rcpt_addr}", NULL), NULL, e); - define(macid("{dsn_notify}", NULL), NULL, e); + MILTER_REPLY("to"); + } +#endif /* MILTER */ + + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_mailer}"), NULL); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_relay}"), NULL); + macdefine(&e->e_macro, A_PERM, + macid("{rcpt_addr}"), NULL); + macdefine(&e->e_macro, A_PERM, + macid("{dsn_notify}"), NULL); if (Errors > 0) - break; + goto rcpt_done; /* save in recipient list after ESMTP mods */ a = recipient(a, &e->e_sendqueue, 0, e); if (Errors > 0) - break; + goto rcpt_done; /* no errors during parsing, but might be a duplicate */ e->e_to = a->q_paddr; if (!QS_IS_BADADDR(a->q_state)) { - if (e->e_queuedir == NOQDIR) + if (smtp.sm_nrcpts == 0) initsys(e); message("250 2.1.5 Recipient ok%s", QS_IS_QUEUEUP(a->q_state) ? " (will queue)" : ""); - nrcpts++; + smtp.sm_nrcpts++; } else { /* punt -- should keep message in ADDRESS.... */ usrerr("550 5.1.1 Addressee unknown"); } + rcpt_done: + if (Errors > 0) + ++n_badrcpts; + } + SM_EXCEPT(exc, "[!F]*") + { + /* An exception occurred while processing RCPT */ + e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY); + ++n_badrcpts; + } + SM_END_TRY break; case CMDDATA: /* data -- text of mail */ - SmtpPhase = "server DATA"; - if (!gotmail) - { - usrerr("503 5.0.0 Need MAIL command"); - break; - } - else if (nrcpts <= 0) - { - usrerr("503 5.0.0 Need RCPT (recipient)"); - break; - } - - /* put back discard bit */ - if (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); - -# if _FFR_MILTER - if (milterize && - Errors <= 0 && - !bitset(EF_DISCARD, e->e_flags)) - { - char state; - char *response; - - response = milter_data(e, &state); - switch (state) - { - case SMFIR_REPLYCODE: - usrerr(response); - break; - - case SMFIR_REJECT: - usrerr("554 5.7.1 Command rejected"); - break; - - case SMFIR_DISCARD: - e->e_flags |= EF_DISCARD; - break; - - case SMFIR_TEMPFAIL: - usrerr("451 4.7.1 Please try again later"); - break; - } - if (response != NULL) - sm_free(response); - } - - /* abort message filters that didn't get the body */ - if (milterize) - milter_abort(e); -# endif /* _FFR_MILTER */ - - /* redefine message size */ - if ((q = macvalue(macid("{msg_size}", NULL), e)) - != NULL) - sm_free(q); - snprintf(inp, sizeof inp, "%ld", e->e_msgsize); - define(macid("{msg_size}", NULL), newstr(inp), e); - if (Errors > 0) - { - /* Log who the mail would have gone to */ - if (LogLevel > 8 && - e->e_message != NULL) - { - for (a = e->e_sendqueue; - a != NULL; - a = a->q_next) - { - if (!QS_IS_UNDELIVERED(a->q_state)) - continue; - - e->e_to = a->q_paddr; - logdelivery(NULL, NULL, - a->q_status, - e->e_message, - NULL, - (time_t) 0, e); - } - e->e_to = NULL; - } - flush_errors(TRUE); - buffer_errors(); - goto abortmessage; - } - - /* make sure we actually do delivery */ - e->e_flags &= ~EF_CLRQUEUE; - - /* from now on, we have to operate silently */ - buffer_errors(); - e->e_errormode = EM_MAIL; - - /* - ** 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) bftruncate(e->e_xfp); - id = e->e_id; - - /* - ** 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; - - if (doublequeue) - { - /* make sure it is in the queue */ - queueup(e, FALSE); - } - else - { - /* send to all recipients */ -# if NAMED_BIND - _res.retry = TimeOuts.res_retry[RES_TO_FIRST]; - _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST]; -# endif /* NAMED_BIND */ - sendall(e, SM_DEFAULT); - } - e->e_to = NULL; - - /* issue success message */ - message("250 2.0.0 %s Message accepted for delivery", id); - - /* if we just queued, poke it */ - if (doublequeue && - e->e_sendmode != SM_QUEUE && - e->e_sendmode != SM_DEFER) - { - CurrentLA = sm_getla(e); - - if (!shouldqueue(e->e_msgpriority, e->e_ctime)) - { - /* close all the queue files */ - closexscript(e); - if (e->e_dfp != NULL) - (void) bfclose(e->e_dfp); - e->e_dfp = NULL; - unlockqueue(e); - - (void) dowork(e->e_queuedir, id, - TRUE, TRUE, e); - } - } - - abortmessage: - if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) - logsender(e, NULL); - e->e_flags &= ~EF_LOGSENDER; - - /* if in a child, pop back to our parent */ - if (InChild) - finis(TRUE, ExitStat); - - /* clean up a bit */ - gotmail = FALSE; - dropenvelope(e, TRUE); - CurEnv = e = newenvelope(e, CurEnv); - e->e_flags = BlankEnvelope.e_flags; + DELAY_CONN("DATA"); + smtp_data(&smtp, e); break; case CMDRSET: /* rset -- reset state */ -# if _FFR_MILTER - /* abort milter filters */ - milter_abort(e); -# endif /* _FFR_MILTER */ - if (tTd(94, 100)) message("451 4.0.0 Test failure"); else message("250 2.0.0 Reset state"); - - /* arrange to ignore any current send list */ - 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; - - if (InChild) - finis(TRUE, ExitStat); - - /* clean up a bit */ - gotmail = FALSE; - SuprErrs = TRUE; - dropenvelope(e, TRUE); - CurEnv = e = newenvelope(e, CurEnv); + CLEAR_STATE(cmdbuf); +#if _FFR_QUARANTINE + /* 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); + } +#endif /* _FFR_QUARANTINE */ 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 %.100s tempfailed (due to previous checks)", - c->cmd_code == CMDVRFY ? "VRFY" : "EXPN", + 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(&nverifies, MAXVRFYCOMMANDS, FALSE, - c->cmd_code == CMDVRFY ? "VRFY" : "EXPN", e); + wt = checksmtpattack(&n_verifies, MAXVRFYCOMMANDS, + false, vrfy ? "VRFY" : "EXPN", e); previous = curtime(); - vrfy = c->cmd_code == CMDVRFY; if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, - PrivacyFlags)) + PrivacyFlags)) { if (vrfy) message("252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)"); @@ -2050,18 +2363,15 @@ smtp(nullserver, d_flags, e) usrerr("503 5.0.0 I demand that you introduce yourself first"); break; } - if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) - break; if (Errors > 0) - goto undo_subproc; + break; if (LogLevel > 5) - sm_syslog(LOG_INFO, e->e_id, - "%.100s: %s", + sm_syslog(LOG_INFO, e->e_id, "%.100s: %s", CurSmtpClient, shortenstring(inp, MAXSHORTSTR)); - if (setjmp(TopFrame) > 0) - goto undo_subproc; - QuickAbort = TRUE; + SM_TRY + { + QuickAbort = true; vrfyqueue = NULL; if (vrfy) e->e_flags |= EF_VRFYONLY; @@ -2075,9 +2385,10 @@ smtp(nullserver, d_flags, e) { /* do config file checking of the address */ if (rscheck(vrfy ? "check_vrfy" : "check_expn", - p, NULL, e, TRUE, FALSE, 4, NULL) - != EX_OK || Errors > 0) - goto undo_subproc; + p, NULL, e, true, false, 3, NULL, + NOQID) != EX_OK || + Errors > 0) + sm_exc_raisenew_x(&EtypeQuickAbort, 1); (void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e); } if (wt > 0) @@ -2089,7 +2400,7 @@ smtp(nullserver, d_flags, e) (void) sleep(t); } if (Errors > 0) - goto undo_subproc; + sm_exc_raisenew_x(&EtypeQuickAbort, 1); if (vrfyqueue == NULL) { usrerr("554 5.5.2 Nothing to %s", vrfy ? "VRFY" : "EXPN"); @@ -2110,13 +2421,26 @@ smtp(nullserver, d_flags, e) printvrfyaddr(vrfyqueue, a == NULL, vrfy); vrfyqueue = a; } - if (InChild) - finis(TRUE, ExitStat); + } + 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 */ - if (bitset(PRIV_NOETRN, PrivacyFlags) || - bitnset(D_NOETRN, d_flags)) + 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"); @@ -2133,7 +2457,7 @@ smtp(nullserver, d_flags, e) sm_syslog(LOG_INFO, e->e_id, "SMTP ETRN command (%.100s) from %.100s tempfailed (due to previous checks)", p, CurSmtpClient); - usrerr("451 4.7.1 Please try again later"); + usrerr(MSG_TEMPFAIL); break; } @@ -2144,80 +2468,119 @@ smtp(nullserver, d_flags, e) } /* crude way to avoid denial-of-service attacks */ - (void) checksmtpattack(&n_etrn, MAXETRNCOMMANDS, TRUE, + (void) checksmtpattack(&n_etrn, MAXETRNCOMMANDS, true, "ETRN", e); - /* do config file checking of the parameter */ - if (rscheck("check_etrn", p, NULL, e, TRUE, FALSE, 4, - NULL) != EX_OK || Errors > 0) + /* + ** 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, true, false, 3, + NULL, NOQID) != EX_OK || Errors > 0) break; if (LogLevel > 5) sm_syslog(LOG_INFO, e->e_id, - "%.100s: ETRN %s", - CurSmtpClient, + "%.100s: ETRN %s", CurSmtpClient, shortenstring(p, MAXSHORTSTR)); id = p; + if (*id == '#') + { + int wgrp; + + id++; + wgrp = name2qid(id); + if (!ISVALIDQGRP(wgrp)) + { + usrerr("459 4.5.4 Queue %s unknown", + id); + break; + } + ok = run_work_group(wgrp, true, false, + false, true); + 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 *)xalloc(sizeof(QUEUE_CHAR)); + 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); - sm_free(QueueLimitRecipient); + 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 */ - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, + DELAY_CONN("NOOP"); + (void) 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 +#if STARTTLS /* shutdown TLS connection */ if (tls_active) { (void) endtls(srv_ssl, "server"); - tls_active = FALSE; + tls_active = false; } -# endif /* STARTTLS */ -# if SASL +#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 */ +#endif /* SASL */ doquit: /* avoid future 050 messages */ disconnect(1, e); -# if _FFR_MILTER +#if MILTER /* close out milter filters */ milter_quit(e); -# endif /* _FFR_MILTER */ - - if (InChild) - ExitStat = EX_QUIT; +#endif /* MILTER */ if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) logsender(e, NULL); @@ -2227,49 +2590,48 @@ doquit: { char *d; - d = macvalue(macid("{daemon_name}", NULL), e); + d = macvalue(macid("{daemon_name}"), e); if (d == NULL) d = "stdin"; - sm_syslog(LOG_INFO, NULL, - "%.100s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s", + + /* + ** 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, + "%.100s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s", CurSmtpClient, d); } - finis(TRUE, ExitStat); +#if PROFILING + return; +#endif /* PROFILING */ + finis(true, true, ExitStat); /* NOTREACHED */ case CMDVERB: /* set verbose mode */ + DELAY_CONN("VERB"); if (bitset(PRIV_NOEXPN, PrivacyFlags) || + !bitset(SRV_OFFER_VERB, features) || bitset(PRIV_NOVERB, PrivacyFlags)) { /* this would give out the same info */ message("502 5.7.0 Verbose unavailable"); break; } - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, + (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, true, "VERB", e); Verbose = 1; set_delivery_mode(SM_DELIVER, e); message("250 2.0.0 Verbose mode"); break; - case CMDONEX: /* doing one transaction only */ - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, - "ONEX", e); - OneXact = TRUE; - message("250 2.0.0 Only one transaction"); - break; - - case CMDXUSR: /* initial (user) submission */ - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, - "XUSR", e); - define(macid("{daemon_flags}", NULL), "c u", CurEnv); - message("250 2.0.0 Initial submission"); - break; - -# if SMTPDEBUG +#if SMTPDEBUG case CMDDBGQSHOW: /* show queues */ - printf("Send Queue="); - printaddr(e->e_sendqueue, TRUE); + (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, + "Send Queue="); + printaddr(e->e_sendqueue, true); break; case CMDDBGDEBUG: /* set debug mode */ @@ -2278,11 +2640,12 @@ doquit: message("200 2.0.0 Debug set"); break; -# else /* SMTPDEBUG */ +#else /* SMTPDEBUG */ case CMDDBGQSHOW: /* show queues */ case CMDDBGDEBUG: /* set debug mode */ -# endif /* SMTPDEBUG */ +#endif /* SMTPDEBUG */ case CMDLOGBOGUS: /* bogus command */ + DELAY_CONN("Bogus"); if (LogLevel > 0) sm_syslog(LOG_CRIT, e->e_id, "\"%s\" command from %.100s (%.100s)", @@ -2291,7 +2654,8 @@ doquit: /* FALLTHROUGH */ case CMDERROR: /* unknown command */ - if (++badcommands > MAXBADCOMMANDS) +#if MAXBADCOMMANDS > 0 + if (++n_badcmds > MAXBADCOMMANDS) { message("421 4.7.0 %s Too many bad commands; closing connection", MyHostName); @@ -2300,28 +2664,439 @@ doquit: e->e_sendqueue = NULL; goto doquit; } +#endif /* MAXBADCOMMANDS > 0 */ 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 +#if SASL } -# endif /* 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: +** none. +** +** Side Effects: +** possibly sends message. +*/ + +static void +smtp_data(smtp, e) + SMTP_T *smtp; + ENVELOPE *e; +{ +#if MILTER + bool milteraccept; +#endif /* MILTER */ + bool aborting; + bool doublequeue; + ADDRESS *a; + ENVELOPE *ee; + char *id; + char buf[32]; + + SmtpPhase = "server DATA"; + if (!smtp->sm_gotmail) + { + usrerr("503 5.0.0 Need MAIL command"); + return; + } + else if (smtp->sm_nrcpts <= 0) + { + usrerr("503 5.0.0 Need RCPT (recipient)"); + return; + } + (void) sm_snprintf(buf, sizeof buf, "%u", smtp->sm_nrcpts); + if (rscheck("check_data", buf, NULL, e, + true, false, 3, NULL, e->e_id) != EX_OK) + return; + + /* 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(); + +#if _FFR_ADAPTIVE_EOL + /* triggers error in collect, disabled for now */ + if (smtp->sm_crlf) + e->e_flags |= EF_NL_NOT_EOL; +#endif /* _FFR_ADAPTIVE_EOL */ + + collect(InChannel, true, NULL, e); + + /* redefine message size */ + (void) sm_snprintf(buf, sizeof buf, "%ld", e->e_msgsize); + macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"), buf); + +#if _FFR_CHECK_EOM + /* rscheck() will set Errors or EF_DISCARD if it trips */ + (void) rscheck("check_eom", buf, NULL, e, false, + true, 3, NULL, e->e_id); +#endif /* _FFR_CHECK_EOM */ + +#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; + } + 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"); + } +#endif /* MILTER */ + +#if _FFR_QUARANTINE + /* Check if quarantining stats should be updated */ + if (e->e_quarmsg != NULL) + markstats(e, NULL, STATS_QUARANTINE); +#endif /* _FFR_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 && +#if _FFR_QUARANTINE + (QueueMode == QM_QUARANTINE || e->e_quarmsg == NULL) && +#endif /* _FFR_QUARANTINE */ + !split_by_recipient(e)) + aborting = bitset(EF_FATALERRS, e->e_flags); + + if (aborting) + { + /* Log who the mail would have gone to */ + logundelrcpts(e, e->e_message, 8, false); + 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 + { + /* send to all recipients */ + sendall(ee, SM_DEFAULT); + } + ee->e_to = NULL; + } + + /* issue success message */ + message("250 2.0.0 %s Message accepted for delivery", id); + + /* 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; + } +#if _FFR_QUARANTINE + else if (QueueMode != QM_QUARANTINE && + ee->e_quarmsg != NULL) + { + ee->e_sendmode = SM_QUEUE; + continue; + } +#endif /* _FFR_QUARANTINE */ + 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 _FFR_QUARANTINE + if (!doublequeue && + QueueMode != QM_QUARANTINE && + ee->e_quarmsg != NULL) + { + dropenvelope(ee, true, false); + continue; + } +#endif /* _FFR_QUARANTINE */ + 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; + newenvelope(e, e, sm_rpool_new_x(NULL)); + e->e_flags = BlankEnvelope.e_flags; + +#if _FFR_QUARANTINE + /* 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); + } +#endif /* _FFR_QUARANTINE */ +} +/* +** 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: @@ -2341,12 +3116,15 @@ doquit: static time_t checksmtpattack(pcounter, maxcount, waitnow, cname, e) - volatile int *pcounter; + volatile unsigned int *pcounter; int maxcount; bool waitnow; char *cname; ENVELOPE *e; { + if (maxcount <= 0) /* no limit */ + return (time_t) 0; + if (++(*pcounter) >= maxcount) { time_t s; @@ -2354,25 +3132,102 @@ checksmtpattack(pcounter, maxcount, waitnow, cname, e) if (*pcounter == maxcount && LogLevel > 5) { sm_syslog(LOG_INFO, e->e_id, - "%.100s: possible SMTP attack: command=%.40s, count=%d", + "%.100s: possible SMTP attack: command=%.40s, count=%u", CurSmtpClient, cname, *pcounter); } s = 1 << (*pcounter - maxcount); - if (s >= MAXTIMEOUT) + if (s >= MAXTIMEOUT || s <= 0) s = MAXTIMEOUT; + /* sleep at least 1 second before returning */ (void) sleep(*pcounter / maxcount); s -= *pcounter / maxcount; if (waitnow) { (void) sleep(s); - return(0); + return 0; } - return(s); + return s; } - return((time_t) 0); + 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: @@ -2396,8 +3251,7 @@ skipword(p, w) char *firstp = p; /* find beginning of word */ - while (isascii(*p) && isspace(*p)) - p++; + SKIP_SPACE(p); q = p; /* find end of word */ @@ -2413,19 +3267,18 @@ skipword(p, w) return NULL; } *p++ = '\0'; - while (isascii(*p) && isspace(*p)) - p++; + SKIP_SPACE(p); if (*p == '\0') goto syntax; /* see if the input word matches desired word */ - if (strcasecmp(q, w)) + if (sm_strcasecmp(q, w)) goto syntax; return p; } -/* +/* ** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line ** ** Parameters: @@ -2443,45 +3296,50 @@ mail_esmtp_args(kp, vp, e) char *vp; ENVELOPE *e; { - if (strcasecmp(kp, "size") == 0) + if (sm_strcasecmp(kp, "size") == 0) { if (vp == NULL) { usrerr("501 5.5.2 SIZE requires a value"); /* NOTREACHED */ } - define(macid("{msg_size}", NULL), newstr(vp), e); + 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 (strcasecmp(kp, "body") == 0) + else if (sm_strcasecmp(kp, "body") == 0) { if (vp == NULL) { usrerr("501 5.5.2 BODY requires a value"); /* NOTREACHED */ } - else if (strcasecmp(vp, "8bitmime") == 0) + else if (sm_strcasecmp(vp, "8bitmime") == 0) { - SevenBitInput = FALSE; + SevenBitInput = false; } - else if (strcasecmp(vp, "7bit") == 0) + else if (sm_strcasecmp(vp, "7bit") == 0) { - SevenBitInput = TRUE; + SevenBitInput = true; } else { - usrerr("501 5.5.4 Unknown BODY type %s", - vp); + usrerr("501 5.5.4 Unknown BODY type %s", vp); /* NOTREACHED */ } - e->e_bodytype = newstr(vp); + e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, vp); } - else if (strcasecmp(kp, "envid") == 0) + else if (sm_strcasecmp(kp, "envid") == 0) { if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) { @@ -2503,10 +3361,11 @@ mail_esmtp_args(kp, vp, e) usrerr("501 5.5.0 Duplicate ENVID parameter"); /* NOTREACHED */ } - e->e_envid = newstr(vp); - define(macid("{dsn_envid}", NULL), newstr(vp), e); + 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 (strcasecmp(kp, "ret") == 0) + else if (sm_strcasecmp(kp, "ret") == 0) { if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) { @@ -2524,23 +3383,24 @@ mail_esmtp_args(kp, vp, e) /* NOTREACHED */ } e->e_flags |= EF_RET_PARAM; - if (strcasecmp(vp, "hdrs") == 0) + if (sm_strcasecmp(vp, "hdrs") == 0) e->e_flags |= EF_NO_BODY_RETN; - else if (strcasecmp(vp, "full") != 0) + else if (sm_strcasecmp(vp, "full") != 0) { usrerr("501 5.5.2 Bad argument \"%s\" to RET", vp); /* NOTREACHED */ } - define(macid("{dsn_ret}", NULL), newstr(vp), e); + macdefine(&e->e_macro, A_TEMP, macid("{dsn_ret}"), vp); } -# if SASL - else if (strcasecmp(kp, "auth") == 0) +#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; char pbuf[256]; if (vp == NULL) @@ -2558,7 +3418,7 @@ mail_esmtp_args(kp, vp, e) else len = strlen(vp) + 1; auth_param = xalloc(len); - (void) strlcpy(auth_param, vp, len); + (void) sm_strlcpy(auth_param, vp, len); if (!xtextok(auth_param)) { usrerr("501 5.5.4 Syntax error in AUTH parameter value"); @@ -2567,11 +3427,11 @@ mail_esmtp_args(kp, vp, e) } /* XXX this might be cut off */ - snprintf(pbuf, sizeof pbuf, "%s", xuntextify(auth_param)); + (void) sm_strlcpy(pbuf, xuntextify(auth_param), sizeof pbuf); /* xalloc() the buffer instead? */ /* XXX define this always or only if trusted? */ - define(macid("{auth_author}", NULL), newstr(pbuf), e); + macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), pbuf); /* ** call Strust_auth to find out whether @@ -2580,42 +3440,123 @@ mail_esmtp_args(kp, vp, e) ** (required by RFC, leave it to ruleset?) */ - SuprErrs = TRUE; - QuickAbort = FALSE; + SuprErrs = true; + QuickAbort = false; if (strcmp(auth_param, "<>") != 0 && - (rscheck("trust_auth", pbuf, NULL, e, TRUE, FALSE, 10, - NULL) != EX_OK || Errors > 0)) + (rscheck("trust_auth", pbuf, NULL, e, true, false, 9, + NULL, NOQID) != EX_OK || Errors > 0)) { if (tTd(95, 8)) { q = e->e_auth_param; - dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n", + sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n", pbuf, (q == NULL) ? "" : q); } + /* not trusted */ - e->e_auth_param = newstr("<>"); + 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)) - dprintf("auth=\"%.100s\" trusted\n", pbuf); - e->e_auth_param = newstr(auth_param); + sm_dprintf("auth=\"%.100s\" trusted\n", pbuf); + e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, + auth_param); } - sm_free(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? */ } -# endif /* SASL */ else { usrerr("555 5.5.4 %s parameter unrecognized", kp); /* NOTREACHED */ } } -/* +/* ** RCPT_ESMTP_ARGS -- process ESMTP arguments from RCPT line ** ** Parameters: @@ -2635,7 +3576,7 @@ rcpt_esmtp_args(a, kp, vp, e) char *vp; ENVELOPE *e; { - if (strcasecmp(kp, "notify") == 0) + if (sm_strcasecmp(kp, "notify") == 0) { char *p; @@ -2651,20 +3592,20 @@ rcpt_esmtp_args(a, kp, vp, e) } a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY); a->q_flags |= QHASNOTIFY; - define(macid("{dsn_notify}", NULL), newstr(vp), e); + macdefine(&e->e_macro, A_TEMP, macid("{dsn_notify}"), vp); - if (strcasecmp(vp, "never") == 0) + if (sm_strcasecmp(vp, "never") == 0) return; for (p = vp; p != NULL; vp = p) { p = strchr(p, ','); if (p != NULL) *p++ = '\0'; - if (strcasecmp(vp, "success") == 0) + if (sm_strcasecmp(vp, "success") == 0) a->q_flags |= QPINGONSUCCESS; - else if (strcasecmp(vp, "failure") == 0) + else if (sm_strcasecmp(vp, "failure") == 0) a->q_flags |= QPINGONFAILURE; - else if (strcasecmp(vp, "delay") == 0) + else if (sm_strcasecmp(vp, "delay") == 0) a->q_flags |= QPINGONDELAY; else { @@ -2674,7 +3615,7 @@ rcpt_esmtp_args(a, kp, vp, e) } } } - else if (strcasecmp(kp, "orcpt") == 0) + else if (sm_strcasecmp(kp, "orcpt") == 0) { if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) { @@ -2696,7 +3637,7 @@ rcpt_esmtp_args(a, kp, vp, e) usrerr("501 5.5.0 Duplicate ORCPT parameter"); /* NOTREACHED */ } - a->q_orcpt = newstr(vp); + a->q_orcpt = sm_rpool_strdup_x(e->e_rpool, vp); } else { @@ -2704,11 +3645,11 @@ rcpt_esmtp_args(a, kp, vp, e) /* NOTREACHED */ } } -/* +/* ** PRINTVRFYADDR -- print an entry in the verify queue ** ** Parameters: -** a -- the address to print +** a -- the address to print. ** last -- set if this is the last one. ** vrfy -- set if this is a VRFY command. ** @@ -2730,21 +3671,21 @@ printvrfyaddr(a, last, vrfy) if (vrfy && a->q_mailer != NULL && !bitnset(M_VRFY250, a->q_mailer->m_flags)) - (void) strlcpy(fmtbuf, "252", sizeof fmtbuf); + (void) sm_strlcpy(fmtbuf, "252", sizeof fmtbuf); else - (void) strlcpy(fmtbuf, "250", sizeof fmtbuf); + (void) sm_strlcpy(fmtbuf, "250", sizeof fmtbuf); fmtbuf[3] = last ? ' ' : '-'; - (void) strlcpy(&fmtbuf[4], "2.1.5 ", sizeof fmtbuf - 4); + (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 || - strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && + sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && strchr(a->q_user, '@') == NULL) - (void) strlcpy(&fmtbuf[OFFF], "<%s@%s>", + (void) sm_strlcpy(&fmtbuf[OFFF], "<%s@%s>", sizeof fmtbuf - OFFF); else - (void) strlcpy(&fmtbuf[OFFF], "<%s>", + (void) sm_strlcpy(&fmtbuf[OFFF], "<%s>", sizeof fmtbuf - OFFF); message(fmtbuf, a->q_user, MyHostName); } @@ -2752,127 +3693,27 @@ printvrfyaddr(a, last, vrfy) { if ((a->q_mailer == NULL || a->q_mailer->m_addrtype == NULL || - strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && + sm_strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && strchr(a->q_user, '@') == NULL) - (void) strlcpy(&fmtbuf[OFFF], "%s <%s@%s>", + (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s@%s>", sizeof fmtbuf - OFFF); else - (void) strlcpy(&fmtbuf[OFFF], "%s <%s>", + (void) sm_strlcpy(&fmtbuf[OFFF], "%s <%s>", sizeof fmtbuf - OFFF); message(fmtbuf, a->q_fullname, a->q_user, MyHostName); } } -/* -** RUNINCHILD -- return twice -- once in the child, then in the parent again -** -** Parameters: -** label -- a string used in error messages -** -** Returns: -** RIC_INCHILD in the child -** RIC_INPARENT in the parent -** RIC_TEMPFAIL tempfail condition -** -** Side Effects: -** none. -*/ - -static int -runinchild(label, e) - char *label; - register ENVELOPE *e; -{ - pid_t childpid; - - if (!OneXact) - { - extern int NumQueues; - /* - ** advance state of PRNG - ** this is necessary because otherwise all child processes - ** will produce the same PRN sequence and hence the selection - ** of a queue directory is not "really" random. - */ - if (NumQueues > 1) - (void) get_random(); - - /* - ** Disable child process reaping, in case ETRN has preceded - ** MAIL command, and then fork. - */ - - (void) blocksignal(SIGCHLD); - - - childpid = dofork(); - if (childpid < 0) - { - syserr("451 4.3.0 %s: cannot fork", label); - (void) releasesignal(SIGCHLD); - return RIC_INPARENT; - } - if (childpid > 0) - { - auto int st; - - /* parent -- wait for child to complete */ - sm_setproctitle(TRUE, e, "server %s child wait", - CurSmtpClient); - st = waitfor(childpid); - if (st == -1) - syserr("451 4.3.0 %s: lost child", label); - else if (!WIFEXITED(st)) - { - syserr("451 4.3.0 %s: died on signal %d", - label, st & 0177); - return RIC_TEMPFAIL; - } - - /* if exited on a QUIT command, complete the process */ - if (WEXITSTATUS(st) == EX_QUIT) - { - disconnect(1, e); - finis(TRUE, ExitStat); - } - - /* restore the child signal */ - (void) releasesignal(SIGCHLD); - - return RIC_INPARENT; - } - else - { - /* child */ - InChild = TRUE; - QuickAbort = FALSE; - - /* Reset global flags */ - RestartRequest = NULL; - ShutdownRequest = NULL; - PendingSignal = 0; - - clearstats(); - clearenvelope(e, FALSE); - assign_queueid(e); - (void) setsignal(SIGCHLD, SIG_DFL); - (void) releasesignal(SIGCHLD); - } - } - return RIC_INCHILD; -} - -# if SASL - -/* +#if SASL +/* ** SASLMECHS -- get list of possible AUTH mechanisms ** ** Parameters: -** conn -- SASL connection info -** mechlist -- output parameter for list of mechanisms +** conn -- SASL connection info. +** mechlist -- output parameter for list of mechanisms. ** ** Returns: -** number of mechs +** number of mechs. */ static int @@ -2885,35 +3726,41 @@ saslmechs(conn, mechlist) /* "user" is currently unused */ result = sasl_listmech(conn, "user", /* XXX */ "", " ", "", mechlist, - (u_int *)&len, (u_int *)&num); - if (result == SASL_OK && num > 0) + (unsigned int *)&len, (unsigned int *)&num); + 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, - "SASL: available mech=%s, allowed mech=%s", + "AUTH: available mech=%s, allowed mech=%s", *mechlist, AuthMechanisms); - *mechlist = intersect(AuthMechanisms, *mechlist); + *mechlist = intersect(AuthMechanisms, *mechlist, NULL); } else { - if (LogLevel > 9) + *mechlist = NULL; /* be paranoid... */ + if (result == SASL_OK && LogLevel > 9) sm_syslog(LOG_WARNING, NOQID, - "SASL error: listmech=%d, num=%d", - result, num); - num = 0; + "AUTH warning: no mechanisms"); } return num; } - -/* +/* ** PROXY_POLICY -- define proxy policy for AUTH ** ** Parameters: -** conntext -- unused -** auth_identity -- authentication identity -** requested_user -- authorization identity -** user -- allowed user (output) -** errstr -- possible error string (output) +** context -- unused. +** auth_identity -- authentication identity. +** requested_user -- authorization identity. +** user -- allowed user (output). +** errstr -- possible error string (output). ** ** Returns: ** ok? @@ -2932,1275 +3779,139 @@ proxy_policy(context, auth_identity, requested_user, user, errstr) *user = newstr(auth_identity); return SASL_OK; } +#endif /* SASL */ -# endif /* SASL */ - -# if STARTTLS -# if !TLS_NO_RSA -RSA *rsa_tmp; /* temporary RSA key */ -static RSA * tmp_rsa_key __P((SSL *, int, int)); -# endif /* !TLS_NO_RSA */ - -# if !NO_DH -static DH *get_dh512 __P((void)); - -static unsigned char dh512_p[] = -{ - 0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75, - 0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F, - 0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3, - 0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12, - 0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C, - 0x47,0x74,0xE8,0x33 -}; -static unsigned char dh512_g[] = -{ - 0x02 -}; - -static DH * -get_dh512() -{ - DH *dh = NULL; - - if ((dh = DH_new()) == NULL) - return(NULL); - dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); - dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL); - if ((dh->p == NULL) || (dh->g == NULL)) - return(NULL); - return(dh); -} -# endif /* !NO_DH */ - -/* -** TLS_RAND_INIT -- initialize STARTTLS random generator -** -** Parameters: -** randfile -- name of file with random data -** logl -- loglevel -** -** Returns: -** success/failure -** -** Side Effects: -** initializes PRNG for tls library. -*/ - -#define MIN_RAND_BYTES 16 /* 128 bits */ - -bool -tls_rand_init(randfile, logl) - char *randfile; - int logl; -{ -# ifndef HASURANDOMDEV - /* not required if /dev/urandom exists, OpenSSL does it internally */ -#define RF_OK 0 /* randfile OK */ -#define RF_MISS 1 /* randfile == NULL || *randfile == '\0' */ -#define RF_UNKNOWN 2 /* unknown prefix for randfile */ - -#define RI_NONE 0 /* no init yet */ -#define RI_SUCCESS 1 /* init was successful */ -#define RI_FAIL 2 /* init failed */ - - bool ok; - int randdef; - static int done = RI_NONE; - - /* - ** initialize PRNG - */ - - /* did we try this before? if yes: return old value */ - if (done != RI_NONE) - return done == RI_SUCCESS; - - /* set default values */ - ok = FALSE; - done = RI_FAIL; - randdef = (randfile == NULL || *randfile == '\0') ? RF_MISS : RF_OK; -# if EGD - if (randdef == RF_OK && strncasecmp(randfile, "egd:", 4) == 0) - { - randfile += 4; - if (RAND_egd(randfile) < 0) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: RAND_egd(%s) failed: random number generator not seeded", - randfile); - } - else - ok = TRUE; - } - else -# endif /* EGD */ - if (randdef == RF_OK && strncasecmp(randfile, "file:", 5) == 0) - { - int fd; - long sff; - struct stat st; - - randfile += 5; - sff = SFF_SAFEDIRPATH | SFF_NOWLINK - | SFF_NOGWFILES | SFF_NOWWFILES - | SFF_NOGRFILES | SFF_NOWRFILES - | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT; - if ((fd = safeopen(randfile, O_RDONLY, 0, sff)) >= 0) - { - if (fstat(fd, &st) < 0) - { - if (LogLevel > logl) - sm_syslog(LOG_ERR, NOQID, - "TLS: can't fstat(%s)", - randfile); - } - else - { - bool use, problem; - - use = TRUE; - problem = FALSE; - if (st.st_mtime + 600 < curtime()) - { - use = bitnset(DBS_INSUFFICIENTENTROPY, - DontBlameSendmail); - problem = TRUE; - if (LogLevel > logl) - sm_syslog(LOG_ERR, NOQID, - "TLS: RandFile %s too old: %s", - randfile, - use ? "unsafe" : - "unusable"); - } - if (use && st.st_size < MIN_RAND_BYTES) - { - use = bitnset(DBS_INSUFFICIENTENTROPY, - DontBlameSendmail); - problem = TRUE; - if (LogLevel > logl) - sm_syslog(LOG_ERR, NOQID, - "TLS: size(%s) < %d: %s", - randfile, - MIN_RAND_BYTES, - use ? "unsafe" : - "unusable"); - } - if (use) - ok = RAND_load_file(randfile, -1) >= - MIN_RAND_BYTES; - if (use && !ok) - { - if (LogLevel > logl) - sm_syslog(LOG_WARNING, - NOQID, - "TLS: RAND_load_file(%s) failed: random number generator not seeded", - randfile); - } - if (problem) - ok = FALSE; - } - if (ok || bitnset(DBS_INSUFFICIENTENTROPY, - DontBlameSendmail)) - { - /* add this even if fstat() failed */ - RAND_seed((void *) &st, sizeof st); - } - (void) close(fd); - } - else - { - if (LogLevel > logl) - sm_syslog(LOG_WARNING, NOQID, - "TLS: Warning: safeopen(%s) failed", - randfile); - } - } - else if (randdef == RF_OK) - { - if (LogLevel > logl) - sm_syslog(LOG_WARNING, NOQID, - "TLS: Error: no proper random file definition %s", - randfile); - randdef = RF_UNKNOWN; - } - if (randdef == RF_MISS) - { - if (LogLevel > logl) - sm_syslog(LOG_WARNING, NOQID, - "TLS: Error: missing random file definition"); - } - if (!ok && bitnset(DBS_INSUFFICIENTENTROPY, DontBlameSendmail)) - { - int i; - long r; - unsigned char buf[MIN_RAND_BYTES]; - - /* assert((MIN_RAND_BYTES % sizeof(long)) == 0); */ - for (i = 0; i <= sizeof(buf) - sizeof(long); i += sizeof(long)) - { - r = get_random(); - (void) memcpy(buf + i, (void *) &r, sizeof(long)); - } - RAND_seed(buf, sizeof buf); - if (LogLevel > logl) - sm_syslog(LOG_WARNING, NOQID, - "TLS: Warning: random number generator not properly seeded"); - ok = TRUE; - } - done = ok ? RI_SUCCESS : RI_FAIL; - return ok; -# else /* !HASURANDOMDEV */ - return TRUE; -# endif /* !HASURANDOMDEV */ -} - +#if STARTTLS /* -** status in initialization -** these flags keep track of the status of the initialization -** i.e., whether a file exists (_EX) and whether it can be used (_OK) -** [due to permissions] -*/ -#define TLS_S_NONE 0x00000000 /* none yet */ -#define TLS_S_CERT_EX 0x00000001 /* CERT file exists */ -#define TLS_S_CERT_OK 0x00000002 /* CERT file is ok */ -#define TLS_S_KEY_EX 0x00000004 /* KEY file exists */ -#define TLS_S_KEY_OK 0x00000008 /* KEY file is ok */ -#define TLS_S_CERTP_EX 0x00000010 /* CA CERT PATH exists */ -#define TLS_S_CERTP_OK 0x00000020 /* CA CERT PATH is ok */ -#define TLS_S_CERTF_EX 0x00000040 /* CA CERT FILE exists */ -#define TLS_S_CERTF_OK 0x00000080 /* CA CERT FILE is ok */ - -# if _FFR_TLS_1 -#define TLS_S_CERT2_EX 0x00001000 /* 2nd CERT file exists */ -#define TLS_S_CERT2_OK 0x00002000 /* 2nd CERT file is ok */ -#define TLS_S_KEY2_EX 0x00004000 /* 2nd KEY file exists */ -#define TLS_S_KEY2_OK 0x00008000 /* 2nd KEY file is ok */ -# endif /* _FFR_TLS_1 */ - -#define TLS_S_DH_OK 0x00200000 /* DH cert is ok */ -#define TLS_S_DHPAR_EX 0x00400000 /* DH param file exists */ -#define TLS_S_DHPAR_OK 0x00800000 /* DH param file is ok to use */ - -/* -** TLS_OK_F -- can var be an absolute filename? -** -** Parameters: -** var -- filename -** fn -- what is the filename used for? -** -** Returns: -** ok? -*/ - -static bool -tls_ok_f(var, fn) - char *var; - char *fn; -{ - /* must be absolute pathname */ - if (var != NULL && *var == '/') - return TRUE; - if (LogLevel > 12) - sm_syslog(LOG_WARNING, NOQID, "TLS: file %s missing", fn); - return FALSE; -} - -/* -** TLS_SAFE_F -- is a file safe to use? -** -** Parameters: -** var -- filename -** sff -- flags for safefile() -** -** Returns: -** ok? -*/ - -static bool -tls_safe_f(var, sff) - char *var; - long sff; -{ - int ret; - - if ((ret = safefile(var, RunAsUid, RunAsGid, RunAsUserName, sff, - S_IRUSR, NULL)) == 0) - return TRUE; - if (LogLevel > 7) - sm_syslog(LOG_WARNING, NOQID, "TLS: file %s unsafe: %s", - var, errstring(ret)); - return FALSE; -} - -/* -** TLS_OK_F -- macro to simplify calls to tls_ok_f -** -** Parameters: -** var -- filename -** fn -- what is the filename used for? -** req -- is the file required? -** st -- status bit to set if ok -** -** Side Effects: -** uses r, ok; may change ok and status. -** -*/ - -#define TLS_OK_F(var, fn, req, st) if (ok) \ - { \ - r = tls_ok_f(var, fn); \ - if (r) \ - status |= st; \ - else if (req) \ - ok = FALSE; \ - } - -/* -** TLS_UNR -- macro to return whether a file should be unreadable -** -** Parameters: -** bit -- flag to test -** req -- flags -** -** Returns: -** 0/SFF_NORFILES -*/ -#define TLS_UNR(bit, req) (bitset(bit, req) ? SFF_NORFILES : 0) - -/* -** TLS_SAFE_F -- macro to simplify calls to tls_safe_f -** -** Parameters: -** var -- filename -** sff -- flags for safefile() -** req -- is the file required? -** ex -- does the file exist? -** st -- status bit to set if ok -** -** Side Effects: -** uses r, ok, ex; may change ok and status. -** -*/ - -#define TLS_SAFE_F(var, sff, req, ex, st) if (ex && ok) \ - { \ - r = tls_safe_f(var, sff); \ - if (r) \ - status |= st; \ - else if (req) \ - ok = FALSE; \ - } -/* -** INIT_TLS_LIBRARY -- calls functions which setup TLS library for global use +** INITSRVTLS -- initialize server side TLS ** ** Parameters: -** none. +** tls_ok -- should tls initialization be done? ** ** Returns: ** succeeded? ** ** Side Effects: -** Sets tls_ok_srv static, even when called from main() +** sets tls_ok_srv which is a static variable in this module. +** Do NOT remove assignments to it! */ bool -init_tls_library() +initsrvtls(tls_ok) + bool tls_ok; { - /* - ** basic TLS initialization - ** ignore result for now - */ - - SSL_library_init(); - SSL_load_error_strings(); -# if 0 - /* this is currently a macro for SSL_library_init */ - SSLeay_add_ssl_algorithms(); -# endif /* 0 */ - - /* initialize PRNG */ - tls_ok_srv = tls_rand_init(RandFile, 7); + 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; } -/* -** INITTLS -- initialize TLS +#endif /* STARTTLS */ +/* +** SRVFEATURES -- get features for SMTP server ** ** Parameters: -** ctx -- pointer to context -** req -- requirements for initialization (see sendmail.h) -** srv -- server side? -** certfile -- filename of certificate -** keyfile -- filename of private key -** cacertpath -- path to CAs -** cacertfile -- file with CA -** dhparam -- parameters for DH +** e -- envelope (should be session context). +** clientname -- name of client. +** features -- default features for this invocation. ** ** Returns: -** succeeded? +** server features. */ -bool -inittls(ctx, req, srv, certfile, keyfile, cacertpath, cacertfile, dhparam) - SSL_CTX **ctx; - u_long req; - bool srv; - char *certfile, *keyfile, *cacertpath, *cacertfile, *dhparam; +/* table with options: it uses just one character, how about strings? */ +static struct { -# if !NO_DH - static DH *dh = NULL; -# endif /* !NO_DH */ - int r; - bool ok; - long sff, status; - char *who; -# if _FFR_TLS_1 - char *cf2, *kf2; -# endif /* _FFR_TLS_1 */ - - status = TLS_S_NONE; - who = srv ? "srv" : "clt"; - if (ctx == NULL) - syserr("TLS: %s:inittls: ctx == NULL", who); - - /* already initialized? (we could re-init...) */ - if (*ctx != NULL) - return TRUE; - - /* PRNG seeded? */ - if (!tls_rand_init(RandFile, 10)) - return FALSE; - - /* let's start with the assumption it will work */ - ok = TRUE; - -# if _FFR_TLS_1 - /* - ** look for a second filename: it must be separated by a ',' - ** no blanks allowed (they won't be skipped). - ** we change a global variable here! this change will be undone - ** before return from the function but only if it returns TRUE. - ** this isn't a problem since in a failure case this function - ** won't be called again with the same (overwritten) values. - ** otherwise each return must be replaced with a goto endinittls. - */ - cf2 = NULL; - kf2 = NULL; - if (certfile != NULL && (cf2 = strchr(certfile, ',')) != NULL) - { - *cf2++ = '\0'; - if (keyfile != NULL && (kf2 = strchr(keyfile, ',')) != NULL) - *kf2++ = '\0'; - } -# endif /* _FFR_TLS_1 */ - - /* - ** what do we require from the client? - ** must it have CERTs? - ** introduce an option and decide based on that - */ - - TLS_OK_F(certfile, "CertFile", bitset(TLS_I_CERT_EX, req), - TLS_S_CERT_EX); - TLS_OK_F(keyfile, "KeyFile", bitset(TLS_I_KEY_EX, req), - TLS_S_KEY_EX); - TLS_OK_F(cacertpath, "CACERTPath", bitset(TLS_I_CERTP_EX, req), - TLS_S_CERTP_EX); - TLS_OK_F(cacertfile, "CACERTFile", bitset(TLS_I_CERTF_EX, req), - TLS_S_CERTF_EX); - -# if _FFR_TLS_1 - if (cf2 != NULL) - { - TLS_OK_F(cf2, "CertFile", bitset(TLS_I_CERT_EX, req), - TLS_S_CERT2_EX); - } - if (kf2 != NULL) - { - TLS_OK_F(kf2, "KeyFile", bitset(TLS_I_KEY_EX, req), - TLS_S_KEY2_EX); - } -# endif /* _FFR_TLS_1 */ - - /* - ** valid values for dhparam are (only the first char is checked) - ** none no parameters: don't use DH - ** 512 generate 512 bit parameters (fixed) - ** 1024 generate 1024 bit parameters - ** /file/name read parameters from /file/name - ** default is: 1024 for server, 512 for client (OK? XXX) - */ - if (bitset(TLS_I_TRY_DH, req)) - { - if (dhparam != NULL) - { - char c = *dhparam; - - if (c == '1') - req |= TLS_I_DH1024; - else if (c == '5') - req |= TLS_I_DH512; - else if (c != 'n' && c != 'N' && c != '/') - { - if (LogLevel > 12) - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: illegal value '%s' for DHParam", - dhparam); - dhparam = NULL; - } - } - if (dhparam == NULL) - dhparam = srv ? "1" : "5"; - else if (*dhparam == '/') - { - TLS_OK_F(dhparam, "DHParameters", - bitset(TLS_I_DHPAR_EX, req), - TLS_S_DHPAR_EX); - } - } - if (!ok) - return ok; - - /* certfile etc. must be "safe". */ - sff = SFF_REGONLY | SFF_SAFEDIRPATH | SFF_NOWLINK - | SFF_NOGWFILES | SFF_NOWWFILES - | SFF_MUSTOWN | SFF_ROOTOK | SFF_OPENASROOT; - if (DontLockReadFiles) - sff |= SFF_NOLOCK; - - TLS_SAFE_F(certfile, sff | TLS_UNR(TLS_I_CERT_UNR, req), - bitset(TLS_I_CERT_EX, req), - bitset(TLS_S_CERT_EX, status), TLS_S_CERT_OK); - TLS_SAFE_F(keyfile, sff | TLS_UNR(TLS_I_KEY_UNR, req), - bitset(TLS_I_KEY_EX, req), - bitset(TLS_S_KEY_EX, status), TLS_S_KEY_OK); - TLS_SAFE_F(cacertfile, sff | TLS_UNR(TLS_I_CERTF_UNR, req), - bitset(TLS_I_CERTF_EX, req), - bitset(TLS_S_CERTF_EX, status), TLS_S_CERTF_OK); - TLS_SAFE_F(dhparam, sff | TLS_UNR(TLS_I_DHPAR_UNR, req), - bitset(TLS_I_DHPAR_EX, req), - bitset(TLS_S_DHPAR_EX, status), TLS_S_DHPAR_OK); - if (!ok) - return ok; -# if _FFR_TLS_1 - if (cf2 != NULL) - { - TLS_SAFE_F(cf2, sff | TLS_UNR(TLS_I_CERT_UNR, req), - bitset(TLS_I_CERT_EX, req), - bitset(TLS_S_CERT2_EX, status), TLS_S_CERT2_OK); - } - if (kf2 != NULL) - { - TLS_SAFE_F(kf2, sff | TLS_UNR(TLS_I_KEY_UNR, req), - bitset(TLS_I_KEY_EX, req), - bitset(TLS_S_KEY2_EX, status), TLS_S_KEY2_OK); - } -# endif /* _FFR_TLS_1 */ - - /* create a method and a new context */ - if (srv) - { - if ((*ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) - { - if (LogLevel > 7) - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: SSL_CTX_new(SSLv23_server_method()) failed"); - if (LogLevel > 9) - tlslogerr(); - return FALSE; - } - } - else - { - if ((*ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) - { - if (LogLevel > 7) - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: SSL_CTX_new(SSLv23_client_method()) failed"); - if (LogLevel > 9) - tlslogerr(); - return FALSE; - } - } - -# if TLS_NO_RSA - /* turn off backward compatibility, required for no-rsa */ - SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2); -# endif /* TLS_NO_RSA */ - + char srvf_opt; + unsigned int srvf_flag; +} srv_feat_table[] = +{ + { 'A', SRV_OFFER_AUTH }, + { 'B', SRV_OFFER_VERB }, + { 'D', SRV_OFFER_DSN }, + { 'E', SRV_OFFER_ETRN }, + { 'L', SRV_REQ_AUTH }, /* not documented in 8.12 */ +#if PIPELINING +# if _FFR_NO_PIPE + { 'N', SRV_NO_PIPE }, +# endif /* _FFR_NO_PIPE */ + { 'P', SRV_OFFER_PIPE }, +#endif /* PIPELINING */ + { 'R', SRV_VRFY_CLT }, + { 'S', SRV_OFFER_TLS }, +/* { 'T', SRV_TMP_FAIL }, */ + { 'V', SRV_VRFY_CLT }, + { 'X', SRV_OFFER_EXPN }, +/* { 'Y', SRV_OFFER_VRFY }, */ + { '\0', SRV_NONE } +}; -# if !TLS_NO_RSA - /* - ** Create a temporary RSA key - ** XXX Maybe we shouldn't create this always (even though it - ** is only at startup). - ** It is a time-consuming operation and it is not always necessary. - ** maybe we should do it only on demand... - */ - if (bitset(TLS_I_RSA_TMP, req) && - (rsa_tmp = RSA_generate_key(RSA_KEYLENGTH, RSA_F4, NULL, - NULL)) == NULL - ) - { - if (LogLevel > 7) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: RSA_generate_key failed", - who); - if (LogLevel > 9) - tlslogerr(); - } - return FALSE; - } -# endif /* !TLS_NO_RSA */ +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; /* - ** load private key - ** XXX change this for DSA-only version + ** 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. */ - if (bitset(TLS_S_KEY_OK, status) && - SSL_CTX_use_PrivateKey_file(*ctx, keyfile, - SSL_FILETYPE_PEM) <= 0) - { - if (LogLevel > 7) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_use_PrivateKey_file(%s) failed", - who, keyfile); - if (LogLevel > 9) - tlslogerr(); - } - if (bitset(TLS_I_USE_KEY, req)) - return FALSE; - } - - /* get the certificate file */ - if (bitset(TLS_S_CERT_OK, status) && - SSL_CTX_use_certificate_file(*ctx, certfile, - SSL_FILETYPE_PEM) <= 0) - { - if (LogLevel > 7) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_use_certificate_file(%s) failed", - who, certfile); - if (LogLevel > 9) - tlslogerr(); - } - if (bitset(TLS_I_USE_CERT, req)) - return FALSE; - } - - /* check the private key */ - if (bitset(TLS_S_KEY_OK, status) && - (r = SSL_CTX_check_private_key(*ctx)) <= 0) - { - /* Private key does not match the certificate public key */ - if (LogLevel > 5) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_check_private_key failed(%s): %d", - who, keyfile, r); - if (LogLevel > 9) - tlslogerr(); - } - if (bitset(TLS_I_USE_KEY, req)) - return FALSE; - } -# if _FFR_TLS_1 - /* XXX this code is pretty much duplicated from above! */ - - /* load private key */ - if (bitset(TLS_S_KEY2_OK, status) && - SSL_CTX_use_PrivateKey_file(*ctx, kf2, SSL_FILETYPE_PEM) <= 0) + for (i = 1; pvp[i] != NULL; i++) { - if (LogLevel > 7) + c = pvp[i][0]; + j = 0; + for (;;) { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_use_PrivateKey_file(%s) failed", - who, kf2); - if (LogLevel > 9) - tlslogerr(); - } - } - - /* get the certificate file */ - if (bitset(TLS_S_CERT2_OK, status) && - SSL_CTX_use_certificate_file(*ctx, cf2, SSL_FILETYPE_PEM) <= 0) - { - if (LogLevel > 7) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_use_certificate_file(%s) failed", - who, cf2); - if (LogLevel > 9) - tlslogerr(); - } - } - - /* we should also check the private key: */ - if (bitset(TLS_S_KEY2_OK, status) && - (r = SSL_CTX_check_private_key(*ctx)) <= 0) - { - /* Private key does not match the certificate public key */ - if (LogLevel > 5) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_check_private_key 2 failed: %d", - who, r); - if (LogLevel > 9) - tlslogerr(); - } - } -# endif /* _FFR_TLS_1 */ - - /* SSL_CTX_set_quiet_shutdown(*ctx, 1); violation of standard? */ - SSL_CTX_set_options(*ctx, SSL_OP_ALL); /* XXX bug compatibility? */ - -# if !NO_DH - /* Diffie-Hellman initialization */ - if (bitset(TLS_I_TRY_DH, req)) - { - if (bitset(TLS_S_DHPAR_OK, status)) - { - BIO *bio; - - if ((bio = BIO_new_file(dhparam, "r")) != NULL) + if ((opt = srv_feat_table[j].srvf_opt) == '\0') { - dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); - BIO_free(bio); - if (dh == NULL && LogLevel > 7) - { - u_long err; - - err = ERR_get_error(); - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: cannot read DH parameters(%s): %s", - who, dhparam, - ERR_error_string(err, NULL)); - if (LogLevel > 9) - tlslogerr(); - } - } - else - { - if (LogLevel > 5) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: BIO_new_file(%s) failed", - who, dhparam); - if (LogLevel > 9) - tlslogerr(); - } - } - } - if (dh == NULL && bitset(TLS_I_DH1024, req)) - { - DSA *dsa; - - /* this takes a while! (7-130s on a 450MHz AMD K6-2) */ - dsa = DSA_generate_parameters(1024, NULL, 0, NULL, - NULL, 0, NULL); - dh = DSA_dup_DH(dsa); - DSA_free(dsa); - } - else - if (dh == NULL && bitset(TLS_I_DH512, req)) - dh = get_dh512(); - - if (dh == NULL) - { - if (LogLevel > 9) - { - u_long err; - - err = ERR_get_error(); - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: cannot read or set DH parameters(%s): %s", - who, dhparam, - ERR_error_string(err, NULL)); + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "srvfeatures: unknown feature %s", + pvp[i]); + break; } - if (bitset(TLS_I_REQ_DH, req)) - return FALSE; - } - else - { - SSL_CTX_set_tmp_dh(*ctx, dh); - - /* important to avoid small subgroup attacks */ - SSL_CTX_set_options(*ctx, SSL_OP_SINGLE_DH_USE); - if (LogLevel > 12) - sm_syslog(LOG_INFO, NOQID, - "TLS: %s: Diffie-Hellman init, key=%d bit (%c)", - who, 8 * DH_size(dh), *dhparam); - DH_free(dh); - } - } -# endif /* !NO_DH */ - - - /* XXX do we need this cache here? */ - if (bitset(TLS_I_CACHE, req)) - SSL_CTX_sess_set_cache_size(*ctx, 128); - /* timeout? SSL_CTX_set_timeout(*ctx, TimeOut...); */ - - /* load certificate locations and default CA paths */ - if (bitset(TLS_S_CERTP_EX, status) && bitset(TLS_S_CERTF_EX, status)) - { - if ((r = SSL_CTX_load_verify_locations(*ctx, cacertfile, - cacertpath)) == 1) - { -# if !TLS_NO_RSA - if (bitset(TLS_I_RSA_TMP, req)) - SSL_CTX_set_tmp_rsa_callback(*ctx, tmp_rsa_key); -# endif /* !TLS_NO_RSA */ - - /* ask to verify the peer */ - SSL_CTX_set_verify(*ctx, SSL_VERIFY_PEER, NULL); - - /* install verify callback */ - SSL_CTX_set_cert_verify_callback(*ctx, tls_verify_cb, - NULL); - SSL_CTX_set_client_CA_list(*ctx, - SSL_load_client_CA_file(cacertfile)); - } - else - { - /* - ** can't load CA data; do we care? - ** the data is necessary to authenticate the client, - ** which in turn would be necessary - ** if we want to allow relaying based on it. - */ - if (LogLevel > 5) + if (c == opt) { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: %d load verify locs %s, %s", - who, r, cacertpath, cacertfile); - if (LogLevel > 9) - tlslogerr(); + features &= ~(srv_feat_table[j].srvf_flag); + break; } - if (bitset(TLS_I_VRFY_LOC, req)) - return FALSE; - } - } - - /* XXX: make this dependent on an option? */ - if (tTd(96, 9)) - SSL_CTX_set_info_callback(*ctx, apps_ssl_info_cb); - -# if _FFR_TLS_1 - /* - ** XXX install our own cipher list: option? - */ - if (CipherList != NULL && *CipherList != '\0') - { - if (SSL_CTX_set_cipher_list(*ctx, CipherList) <= 0) - { - if (LogLevel > 7) + if (c == tolower(opt)) { - sm_syslog(LOG_WARNING, NOQID, - "TLS: error: %s: SSL_CTX_set_cipher_list(%s) failed, list ignored", - who, CipherList); - - if (LogLevel > 9) - tlslogerr(); + features |= srv_feat_table[j].srvf_flag; + break; } - /* failure if setting to this list is required? */ + ++j; } } -# endif /* _FFR_TLS_1 */ - if (LogLevel > 12) - sm_syslog(LOG_INFO, NOQID, "TLS: init(%s)=%d", who, ok); - -# if _FFR_TLS_1 -# if 0 - /* - ** this label is required if we want to have a "clean" exit - ** see the comments above at the initialization of cf2 - */ - endinittls: -# endif /* 0 */ - - /* undo damage to global variables */ - if (cf2 != NULL) - *--cf2 = ','; - if (kf2 != NULL) - *--kf2 = ','; -# endif /* _FFR_TLS_1 */ - - return ok; + return features; } -/* -** INITSRVTLS -- initialize server side TLS -** -** Parameters: -** none. -** -** Returns: -** succeeded? -** -** Side Effects: -** sets tls_ok_srv static, even when called from main() -*/ - -bool -initsrvtls() -{ - tls_ok_srv = inittls(&srv_ctx, TLS_I_SRV, TRUE, SrvCERTfile, - Srvkeyfile, CACERTpath, CACERTfile, DHParams); - return tls_ok_srv; -} -/* -** TLS_GET_INFO -- get information about TLS connection -** -** Parameters: -** ssl -- SSL connection structure -** e -- current envelope -** srv -- server or client -** host -- hostname of other side -** log -- log connection information? -** -** Returns: -** result of authentication. -** -** Side Effects: -** sets ${cipher}, ${tls_version}, ${verify}, ${cipher_bits}, -** ${cert} -*/ - -int -tls_get_info(ssl, e, srv, host, log) - SSL *ssl; - ENVELOPE *e; - bool srv; - char *host; - bool log; -{ - SSL_CIPHER *c; - int b, r; - char *s; - char bitstr[16]; - X509 *cert; - - c = SSL_get_current_cipher(ssl); - define(macid("{cipher}", NULL), newstr(SSL_CIPHER_get_name(c)), e); - b = SSL_CIPHER_get_bits(c, &r); - (void) snprintf(bitstr, sizeof bitstr, "%d", b); - define(macid("{cipher_bits}", NULL), newstr(bitstr), e); -# if _FFR_TLS_1 - (void) snprintf(bitstr, sizeof bitstr, "%d", r); - define(macid("{alg_bits}", NULL), newstr(bitstr), e); -# endif /* _FFR_TLS_1 */ - s = SSL_CIPHER_get_version(c); - if (s == NULL) - s = "UNKNOWN"; - define(macid("{tls_version}", NULL), newstr(s), e); - - cert = SSL_get_peer_certificate(ssl); - if (log && LogLevel >= 14) - sm_syslog(LOG_INFO, e->e_id, - "TLS: get_verify in %s: %ld get_peer: 0x%lx", - srv ? "srv" : "clt", - SSL_get_verify_result(ssl), (u_long) cert); - if (cert != NULL) - { - char buf[MAXNAME]; - - X509_NAME_oneline(X509_get_subject_name(cert), - buf, sizeof buf); - define(macid("{cert_subject}", NULL), - newstr(xtextify(buf, "<>\")")), e); - X509_NAME_oneline(X509_get_issuer_name(cert), - buf, sizeof buf); - define(macid("{cert_issuer}", NULL), - newstr(xtextify(buf, "<>\")")), e); -# if _FFR_TLS_1 - X509_NAME_get_text_by_NID(X509_get_subject_name(cert), - NID_commonName, buf, sizeof buf); - define(macid("{cn_subject}", NULL), - newstr(xtextify(buf, "<>\")")), e); - X509_NAME_get_text_by_NID(X509_get_issuer_name(cert), - NID_commonName, buf, sizeof buf); - define(macid("{cn_issuer}", NULL), - newstr(xtextify(buf, "<>\")")), e); -# endif /* _FFR_TLS_1 */ - } - else - { - define(macid("{cert_subject}", NULL), "", e); - define(macid("{cert_issuer}", NULL), "", e); -# if _FFR_TLS_1 - define(macid("{cn_subject}", NULL), "", e); - define(macid("{cn_issuer}", NULL), "", e); -# endif /* _FFR_TLS_1 */ - } - switch(SSL_get_verify_result(ssl)) - { - case X509_V_OK: - if (cert != NULL) - { - s = "OK"; - r = TLS_AUTH_OK; - } - else - { - s = "NO"; - r = TLS_AUTH_NO; - } - break; - default: - s = "FAIL"; - r = TLS_AUTH_FAIL; - break; - } - define(macid("{verify}", NULL), newstr(s), e); - if (cert != NULL) - X509_free(cert); - - /* do some logging */ - if (log && LogLevel > 9) - { - char *vers, *s1, *s2, *bits; - - vers = macvalue(macid("{tls_version}", NULL), e); - bits = macvalue(macid("{cipher_bits}", NULL), e); - s1 = macvalue(macid("{verify}", NULL), e); - s2 = macvalue(macid("{cipher}", NULL), e); - sm_syslog(LOG_INFO, NOQID, - "TLS: connection %s %.64s, version=%.16s, verify=%.16s, cipher=%.64s, bits=%.6s", - srv ? "from" : "to", - host == NULL ? "none" : host, - vers == NULL ? "none" : vers, - s1 == NULL ? "none" : s1, - s2 == NULL ? "none" : s2, - bits == NULL ? "0" : bits); - if (LogLevel > 11) - { - /* - ** maybe run xuntextify on the strings? - ** that is easier to read but makes it maybe a bit - ** more complicated to figure out the right values - ** for the access map... - */ - s1 = macvalue(macid("{cert_subject}", NULL), e); - s2 = macvalue(macid("{cert_issuer}", NULL), e); - sm_syslog(LOG_INFO, NOQID, - "TLS: %s cert subject:%.128s, cert issuer=%.128s", - srv ? "client" : "server", - s1 == NULL ? "none" : s1, - s2 == NULL ? "none" : s2); - } - } - - return r; -} - -# if !TLS_NO_RSA -/* -** TMP_RSA_KEY -- return temporary RSA key -** -** Parameters: -** s -- SSL connection structure -** export -- -** keylength -- -** -** Returns: -** temporary RSA key. -*/ - -/* ARGUSED0 */ -static RSA * -tmp_rsa_key(s, export, keylength) - SSL *s; - int export; - int keylength; -{ - return rsa_tmp; -} -# endif /* !TLS_NO_RSA */ -/* -** APPS_SSL_INFO_CB -- info callback for TLS connections -** -** Parameters: -** s -- SSL connection structure -** where -- -** ret -- -** -** Returns: -** none. -*/ - -void -apps_ssl_info_cb(s, where, ret) - SSL *s; - int where; - int ret; -{ - char *str; - int w; - BIO *bio_err = NULL; - - if (LogLevel > 14) - sm_syslog(LOG_INFO, NOQID, - "info_callback where 0x%x ret %d", where, ret); - - w = where & ~SSL_ST_MASK; - if (bio_err == NULL) - bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); - - if (w & SSL_ST_CONNECT) - str = "SSL_connect"; - else if (w & SSL_ST_ACCEPT) - str = "SSL_accept"; - else - str = "undefined"; - - if (where & SSL_CB_LOOP) - { - if (LogLevel > 12) - sm_syslog(LOG_NOTICE, NOQID, - "%s:%s\n", str, SSL_state_string_long(s)); - } - else if (where & SSL_CB_ALERT) - { - str = (where & SSL_CB_READ) ? "read" : "write"; - if (LogLevel > 12) - sm_syslog(LOG_NOTICE, NOQID, - "SSL3 alert %s:%s:%s\n", - str, SSL_alert_type_string_long(ret), - SSL_alert_desc_string_long(ret)); - } - else if (where & SSL_CB_EXIT) - { - if (ret == 0) - { - if (LogLevel > 7) - sm_syslog(LOG_WARNING, NOQID, - "%s:failed in %s\n", - str, SSL_state_string_long(s)); - } - else if (ret < 0) - { - if (LogLevel > 7) - sm_syslog(LOG_WARNING, NOQID, - "%s:error in %s\n", - str, SSL_state_string_long(s)); - } - } -} -/* -** TLS_VERIFY_LOG -- log verify error for TLS certificates -** -** Parameters: -** ok -- verify ok? -** ctx -- x509 context -** -** Returns: -** 0 -- fatal error -** 1 -- ok -*/ - -static int -tls_verify_log(ok, ctx) - int ok; - X509_STORE_CTX *ctx; -{ - SSL *ssl; - X509 *cert; - int reason, depth; - char buf[512]; - - cert = X509_STORE_CTX_get_current_cert(ctx); - reason = X509_STORE_CTX_get_error(ctx); - depth = X509_STORE_CTX_get_error_depth(ctx); - ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, - SSL_get_ex_data_X509_STORE_CTX_idx()); - - if (ssl == NULL) - { - /* internal error */ - sm_syslog(LOG_ERR, NOQID, - "TLS: internal error: tls_verify_cb: ssl == NULL"); - return 0; - } - - X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof buf); - sm_syslog(LOG_INFO, NOQID, - "TLS cert verify: depth=%d %s, state=%d, reason=%s\n", - depth, buf, ok, X509_verify_cert_error_string(reason)); - return 1; -} - -/* -** TLS_VERIFY_CB -- verify callback for TLS certificates -** -** Parameters: -** ctx -- x509 context -** -** Returns: -** accept connection? -** currently: always yes. -*/ - -static int -tls_verify_cb(ctx) - X509_STORE_CTX *ctx; -{ - int ok; - - ok = X509_verify_cert(ctx); - if (ok == 0) - { - if (LogLevel > 13) - return tls_verify_log(ok, ctx); - return 1; /* override it */ - } - return ok; -} - - -/* -** TLSLOGERR -- log the errors from the TLS error stack -** -** Parameters: -** none. -** -** Returns: -** none. -*/ - -void -tlslogerr() -{ - unsigned long l; - int line, flags; - unsigned long es; - char *file, *data; - char buf[256]; -#define CP (const char **) - - es = CRYPTO_thread_id(); - while ((l = ERR_get_error_line_data(CP &file, &line, CP &data, &flags)) - != 0) - { - sm_syslog(LOG_WARNING, NOQID, - "TLS: %lu:%s:%s:%d:%s\n", es, ERR_error_string(l, buf), - file, line, (flags & ERR_TXT_STRING) ? data : ""); - } -} - -# endif /* STARTTLS */ -#endif /* SMTP */ -/* +/* ** HELP -- implement the HELP command. ** ** Parameters: ** topic -- the topic we want help for. -** e -- envelope +** e -- envelope. ** ** Returns: ** none. @@ -4216,11 +3927,11 @@ help(topic, e) char *topic; ENVELOPE *e; { - register FILE *hf; + register SM_FILE_T *hf; register char *p; int len; bool noinfo; - bool first = TRUE; + bool first = true; long sff = SFF_OPENASROOT|SFF_REGONLY; char buf[MAXLINE]; char inp[MAXLINE]; @@ -4245,17 +3956,17 @@ help(topic, e) if (topic == NULL || *topic == '\0') { topic = "smtp"; - noinfo = FALSE; + noinfo = false; } else { makelower(topic); - noinfo = TRUE; + noinfo = true; } len = strlen(topic); - while (fgets(buf, sizeof buf, hf) != NULL) + while (sm_io_fgets(hf, SM_TIME_DEFAULT, buf, sizeof buf) != NULL) { if (buf[0] == '#') { @@ -4264,8 +3975,8 @@ help(topic, e) { int h; - if (sscanf(buf + strlen(HELPVSTR), "%d", - &h) == 1) + if (sm_io_sscanf(buf + strlen(HELPVSTR), "%d", + &h) == 1) foundvers = h; } continue; @@ -4274,7 +3985,7 @@ help(topic, e) { if (first) { - first = FALSE; + first = false; /* print version if no/old vers# in file */ if (foundvers < 2 && !noinfo) @@ -4285,7 +3996,7 @@ help(topic, e) p = buf + strlen(buf) - 1; else p++; - fixcrlf(p, TRUE); + fixcrlf(p, true); if (foundvers >= 2) { translate_dollars(p); @@ -4293,7 +4004,7 @@ help(topic, e) p = inp; } message("214-2.0.0 %s", p); - noinfo = FALSE; + noinfo = false; } } @@ -4313,5 +4024,5 @@ help(topic, e) foundvers = 0; } - (void) fclose(hf); + (void) sm_io_close(hf, SM_TIME_DEFAULT); } |