diff options
author | gshapiro <gshapiro@FreeBSD.org> | 2004-08-01 01:04:57 +0000 |
---|---|---|
committer | gshapiro <gshapiro@FreeBSD.org> | 2004-08-01 01:04:57 +0000 |
commit | 1fc446a819a244515d9461fa50d34ee191414d6f (patch) | |
tree | f6477ae85b00ee6d58b086b0d1d597dd9a403391 /contrib/sendmail/src/srvrsmtp.c | |
parent | 238623a0204c90e8d61dbde7b3b499a5036f2e5d (diff) | |
download | FreeBSD-src-1fc446a819a244515d9461fa50d34ee191414d6f.zip FreeBSD-src-1fc446a819a244515d9461fa50d34ee191414d6f.tar.gz |
Import sendmail 8.13.1
Diffstat (limited to 'contrib/sendmail/src/srvrsmtp.c')
-rw-r--r-- | contrib/sendmail/src/srvrsmtp.c | 620 |
1 files changed, 423 insertions, 197 deletions
diff --git a/contrib/sendmail/src/srvrsmtp.c b/contrib/sendmail/src/srvrsmtp.c index e752aa0..cb0366e 100644 --- a/contrib/sendmail/src/srvrsmtp.c +++ b/contrib/sendmail/src/srvrsmtp.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998-2003 Sendmail, Inc. and its suppliers. + * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers. * All rights reserved. * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 @@ -13,13 +13,16 @@ #include <sendmail.h> #if MILTER +# include <libmilter/mfapi.h> # include <libmilter/mfdef.h> #endif /* MILTER */ -SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.829.2.34 2004/01/14 19:13:46 ca Exp $") +SM_RCSID("@(#)$Id: srvrsmtp.c,v 8.900 2004/07/08 23:29:33 ca Exp $") + +#include <sys/time.h> +#include <sm/fdset.h> #if SASL || STARTTLS -# include <sys/time.h> # include "sfsasl.h" #endif /* SASL || STARTTLS */ #if SASL @@ -56,12 +59,14 @@ extern void tls_set_verify __P((SSL_CTX *, SSL *, bool)); # endif /* _FFR_NO_PIPE */ #endif /* PIPELINING */ #define SRV_REQ_AUTH 0x0400 /* require AUTH */ +#define SRV_REQ_SEC 0x0800 /* require security - equiv to AuthOptions=p */ #define SRV_TMP_FAIL 0x1000 /* ruleset caused a temporary failure */ static unsigned int srvfeatures __P((ENVELOPE *, char *, unsigned int)); -static time_t checksmtpattack __P((volatile unsigned int *, int, bool, - char *, ENVELOPE *)); +#define STOP_ATTACK ((time_t) -1) +static time_t checksmtpattack __P((volatile unsigned int *, unsigned 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 *)); @@ -75,7 +80,7 @@ static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname, char *_auth_id, sasl_ssf_t *_ext_ssf)); # define RESET_SASLCONN \ - result = reset_saslconn(&conn, hostname, remoteip, localip, auth_id, \ + result = reset_saslconn(&conn, AuthRealm, remoteip, localip, auth_id, \ &ext_ssf); \ if (result != SASL_OK) \ { \ @@ -89,7 +94,7 @@ static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname, struct sockaddr_in *_saddr_l, sasl_external_properties_t *_ext_ssf)); # define RESET_SASLCONN \ - result = reset_saslconn(&conn, hostname, &saddr_r, &saddr_l, &ext_ssf); \ + result = reset_saslconn(&conn, AuthRealm, &saddr_r, &saddr_l, &ext_ssf); \ if (result != SASL_OK) \ { \ /* This is pretty fatal */ \ @@ -101,6 +106,16 @@ static int reset_saslconn __P((sasl_conn_t **_conn, char *_hostname, extern ENVELOPE BlankEnvelope; +#define NBADRCPTS \ + do \ + { \ + char buf[16]; \ + (void) sm_snprintf(buf, sizeof buf, "%d", \ + BadRcptThrottle > 0 && n_badrcpts > BadRcptThrottle \ + ? n_badrcpts - 1 : n_badrcpts); \ + macdefine(&e->e_macro, A_TEMP, macid("{nbadrcpts}"), buf); \ + } while (0) + #define SKIP_SPACE(s) while (isascii(*s) && isspace(*s)) \ (s)++ @@ -221,6 +236,31 @@ static char *CurSmtpClient; /* who's at the other end of channel */ # define MAXTIMEOUT (4 * 60) /* max timeout for bad commands */ #endif /* ! MAXTIMEOUT */ +/* +** Maximum shift value to compute timeout for bad commands. +** This introduces an upper limit of 2^MAXSHIFT for the timeout. +*/ + +#ifndef MAXSHIFT +# define MAXSHIFT 8 +#endif /* ! MAXSHIFT */ +#if MAXSHIFT > 31 + ERROR _MAXSHIFT > 31 is invalid +#endif /* MAXSHIFT */ + + +#if MAXBADCOMMANDS > 0 +# define STOP_IF_ATTACK(r) do \ + { \ + if ((r) == STOP_ATTACK) \ + goto stopattack; \ + } while (0) + +#else /* MAXBADCOMMANDS > 0 */ +# define STOP_IF_ATTACK(r) r +#endif /* MAXBADCOMMANDS > 0 */ + + #if SM_HEAP_CHECK static SM_DEBUG_T DebugLeakSmtp = SM_DEBUG_INITIALIZER("leak_smtp", "@(#)$Debug: leak_smtp - trace memory leaks during SMTP processing $"); @@ -230,38 +270,21 @@ 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 *)); +static bool smtp_data __P((SMTP_T *, ENVELOPE *)); -#define MSG_TEMPFAIL "451 4.7.1 Please try again later" +#define MSG_TEMPFAIL "451 4.3.2 Please try again later" #if MILTER # define MILTER_ABORT(e) milter_abort((e)) -#if _FFR_MILTER_421 -# define MILTER_SHUTDOWN \ - if (strncmp(response, "421 ", 4) == 0) \ - { \ - e->e_sendqueue = NULL; \ - goto doquit; \ - } -#else /* _FFR_MILTER_421 */ -# define MILTER_SHUTDOWN -#endif /* _FFR_MILTER_421 */ - # define MILTER_REPLY(str) \ { \ int savelogusrerrs = LogUsrErrs; \ @@ -276,8 +299,18 @@ static void smtp_data __P((SMTP_T *, ENVELOPE *)); str, addr, response); \ LogUsrErrs = false; \ } \ - usrerr(response); \ - MILTER_SHUTDOWN \ + if (strncmp(response, "421 ", 4) == 0) \ + { \ + bool tsave = QuickAbort; \ + \ + QuickAbort = false; \ + usrerr(response); \ + QuickAbort = tsave; \ + e->e_sendqueue = NULL; \ + goto doquit; \ + } \ + else \ + usrerr(response); \ break; \ \ case SMFIR_REJECT: \ @@ -321,6 +354,7 @@ static void smtp_data __P((SMTP_T *, ENVELOPE *)); /* clear all SMTP state (for HELO/EHLO/RSET) */ #define CLEAR_STATE(cmd) \ +do \ { \ /* abort milter filters */ \ MILTER_ABORT(e); \ @@ -347,7 +381,26 @@ static void smtp_data __P((SMTP_T *, ENVELOPE *)); sm_rpool_free(e->e_rpool); \ e = newenvelope(e, CurEnv, sm_rpool_new_x(NULL)); \ CurEnv = e; \ -} + \ + /* put back discard bit */ \ + if (smtp.sm_discard) \ + e->e_flags |= EF_DISCARD; \ + \ + /* restore connection quarantining */ \ + if (smtp.sm_quarmsg == NULL) \ + { \ + e->e_quarmsg = NULL; \ + macdefine(&e->e_macro, A_PERM, \ + macid("{quarantine}"), ""); \ + } \ + else \ + { \ + e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, \ + smtp.sm_quarmsg); \ + macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), \ + e->e_quarmsg); \ + } \ +} while (0) /* sleep to flatten out connection load */ #define MIN_DELAY_LOG 15 /* wait before logging this again */ @@ -401,9 +454,9 @@ smtp(nullserver, d_flags, e) volatile unsigned int n_helo = 0; /* count of HELO/EHLO */ volatile int save_sevenbitinput; bool ok; -#if _FFR_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL +#if _FFR_BLOCK_PROXIES volatile bool first; -#endif /* _FFR_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL */ +#endif /* _FFR_BLOCK_PROXIES */ volatile bool tempfail = false; volatile time_t wt; /* timeout after too many commands */ volatile time_t previous; /* time after checksmtpattack() */ @@ -448,13 +501,12 @@ smtp(nullserver, d_flags, e) volatile unsigned int n_mechs; unsigned int len; #endif /* SASL */ -#if STARTTLS int r; +#if STARTTLS + int fdfl; int rfd, wfd; volatile bool tls_active = false; -# if _FFR_SMTP_SSL - volatile bool smtps = false; -# endif /* _FFR_SMTP_SSL */ + volatile bool smtps = bitnset(D_SMTPS, d_flags); bool saveQuickAbort; bool saveSuprErrs; time_t tlsstart; @@ -521,6 +573,8 @@ smtp(nullserver, d_flags, e) : SRV_OFFER_DSN) #if SASL | (bitnset(D_NOAUTH, d_flags) ? SRV_NONE : SRV_OFFER_AUTH) + | (bitset(SASL_SEC_NOPLAINTEXT, SASLOpts) ? SRV_REQ_SEC + : SRV_NONE) #endif /* SASL */ #if PIPELINING | SRV_OFFER_PIPE @@ -542,19 +596,35 @@ smtp(nullserver, d_flags, e) CurSmtpClient); nullserver = "450 4.3.0 Please try again later."; } + else + { #if PIPELINING # if _FFR_NO_PIPE - else if (bitset(SRV_NO_PIPE, features)) - { - /* for consistency */ - features &= ~SRV_OFFER_PIPE; - } + if (bitset(SRV_NO_PIPE, features)) + { + /* for consistency */ + features &= ~SRV_OFFER_PIPE; + } # endif /* _FFR_NO_PIPE */ #endif /* PIPELINING */ +#if SASL + if (bitset(SRV_REQ_SEC, features)) + SASLOpts |= SASL_SEC_NOPLAINTEXT; + else + SASLOpts &= ~SASL_SEC_NOPLAINTEXT; +#endif /* SASL */ + } + } + else if (strncmp(nullserver, "421 ", 4) == 0) + { + message(nullserver); + goto doquit; } hostname = macvalue('j', e); #if SASL + if (AuthRealm == NULL) + AuthRealm = hostname; sasl_ok = bitset(SRV_OFFER_AUTH, features); n_mechs = 0; authenticating = SASL_NOT_AUTH; @@ -563,14 +633,14 @@ smtp(nullserver, d_flags, e) if (sasl_ok) { # if SASL >= 20000 - result = sasl_server_new("smtp", hostname, NULL, NULL, NULL, + result = sasl_server_new("smtp", AuthRealm, NULL, NULL, NULL, NULL, 0, &conn); # elif SASL > 10505 /* use empty realm: only works in SASL > 1.5.5 */ - result = sasl_server_new("smtp", hostname, "", NULL, 0, &conn); + result = sasl_server_new("smtp", AuthRealm, "", NULL, 0, &conn); # else /* SASL >= 20000 */ /* use no realm -> realm is set to hostname by SASL lib */ - result = sasl_server_new("smtp", hostname, NULL, NULL, 0, + result = sasl_server_new("smtp", AuthRealm, NULL, NULL, 0, &conn); # endif /* SASL >= 20000 */ sasl_ok = result == SASL_OK; @@ -775,7 +845,6 @@ smtp(nullserver, d_flags, e) smtp.sm_milterize = false; break; -#if _FFR_MILTER_421 case SMFIR_SHUTDOWN: if (MilterLogLevel > 3) sm_syslog(LOG_INFO, e->e_id, @@ -790,18 +859,75 @@ smtp(nullserver, d_flags, e) /* arrange to ignore send list */ e->e_sendqueue = NULL; goto doquit; -#endif /* _FFR_MILTER_421 */ } if (response != NULL) - sm_free(response); /* XXX */ } #endif /* MILTER */ + /* + ** Broken proxies and SMTP slammers + ** push data without waiting, catch them + */ + + if ( +#if STARTTLS + !smtps && +#endif /* STARTTLS */ + *greetcode == '2') + { + time_t msecs = 0; + char **pvp; + char pvpbuf[PSBUFSIZE]; + + /* Ask the rulesets how long to pause */ + pvp = NULL; + r = rscap("greet_pause", peerhostname, + anynet_ntoa(&RealHostAddr), e, + &pvp, pvpbuf, sizeof(pvpbuf)); + if (r == EX_OK && pvp != NULL && pvp[0] != NULL && + (pvp[0][0] & 0377) == CANONNET && pvp[1] != NULL) + { + msecs = strtol(pvp[1], NULL, 10); + } + + if (msecs > 0) + { + int fd; + fd_set readfds; + struct timeval timeout; + + /* pause for a moment */ + timeout.tv_sec = msecs / 1000; + timeout.tv_usec = (msecs % 1000) * 1000; + + /* Obey RFC 2821: 4.3.5.2: 220 timeout of 5 minutes */ + if (timeout.tv_sec >= 300) + { + timeout.tv_sec = 300; + timeout.tv_usec = 0; + } + + /* check if data is on the socket during the pause */ + fd = sm_io_getinfo(InChannel, SM_IO_WHAT_FD, NULL); + FD_ZERO(&readfds); + SM_FD_SET(fd, &readfds); + if (select(fd + 1, FDSET_CAST &readfds, + NULL, NULL, &timeout) > 0 && + FD_ISSET(fd, &readfds)) + { + greetcode = "554"; + nullserver = "Command rejected"; + sm_syslog(LOG_INFO, e->e_id, + "rejecting commands from %s [%s] due to pre-greeting traffic", + peerhostname, + anynet_ntoa(&RealHostAddr)); + } + } + } + #if STARTTLS -# if _FFR_SMTP_SSL /* If this an smtps connection, start TLS now */ - smtps = bitnset(D_SMTPS, d_flags); if (smtps) { Errors = 0; @@ -810,7 +936,6 @@ smtp(nullserver, d_flags, e) greeting: -# endif /* _FFR_SMTP_SSL */ #endif /* STARTTLS */ /* output the first line, inserting "ESMTP" as second word */ @@ -854,20 +979,18 @@ smtp(nullserver, d_flags, e) protocol = NULL; sendinghost = macvalue('s', e); -#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_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL +#if _FFR_BLOCK_PROXIES first = true; -#endif /* _FFR_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL */ +#endif /* _FFR_BLOCK_PROXIES */ gothello = false; smtp.sm_gotmail = false; for (;;) @@ -932,10 +1055,9 @@ smtp(nullserver, d_flags, e) goto doquit; } -#if _FFR_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL +#if _FFR_BLOCK_PROXIES if (first) { -#if _FFR_BLOCK_PROXIES size_t inplen, cmdlen; int idx; char *http_cmd; @@ -960,27 +1082,9 @@ smtp(nullserver, d_flags, e) goto doquit; } } -#endif /* _FFR_BLOCK_PROXIES */ -#if _FFR_ADAPTIVE_EOL - 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, - "%s did not use CRLF", - CurSmtpClient); - } - } -#endif /* _FFR_ADAPTIVE_EOL */ first = false; } -#endif /* _FFR_BLOCK_PROXIES || _FFR_ADAPTIVE_EOL */ +#endif /* _FFR_BLOCK_PROXIES */ /* clean up end of line */ fixcrlf(inp, true); @@ -1090,7 +1194,8 @@ smtp(nullserver, d_flags, e) { macdefine(&BlankEnvelope.e_macro, A_TEMP, - macid("{auth_authen}"), user); + macid("{auth_authen}"), + xtextify(user, "<>\")")); } # if 0 @@ -1353,15 +1458,15 @@ smtp(nullserver, d_flags, e) sm_syslog(LOG_INFO, e->e_id, "SMTP AUTH command (%.100s) from %s tempfailed (due to previous checks)", p, CurSmtpClient); - usrerr("454 4.7.1 Please try again later"); + usrerr("454 4.3.0 Please try again later"); break; } ismore = false; /* crude way to avoid crack attempts */ - (void) checksmtpattack(&n_auth, n_mechs + 1, true, - "AUTH", e); + STOP_IF_ATTACK(checksmtpattack(&n_auth, n_mechs + 1, + true, "AUTH", e)); /* make sure mechanism (p) is a valid string */ for (q = p; *q != '\0' && isascii(*q); q++) @@ -1524,12 +1629,10 @@ smtp(nullserver, d_flags, e) sm_syslog(LOG_INFO, e->e_id, "SMTP STARTTLS command (%.100s) from %s tempfailed (due to previous checks)", p, CurSmtpClient); - usrerr("454 4.7.1 Please try again later"); + usrerr("454 4.7.0 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 ? @@ -1554,11 +1657,7 @@ smtp(nullserver, d_flags, e) message("454 4.3.3 TLS not available: error generating SSL handle"); if (LogLevel > 8) tlslogerr("server"); -# if _FFR_SMTP_SSL goto tls_done; -# else /* _FFR_SMTP_SSL */ - break; -# endif /* _FFR_SMTP_SSL */ } # if !TLS_VRFY_PER_CTX @@ -1581,15 +1680,9 @@ 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 */ } -# 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); @@ -1600,6 +1693,9 @@ smtp(nullserver, d_flags, e) # define SSL_ACC(s) SSL_accept(s) tlsstart = curtime(); + fdfl = fcntl(rfd, F_GETFL); + if (fdfl != -1) + fcntl(rfd, F_SETFL, fdfl|O_NONBLOCK); ssl_retry: if ((r = SSL_ACC(srv_ssl)) <= 0) { @@ -1702,6 +1798,9 @@ tlsfail: goto doquit; } + if (fdfl != -1) + fcntl(rfd, F_SETFL, fdfl); + /* ignore return code for now, it's in {verify} */ (void) tls_get_info(srv_ssl, true, CurSmtpClient, @@ -1739,25 +1838,31 @@ tlsfail: # if SASL if (sasl_ok) { - char *s; + int cipher_bits; + bool verified; + char *s, *v, *c; s = macvalue(macid("{cipher_bits}"), e); -# if SASL >= 20000 - if (s != NULL && (ext_ssf = atoi(s)) > 0) + v = macvalue(macid("{verify}"), e); + c = macvalue(macid("{cert_subject}"), e); + verified = (v != NULL && strcmp(v, "OK") == 0); + if (s != NULL && (cipher_bits = atoi(s)) > 0) { - auth_id = macvalue(macid("{cert_subject}"), - e); - sasl_ok = ((sasl_setprop(conn, SASL_SSF_EXTERNAL, - &ext_ssf) == SASL_OK) && - (sasl_setprop(conn, SASL_AUTH_EXTERNAL, - auth_id) == SASL_OK)); +# if SASL >= 20000 + ext_ssf = cipher_bits; + auth_id = verified ? c : NULL; + sasl_ok = ((sasl_setprop(conn, + SASL_SSF_EXTERNAL, + &ext_ssf) == SASL_OK) && + (sasl_setprop(conn, + SASL_AUTH_EXTERNAL, + auth_id) == SASL_OK)); # else /* SASL >= 20000 */ - if (s != NULL && (ext_ssf.ssf = atoi(s)) > 0) - { - ext_ssf.auth_id = macvalue(macid("{cert_subject}"), - e); - sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL, - &ext_ssf) == SASL_OK; + ext_ssf.ssf = cipher_bits; + ext_ssf.auth_id = verified ? c : NULL; + sasl_ok = sasl_setprop(conn, + SASL_SSF_EXTERNAL, + &ext_ssf) == SASL_OK; # endif /* SASL >= 20000 */ mechlist = NULL; if (sasl_ok) @@ -1789,7 +1894,6 @@ tlsfail: nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer"; syserr("STARTTLS: can't switch to encrypted layer"); } -# if _FFR_SMTP_SSL tls_done: if (smtps) { @@ -1798,7 +1902,6 @@ tlsfail: else goto doquit; } -# endif /* _FFR_SMTP_SSL */ break; #endif /* STARTTLS */ @@ -1817,8 +1920,8 @@ tlsfail: } /* avoid denial-of-service */ - (void) checksmtpattack(&n_helo, MAXHELOCOMMANDS, true, - "HELO/EHLO", e); + STOP_IF_ATTACK(checksmtpattack(&n_helo, MAXHELOCOMMANDS, + true, "HELO/EHLO", e)); #if 0 /* RFC2821 4.1.4 allows duplicate HELO/EHLO */ @@ -1891,24 +1994,6 @@ tlsfail: if (gothello) { 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 */ } #if MILTER @@ -1951,7 +2036,6 @@ tlsfail: if (response != NULL) sm_free(response); -# if _FFR_QUARANTINE /* ** If quarantining by a connect/ehlo action, ** save between messages @@ -1960,7 +2044,6 @@ tlsfail: if (smtp.sm_quarmsg == NULL && e->e_quarmsg != NULL) smtp.sm_quarmsg = newstr(e->e_quarmsg); -# endif /* _FFR_QUARANTINE */ } #endif /* MILTER */ gothello = true; @@ -2113,6 +2196,8 @@ tlsfail: n_badrcpts = 0; macdefine(&e->e_macro, A_PERM, macid("{ntries}"), "0"); macdefine(&e->e_macro, A_PERM, macid("{nrcpts}"), "0"); + macdefine(&e->e_macro, A_PERM, macid("{nbadrcpts}"), + "0"); e->e_flags |= EF_CLRQUEUE; sm_setproctitle(true, e, "%s %s: %.80s", qid_printname(e), @@ -2352,6 +2437,7 @@ tlsfail: /* To avoid duplicated message */ n_badrcpts++; } + NBADRCPTS; /* ** Don't use exponential backoff for now. @@ -2556,20 +2642,25 @@ tlsfail: } rcpt_done: if (Errors > 0) + { ++n_badrcpts; + NBADRCPTS; + } } SM_EXCEPT(exc, "[!F]*") { /* An exception occurred while processing RCPT */ e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY); ++n_badrcpts; + NBADRCPTS; } SM_END_TRY break; case CMDDATA: /* data -- text of mail */ DELAY_CONN("DATA"); - smtp_data(&smtp, e); + if (!smtp_data(&smtp, e)) + goto doquit; break; case CMDRSET: /* rset -- reset state */ @@ -2578,22 +2669,6 @@ tlsfail: else message("250 2.0.0 Reset state"); 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 */ @@ -2614,6 +2689,7 @@ tlsfail: } wt = checksmtpattack(&n_verifies, MAXVRFYCOMMANDS, false, vrfy ? "VRFY" : "EXPN", e); + STOP_IF_ATTACK(wt); previous = curtime(); if ((vrfy && bitset(PRIV_NOVRFY, PrivacyFlags)) || (!vrfy && !bitset(SRV_OFFER_EXPN, features))) @@ -2741,8 +2817,8 @@ tlsfail: } /* crude way to avoid denial-of-service attacks */ - (void) checksmtpattack(&n_etrn, MAXETRNCOMMANDS, true, - "ETRN", e); + STOP_IF_ATTACK(checksmtpattack(&n_etrn, MAXETRNCOMMANDS, + true, "ETRN", e)); /* ** Do config file checking of the parameter. @@ -2817,8 +2893,8 @@ tlsfail: case CMDNOOP: /* noop -- do nothing */ DELAY_CONN("NOOP"); - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, true, - "NOOP", e); + STOP_IF_ATTACK(checksmtpattack(&n_noop, MAXNOOPCOMMANDS, + true, "NOOP", e)); message("250 2.0.0 OK"); break; @@ -2900,8 +2976,8 @@ doquit: message("502 5.7.0 Verbose unavailable"); break; } - (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, true, - "VERB", e); + STOP_IF_ATTACK(checksmtpattack(&n_noop, MAXNOOPCOMMANDS, + true, "VERB", e)); Verbose = 1; set_delivery_mode(SM_DELIVER, e); message("250 2.0.0 Verbose mode"); @@ -2911,7 +2987,7 @@ doquit: case CMDDBGQSHOW: /* show queues */ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "Send Queue="); - printaddr(e->e_sendqueue, true); + printaddr(smioout, e->e_sendqueue, true); break; case CMDDBGDEBUG: /* set debug mode */ @@ -2937,6 +3013,7 @@ doquit: #if MAXBADCOMMANDS > 0 if (++n_badcmds > MAXBADCOMMANDS) { + stopattack: message("421 4.7.0 %s Too many bad commands; closing connection", MyHostName); @@ -2946,6 +3023,28 @@ doquit: } #endif /* MAXBADCOMMANDS > 0 */ +#if MILTER && SMFI_VERSION > 2 + if (smtp.sm_milterlist && smtp.sm_milterize && + !bitset(EF_DISCARD, e->e_flags)) + { + char state; + char *response; + + if (MilterLogLevel > 9) + sm_syslog(LOG_INFO, e->e_id, + "Sending \"%s\" to Milter", inp); + response = milter_unknown(inp, e, &state); + MILTER_REPLY("unknown"); + if (state == SMFIR_REPLYCODE || + state == SMFIR_REJECT || + state == SMFIR_TEMPFAIL) + { + /* MILTER_REPLY already gave an error */ + break; + } + } +#endif /* MILTER && SMFI_VERSION > 2 */ + usrerr("500 5.5.1 Command unrecognized: \"%s\"", shortenstring(inp, MAXSHORTSTR)); break; @@ -2984,13 +3083,13 @@ doquit: ** e -- envelope. ** ** Returns: -** none. +** true iff SMTP session can continue. ** ** Side Effects: ** possibly sends message. */ -static void +static bool smtp_data(smtp, e) SMTP_T *smtp; ENVELOPE *e; @@ -3010,18 +3109,79 @@ smtp_data(smtp, e) if (!smtp->sm_gotmail) { usrerr("503 5.0.0 Need MAIL command"); - return; + return true; } else if (smtp->sm_nrcpts <= 0) { usrerr("503 5.0.0 Need RCPT (recipient)"); - return; + return true; } (void) sm_snprintf(buf, sizeof buf, "%u", smtp->sm_nrcpts); if (rscheck("check_data", buf, NULL, e, RSF_RMCOMM|RSF_UNSTRUCTURED|RSF_COUNT, 3, NULL, e->e_id) != EX_OK) - return; + return true; + +#if MILTER && SMFI_VERSION > 3 + if (smtp->sm_milterlist && smtp->sm_milterize && + !bitset(EF_DISCARD, e->e_flags)) + { + char state; + char *response; + int savelogusrerrs = LogUsrErrs; + + response = milter_data_cmd(e, &state); + switch (state) + { + case SMFIR_REPLYCODE: + if (MilterLogLevel > 3) + { + sm_syslog(LOG_INFO, e->e_id, + "Milter: cmd=data, reject=%s", + response); + LogUsrErrs = false; + } + usrerr(response); + if (strncmp(response, "421 ", 4) == 0) + { + e->e_sendqueue = NULL; + return false; + } + return true; + + case SMFIR_REJECT: + if (MilterLogLevel > 3) + { + sm_syslog(LOG_INFO, e->e_id, + "Milter: cmd=data, reject=550 5.7.1 Command rejected"); + LogUsrErrs = false; + } + usrerr("550 5.7.1 Command rejected"); + return true; + + case SMFIR_DISCARD: + if (MilterLogLevel > 3) + sm_syslog(LOG_INFO, e->e_id, + "Milter: cmd=data, discard"); + e->e_flags |= EF_DISCARD; + break; + + case SMFIR_TEMPFAIL: + if (MilterLogLevel > 3) + { + sm_syslog(LOG_INFO, e->e_id, + "Milter: cmd=data, reject=%s", + MSG_TEMPFAIL); + LogUsrErrs = false; + } + usrerr(MSG_TEMPFAIL); + return true; + } + LogUsrErrs = savelogusrerrs; + if (response != NULL) + sm_free(response); /* XXX */ + } +#endif /* MILTER && SMFI_VERSION > 3 */ /* put back discard bit */ if (smtp->sm_discard) @@ -3049,12 +3209,6 @@ smtp_data(smtp, e) 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, true); /* redefine message size */ @@ -3128,13 +3282,86 @@ smtp_data(smtp, e) if (milteraccept && MilterLogLevel > 9) sm_syslog(LOG_INFO, e->e_id, "Milter accept: message"); } + + /* + ** If SuperSafe is SAFE_REALLY_POSTMILTER, and we don't have milter or + ** milter accepted message, sync it now + ** + ** XXX This is almost a copy of the code in collect(): put it into + ** a function that is called from both places? + */ + + if (milteraccept && SuperSafe == SAFE_REALLY_POSTMILTER) + { + int afd; + SM_FILE_T *volatile df; + char *dfname; + + df = e->e_dfp; + dfname = queuename(e, DATAFL_LETTER); + if (sm_io_setinfo(df, SM_BF_COMMIT, NULL) < 0 + && errno != EINVAL) + { + int save_errno; + + save_errno = errno; + if (save_errno == EEXIST) + { + struct stat st; + int dfd; + + if (stat(dfname, &st) < 0) + st.st_size = -1; + errno = EEXIST; + syserr("@collect: bfcommit(%s): already on disk, size=%ld", + dfname, (long) st.st_size); + dfd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL); + if (dfd >= 0) + dumpfd(dfd, true, true); + } + errno = save_errno; + dferror(df, "bfcommit", e); + flush_errors(true); + finis(save_errno != EEXIST, true, ExitStat); + } + else if ((afd = sm_io_getinfo(df, SM_IO_WHAT_FD, NULL)) < 0) + { + dferror(df, "sm_io_getinfo", e); + flush_errors(true); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + else if (fsync(afd) < 0) + { + dferror(df, "fsync", e); + flush_errors(true); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + else if (sm_io_close(df, SM_TIME_DEFAULT) < 0) + { + dferror(df, "sm_io_close", e); + flush_errors(true); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + + /* Now reopen the df file */ + e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, dfname, + SM_IO_RDONLY, NULL); + if (e->e_dfp == NULL) + { + /* we haven't acked receipt yet, so just chuck this */ + syserr("@Cannot reopen %s", dfname); + finis(true, true, ExitStat); + /* NOTREACHED */ + } + } #endif /* MILTER */ -#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) @@ -3148,9 +3375,7 @@ smtp_data(smtp, e) aborting = Errors > 0; if (!(aborting || bitset(EF_DISCARD, e->e_flags)) && -#if _FFR_QUARANTINE (QueueMode == QM_QUARANTINE || e->e_quarmsg == NULL) && -#endif /* _FFR_QUARANTINE */ !split_by_recipient(e)) aborting = bitset(EF_FATALERRS, e->e_flags); @@ -3248,14 +3473,12 @@ smtp_data(smtp, e) 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 */ @@ -3300,7 +3523,6 @@ smtp_data(smtp, e) { for (ee = e; ee != NULL; ee = ee->e_sibling) { -#if _FFR_QUARANTINE if (!doublequeue && QueueMode != QM_QUARANTINE && ee->e_quarmsg != NULL) @@ -3308,7 +3530,6 @@ smtp_data(smtp, e) dropenvelope(ee, true, false); continue; } -#endif /* _FFR_QUARANTINE */ if (WILL_BE_QUEUED(ee->e_sendmode)) dropenvelope(ee, true, false); } @@ -3326,7 +3547,6 @@ smtp_data(smtp, 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) { @@ -3339,7 +3559,7 @@ smtp_data(smtp, e) macdefine(&e->e_macro, A_PERM, macid("{quarantine}"), e->e_quarmsg); } -#endif /* _FFR_QUARANTINE */ + return true; } /* ** LOGUNDELRCPTS -- log undelivered (or all) recipients. @@ -3395,7 +3615,9 @@ logundelrcpts(e, msg, level, all) ** e -- the current envelope. ** ** Returns: -** time to wait. +** time to wait, +** STOP_ATTACK if twice as many commands as allowed and +** MaxChildren > 0. ** ** Side Effects: ** Slows down if we seem to be under attack. @@ -3404,7 +3626,7 @@ logundelrcpts(e, msg, level, all) static time_t checksmtpattack(pcounter, maxcount, waitnow, cname, e) volatile unsigned int *pcounter; - int maxcount; + unsigned int maxcount; bool waitnow; char *cname; ENVELOPE *e; @@ -3414,6 +3636,7 @@ checksmtpattack(pcounter, maxcount, waitnow, cname, e) if (++(*pcounter) >= maxcount) { + unsigned int shift; time_t s; if (*pcounter == maxcount && LogLevel > 5) @@ -3422,19 +3645,25 @@ checksmtpattack(pcounter, maxcount, waitnow, cname, e) "%s: possible SMTP attack: command=%.40s, count=%u", CurSmtpClient, cname, *pcounter); } - s = 1 << (*pcounter - maxcount); - if (s >= MAXTIMEOUT || s <= 0) + shift = *pcounter - maxcount; + s = 1 << shift; + if (shift > MAXSHIFT || s >= MAXTIMEOUT || s <= 0) s = MAXTIMEOUT; +#define IS_ATTACK(s) ((MaxChildren > 0 && *pcounter >= maxcount * 2) \ + ? STOP_ATTACK : (time_t) s) + /* sleep at least 1 second before returning */ (void) sleep(*pcounter / maxcount); s -= *pcounter / maxcount; - if (waitnow) + if (s >= MAXTIMEOUT || s < 0) + s = MAXTIMEOUT; + if (waitnow && s > 0) { (void) sleep(s); - return 0; + return IS_ATTACK(0); } - return s; + return IS_ATTACK(s); } return (time_t) 0; } @@ -3688,7 +3917,6 @@ mail_esmtp_args(kp, vp, e) bool saveQuickAbort = QuickAbort; bool saveSuprErrs = SuprErrs; bool saveExitStat = ExitStat; - char pbuf[256]; if (vp == NULL) { @@ -3713,12 +3941,9 @@ mail_esmtp_args(kp, vp, e) /* NOTREACHED */ } - /* XXX this might be cut off */ - (void) sm_strlcpy(pbuf, xuntextify(auth_param), sizeof pbuf); - /* xalloc() the buffer instead? */ - /* XXX define this always or only if trusted? */ - macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), pbuf); + macdefine(&e->e_macro, A_TEMP, macid("{auth_author}"), + auth_param); /* ** call Strust_auth to find out whether @@ -3730,14 +3955,14 @@ mail_esmtp_args(kp, vp, e) SuprErrs = true; QuickAbort = false; if (strcmp(auth_param, "<>") != 0 && - (rscheck("trust_auth", pbuf, NULL, e, RSF_RMCOMM, + (rscheck("trust_auth", auth_param, NULL, e, RSF_RMCOMM, 9, NULL, NOQID) != EX_OK || Errors > 0)) { if (tTd(95, 8)) { q = e->e_auth_param; sm_dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n", - pbuf, (q == NULL) ? "" : q); + auth_param, (q == NULL) ? "" : q); } /* not trusted */ @@ -3750,7 +3975,7 @@ mail_esmtp_args(kp, vp, e) else { if (tTd(95, 8)) - sm_dprintf("auth=\"%.100s\" trusted\n", pbuf); + sm_dprintf("auth=\"%.100s\" trusted\n", auth_param); e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, auth_param); } @@ -4169,21 +4394,22 @@ static struct } srv_feat_table[] = { { 'A', SRV_OFFER_AUTH }, - { 'B', SRV_OFFER_VERB }, /* FFR; not documented in 8.12 */ - { 'D', SRV_OFFER_DSN }, /* FFR; not documented in 8.12 */ - { 'E', SRV_OFFER_ETRN }, /* FFR; not documented in 8.12 */ - { 'L', SRV_REQ_AUTH }, /* FFR; not documented in 8.12 */ + { 'B', SRV_OFFER_VERB }, + { 'C', SRV_REQ_SEC }, + { 'D', SRV_OFFER_DSN }, + { 'E', SRV_OFFER_ETRN }, + { 'L', SRV_REQ_AUTH }, #if PIPELINING # if _FFR_NO_PIPE { 'N', SRV_NO_PIPE }, # endif /* _FFR_NO_PIPE */ { 'P', SRV_OFFER_PIPE }, #endif /* PIPELINING */ - { 'R', SRV_VRFY_CLT }, /* FFR; not documented in 8.12 */ + { 'R', SRV_VRFY_CLT }, /* same as V; not documented */ { 'S', SRV_OFFER_TLS }, /* { 'T', SRV_TMP_FAIL }, */ { 'V', SRV_VRFY_CLT }, - { 'X', SRV_OFFER_EXPN }, /* FFR; not documented in 8.12 */ + { 'X', SRV_OFFER_EXPN }, /* { 'Y', SRV_OFFER_VRFY }, */ { '\0', SRV_NONE } }; |