diff options
Diffstat (limited to 'contrib/sendmail/src/srvrsmtp.c')
-rw-r--r-- | contrib/sendmail/src/srvrsmtp.c | 3206 |
1 files changed, 2949 insertions, 257 deletions
diff --git a/contrib/sendmail/src/srvrsmtp.c b/contrib/sendmail/src/srvrsmtp.c index f4ffe8c..52ea42f 100644 --- a/contrib/sendmail/src/srvrsmtp.c +++ b/contrib/sendmail/src/srvrsmtp.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998 Sendmail, Inc. All rights reserved. + * Copyright (c) 1998-2000 Sendmail, Inc. and its suppliers. + * All rights reserved. * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. * Copyright (c) 1988, 1993 * The Regents of the University of California. All rights reserved. @@ -10,19 +11,52 @@ * */ -# include "sendmail.h" -#ifndef lint -#if SMTP -static char sccsid[] = "@(#)srvrsmtp.c 8.187 (Berkeley) 10/23/1998 (with SMTP)"; -#else -static char sccsid[] = "@(#)srvrsmtp.c 8.187 (Berkeley) 10/23/1998 (without SMTP)"; -#endif -#endif /* not lint */ - -# include <errno.h> +#include <sendmail.h> +#ifndef lint # if SMTP +static char id[] = "@(#)$Id: srvrsmtp.c,v 8.471.2.2.2.40 2000/07/18 02:24:45 gshapiro Exp $ (with SMTP)"; +# else /* SMTP */ +static char id[] = "@(#)$Id: srvrsmtp.c,v 8.471.2.2.2.40 2000/07/18 02:24:45 gshapiro Exp $ (without SMTP)"; +# endif /* SMTP */ +#endif /* ! lint */ + +#if SMTP +# include "sfsasl.h" +# 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 = 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, + 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 *)); +extern ENVELOPE BlankEnvelope; /* ** SMTP -- run the SMTP protocol. @@ -42,11 +76,11 @@ static char sccsid[] = "@(#)srvrsmtp.c 8.187 (Berkeley) 10/23/1998 (without SMTP struct cmd { - char *cmdname; /* command name */ - int cmdcode; /* internal code, see below */ + char *cmd_name; /* command name */ + int cmd_code; /* internal code, see below */ }; -/* values for cmdcode */ +/* values for cmd_code */ # define CMDERROR 0 /* bad command */ # define CMDMAIL 1 /* mail -- designate sender */ # define CMDRCPT 2 /* rcpt -- designate recipient */ @@ -60,10 +94,18 @@ struct cmd # 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 */ +/* unimplemented commands from RFC 821 */ +# define CMDUNIMPL 19 /* unimplemented rfc821 commands */ /* use this to catch and log "door handle" attempts on your system */ # define CMDLOGBOGUS 23 /* bogus command that should be logged */ /* debugging-only commands, only enabled if SMTPDEBUG is defined */ @@ -87,6 +129,16 @@ static struct cmd CmdTab[] = { "verb", CMDVERB }, { "onex", CMDONEX }, { "xusr", CMDXUSR }, + { "send", CMDUNIMPL }, + { "saml", CMDUNIMPL }, + { "soml", CMDUNIMPL }, + { "turn", CMDUNIMPL }, +# if SASL + { "auth", CMDAUTH, }, +# endif /* SASL */ +# if STARTTLS + { "starttls", CMDSTLS, }, +# endif /* STARTTLS */ /* remaining commands are here only to trap and log attempts to use them */ { "showq", CMDDBGQSHOW }, { "debug", CMDDBGDEBUG }, @@ -95,25 +147,29 @@ static struct cmd CmdTab[] = { NULL, CMDERROR } }; -bool OneXact = FALSE; /* one xaction only this run */ -char *CurSmtpClient; /* who's at the other end of channel */ - -static char *skipword __P((char *volatile, char *)); +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 */ -#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 */ +/* 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, e) - char *nullserver; +smtp(nullserver, d_flags, e) + char *volatile nullserver; + BITMAP256 d_flags; register ENVELOPE *volatile e; { register char *volatile p; - register struct cmd *c; + register struct cmd *volatile c = NULL; char *cmd; auto ADDRESS *vrfyqueue; ADDRESS *a; @@ -126,6 +182,7 @@ smtp(nullserver, e) 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 */ @@ -133,25 +190,62 @@ smtp(nullserver, e) 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 */ bool ok; - volatile int lognullconnection = TRUE; + volatile bool tempfail = FALSE; +# if _FFR_MILTER + volatile bool milterize = (nullserver == NULL); +# endif /* _FFR_MILTER */ + volatile time_t wt; /* timeout after too many commands */ + volatile time_t previous; /* time after checksmtpattack() */ + volatile bool lognullconnection = TRUE; register char *q; + char *addr; + char *greetcode = "220"; QUEUE_CHAR *new; + int argno; + char *args[MAXSMTPARGS]; char inp[MAXLINE]; char cmdbuf[MAXLINE]; - extern ENVELOPE BlankEnvelope; - extern void help __P((char *)); - extern void settime __P((ENVELOPE *)); - extern bool enoughdiskspace __P((long)); - extern int runinchild __P((char *, ENVELOPE *)); - extern void checksmtpattack __P((volatile int *, int, char *, ENVELOPE *)); +# if SASL + sasl_conn_t *conn; + volatile bool sasl_ok; + volatile 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 outlen; + char *volatile auth_type; + char *mechlist; + volatile int n_mechs; + 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 + int r; + volatile bool usetls = TRUE; + volatile bool tls_active = FALSE; + bool saveQuickAbort; + bool saveSuprErrs; +# endif /* STARTTLS */ if (fileno(OutChannel) != fileno(stdout)) { /* arrange for debugging output to go to remote host */ (void) dup2(fileno(OutChannel), fileno(stdout)); } + settime(e); + (void)sm_getla(e); peerhostname = RealHostName; if (peerhostname == NULL) peerhostname = "localhost"; @@ -163,16 +257,169 @@ smtp(nullserver, e) /* check_relay may have set discard bit, save for later */ discard = bitset(EF_DISCARD, e->e_flags); - sm_setproctitle(TRUE, "server %s startup", CurSmtpClient); -#if DAEMON - if (LogLevel > 11) + sm_setproctitle(TRUE, e, "server %s startup", CurSmtpClient); + +# if SASL + sasl_ok = FALSE; /* SASL can't be used (yet) */ + n_mechs = 0; + + /* 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) { - /* log connection information */ - sm_syslog(LOG_INFO, NOQID, - "SMTP connect from %.100s (%.100s)", - CurSmtpClient, anynet_ntoa(&RealHostAddr)); + sasl_ok = TRUE; + + /* + ** SASL set properties for sasl + ** set local/remote IP + ** XXX only IPv4: Cyrus SASL doesn't support anything else + ** + ** XXX where exactly are these used/required? + ** Kerberos_v4 + */ + +# if NETINET + in = macvalue(macid("{daemon_family}", NULL), e); + if (in != NULL && strcmp(in, "inet") == 0) + { + SOCKADDR_LEN_T addrsize; + struct sockaddr_in saddr_l; + struct sockaddr_in saddr_r; + + addrsize = sizeof(struct sockaddr_in); + if (getpeername(fileno(InChannel), + (struct sockaddr *)&saddr_r, + &addrsize) == 0) + { + sasl_setprop(conn, SASL_IP_REMOTE, &saddr_r); + addrsize = sizeof(struct sockaddr_in); + if (getsockname(fileno(InChannel), + (struct sockaddr *)&saddr_l, + &addrsize) == 0) + sasl_setprop(conn, SASL_IP_LOCAL, + &saddr_l); + } + } +# endif /* NETINET */ + + authenticating = SASL_NOT_AUTH; + auth_type = NULL; + mechlist = NULL; + user = NULL; +# if 0 + define(macid("{auth_author}", NULL), NULL, &BlankEnvelope); +# 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() */ + { + ssp.max_ssf = INT_MAX; + 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 + */ + 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 */ + +# if STARTTLS +# if _FFR_TLS_O_T + saveQuickAbort = QuickAbort; + saveSuprErrs = SuprErrs; + SuprErrs = TRUE; + QuickAbort = FALSE; + if (rscheck("offer_tls", CurSmtpClient, "", e, TRUE, FALSE, 8) != EX_OK + || Errors > 0) + usetls = FALSE; + QuickAbort = saveQuickAbort; + SuprErrs = saveSuprErrs; +# endif /* _FFR_TLS_O_T */ +# endif /* STARTTLS */ + +# if _FFR_MILTER + if (milterize) + { + char state; + + /* initialize mail filter connection */ + milter_init(e, &state); + switch (state) + { + case SMFIR_REJECT: + greetcode = "554"; + nullserver = "Command rejected"; + milterize = FALSE; + break; + + case SMFIR_TEMPFAIL: + tempfail = TRUE; + milterize = FALSE; + break; + } } -#endif + + if (milterize && !bitset(EF_DISCARD, e->e_flags)) + { + char state; + + (void) milter_connect(peerhostname, RealHostAddr, + e, &state); + switch (state) + { + case SMFIR_REPLYCODE: /* REPLYCODE shouldn't happen */ + case SMFIR_REJECT: + greetcode = "554"; + nullserver = "Command rejected"; + milterize = FALSE; + break; + + case SMFIR_TEMPFAIL: + tempfail = TRUE; + milterize = FALSE; + break; + } + } +# endif /* _FFR_MILTER */ /* output the first line, inserting "ESMTP" as second word */ expand(SmtpGreeting, inp, sizeof inp, e); @@ -182,8 +429,13 @@ smtp(nullserver, e) id = strchr(inp, ' '); if (id == NULL) id = &inp[strlen(inp)]; - cmd = p == NULL ? "220 %.*s ESMTP%s" : "220-%.*s ESMTP%s"; - message(cmd, id - inp, inp, id); + if (p == NULL) + snprintf(cmdbuf, sizeof cmdbuf, + "%s %%.*s ESMTP%%s", greetcode); + else + snprintf(cmdbuf, sizeof cmdbuf, + "%s-%%.*s ESMTP%%s", greetcode); + message(cmdbuf, id - inp, inp, id); /* output remaining lines */ while ((id = p) != NULL && (p = strchr(id, '\n')) != NULL) @@ -191,13 +443,15 @@ smtp(nullserver, e) *p++ = '\0'; if (isascii(*id) && isspace(*id)) id++; - message("220-%s", id); + (void) snprintf(cmdbuf, sizeof cmdbuf, "%s-%%s", greetcode); + message(cmdbuf, id); } if (id != NULL) { if (isascii(*id) && isspace(*id)) id++; - message("220 %s", id); + (void) snprintf(cmdbuf, sizeof cmdbuf, "%s %%s", greetcode); + message(cmdbuf, id); } protocol = NULL; @@ -218,58 +472,246 @@ smtp(nullserver, e) /* setup for the read */ e->e_to = NULL; Errors = 0; + FileName = NULL; (void) fflush(stdout); /* read the input line */ SmtpPhase = "server cmd read"; - sm_setproctitle(TRUE, "server %s cmd read", CurSmtpClient); - p = sfgets(inp, sizeof inp, InChannel, TimeOuts.to_nextcommand, - SmtpPhase); + sm_setproctitle(TRUE, e, "server %s cmd read", CurSmtpClient); +# if SASL + /* + ** SMTP AUTH requires accepting any length, + ** at least for challenge/response + ** XXX + */ +# endif /* SASL */ /* handle errors */ - if (p == NULL) + if (ferror(OutChannel) || + (p = sfgets(inp, sizeof inp, InChannel, + TimeOuts.to_nextcommand, SmtpPhase)) == NULL) { + char *d; + + d = macvalue(macid("{daemon_name}", NULL), e); + if (d == NULL) + d = "stdin"; /* end of file, just die */ disconnect(1, e); - message("421 %s Lost input channel from %s", + +# if _FFR_MILTER + /* close out milter filters */ + milter_quit(e); +# endif /* _FFR_MILTER */ + + message("421 4.4.1 %s Lost input channel from %s", MyHostName, CurSmtpClient); if (LogLevel > (gotmail ? 1 : 19)) sm_syslog(LOG_NOTICE, e->e_id, - "lost input channel from %.100s", - CurSmtpClient); - if (lognullconnection && LogLevel > 5) - sm_syslog(LOG_INFO, NULL, - "Null connection from %.100s", - CurSmtpClient); - + "lost input channel from %.100s to %s after %s", + CurSmtpClient, d, + (c == NULL || c->cmd_name == NULL) ? "startup" : c->cmd_name); /* ** If have not accepted mail (DATA), do not bounce ** bad addresses back to sender. */ + if (bitset(EF_CLRQUEUE, e->e_flags)) e->e_sendqueue = NULL; - - if (InChild) - ExitStat = EX_QUIT; - finis(TRUE, ExitStat); + goto doquit; } /* clean up end of line */ fixcrlf(inp, TRUE); +# if SASL + if (authenticating == SASL_PROC_AUTH) + { +# if 0 + if (*inp == '\0') + { + authenticating = SASL_NOT_AUTH; + message("501 5.5.2 missing input"); + continue; + } +# endif /* 0 */ + if (*inp == '*' && *(inp + 1) == '\0') + { + authenticating = SASL_NOT_AUTH; + + /* rfc 2254 4. */ + message("501 5.0.0 AUTH aborted"); + continue; + } + + /* could this be shorter? XXX */ + out = xalloc(strlen(inp)); + result = sasl_decode64(inp, strlen(inp), out, &outlen); + if (result != SASL_OK) + { + authenticating = SASL_NOT_AUTH; + + /* rfc 2254 4. */ + message("501 5.5.4 cannot decode AUTH parameter %s", + inp); + continue; + } + + result = sasl_server_step(conn, out, outlen, + &out, &outlen, &errstr); + + /* get an OK if we're done */ + if (result == SASL_OK) + { + authenticated: + message("235 2.0.0 OK Authenticated"); + authenticating = SASL_IS_AUTH; + define(macid("{auth_type}", NULL), + newstr(auth_type), &BlankEnvelope); + + result = sasl_getprop(conn, SASL_USERNAME, + (void **)&user); + if (result != SASL_OK) + { + user = ""; + define(macid("{auth_authen}", NULL), + NULL, &BlankEnvelope); + } + else + { + define(macid("{auth_authen}", NULL), + newstr(user), &BlankEnvelope); + } + +# if 0 + /* get realm? */ + sasl_getprop(conn, SASL_REALM, (void **) &data); +# 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); + ssf = NULL; + } + else + { + char pbuf[8]; + + snprintf(pbuf, sizeof pbuf, "%u", *ssf); + define(macid("{auth_ssf}", NULL), + newstr(pbuf), &BlankEnvelope); + if (tTd(95, 8)) + dprintf("SASL auth_ssf: %u\n", + *ssf); + } + /* + ** 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 + */ + if (sfdcsasl(InChannel, OutChannel, + conn) == 0) + { + /* restart dialogue */ + gothello = FALSE; + OneXact = TRUE; + n_helo = 0; + } + 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) + sm_syslog(LOG_INFO, NOQID, + "SASL: connection from %.64s: mech=%.16s, id=%.64s", + CurSmtpClient, auth_type, + user); +# endif /* SFIO */ + } + else if (result == SASL_CONTINUE) + { + len = ENC64LEN(outlen); + out2 = xalloc(len); + result = sasl_encode64(out, outlen, out2, len, + (u_int *)&out2len); + if (result != SASL_OK) + { + /* correct code? XXX */ + /* 454 Temp. authentication failure */ + message("454 4.5.4 Internal error: unable to encode64"); + if (LogLevel > 5) + sm_syslog(LOG_WARNING, e->e_id, + "SASL encode64 error [%d for \"%s\"]", + result, out); + /* start over? */ + authenticating = SASL_NOT_AUTH; + } + else + { + message("334 %s", out2); + if (tTd(95, 2)) + dprintf("SASL continue: msg='%s' len=%d\n", + out2, out2len); + } + } + else + { + /* not SASL_OK or SASL_CONT */ + message("500 5.7.0 authentication failed"); + if (LogLevel > 9) + sm_syslog(LOG_WARNING, e->e_id, + "AUTH failure (%s): %s (%d)", + auth_type, + sasl_errstring(result, NULL, + NULL), + result); + authenticating = SASL_NOT_AUTH; + } + } + else + { + /* don't want to do any of this if authenticating */ +# endif /* SASL */ + /* echo command to transcript */ if (e->e_xfp != NULL) fprintf(e->e_xfp, "<<< %s\n", inp); if (LogLevel >= 15) sm_syslog(LOG_INFO, e->e_id, - "<-- %s", - inp); + "<-- %s", + inp); if (e->e_id == NULL) - sm_setproctitle(TRUE, "%s: %.80s", CurSmtpClient, inp); + sm_setproctitle(TRUE, e, "%s: %.80s", + CurSmtpClient, inp); else - sm_setproctitle(TRUE, "%s %s: %.80s", e->e_id, CurSmtpClient, inp); + sm_setproctitle(TRUE, e, "%s %s: %.80s", + qid_printname(e), + CurSmtpClient, inp); /* break off command */ for (p = inp; isascii(*p) && isspace(*p); p++) @@ -286,9 +728,9 @@ smtp(nullserver, e) p++; /* decode command */ - for (c = CmdTab; c->cmdname != NULL; c++) + for (c = CmdTab; c->cmd_name != NULL; c++) { - if (!strcasecmp(c->cmdname, cmdbuf)) + if (strcasecmp(c->cmd_name, cmdbuf) == 0) break; } @@ -302,27 +744,47 @@ smtp(nullserver, e) ** to everything. */ - if (nullserver != NULL) + if (nullserver != NULL || bitnset(D_ETRNONLY, d_flags)) { - switch (c->cmdcode) + switch (c->cmd_code) { case CMDQUIT: case CMDHELO: case CMDEHLO: case CMDNOOP: + case CMDRSET: /* process normally */ break; + case CMDETRN: + if (bitnset(D_ETRNONLY, d_flags) && + nullserver == NULL) + break; + continue; + default: if (++badcommands > MAXBADCOMMANDS) - sleep(1); - usrerr("550 %s", nullserver); + { + delay *= 2; + if (delay >= MAXTIMEOUT) + delay = MAXTIMEOUT; + (void) sleep(delay); + } + if (nullserver != NULL) + { + if (ISSMTPREPLY(nullserver)) + usrerr(nullserver); + else + usrerr("550 5.0.0 %s", nullserver); + } + else + usrerr("452 4.4.5 Insufficient disk space; try again later"); continue; } } /* non-null server */ - switch (c->cmdcode) + switch (c->cmd_code) { case CMDMAIL: case CMDEXPN: @@ -331,11 +793,343 @@ smtp(nullserver, e) lognullconnection = FALSE; } - switch (c->cmdcode) + switch (c->cmd_code) { +# if SASL + case CMDAUTH: /* sasl */ + if (!sasl_ok) + { + message("503 5.3.3 AUTH not available"); + break; + } + if (authenticating == SASL_IS_AUTH) + { + message("503 5.5.0 Already Authenticated"); + break; + } + if (gotmail) + { + message("503 5.5.0 AUTH not permitted during a mail transaction"); + break; + } + if (tempfail) + { + if (LogLevel > 9) + sm_syslog(LOG_INFO, e->e_id, + "SMTP AUTH command (%.100s) from %.100s tempfailed (due to previous checks)", + p, CurSmtpClient); + usrerr("454 4.7.1 Please try again later"); + break; + } + + ismore = FALSE; + + /* crude way to avoid crack attempts */ + (void) checksmtpattack(&n_auth, n_mechs + 1, TRUE, + "AUTH", e); + + /* make sure it's a valid string */ + for (q = p; *q != '\0' && isascii(*q); q++) + { + if (isspace(*q)) + { + *q = '\0'; + while (*++q != '\0' && + isascii(*q) && isspace(*q)) + continue; + *(q - 1) = '\0'; + ismore = (*q != '\0'); + break; + } + } + + /* check whether mechanism is available */ + if (iteminlist(p, mechlist, " ") == NULL) + { + message("503 5.3.3 AUTH mechanism %s not available", + p); + break; + } + + if (ismore) + { + /* could this be shorter? XXX */ + in = xalloc(strlen(q)); + result = sasl_decode64(q, strlen(q), in, + (u_int *)&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\"]", + result, q); + /* start over? */ + authenticating = SASL_NOT_AUTH; + in = NULL; + 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 + { + in = NULL; + inlen = 0; + } + + /* see if that auth type exists */ + result = sasl_server_start(conn, p, in, inlen, + &out, &outlen, &errstr); + + if (result != SASL_OK && result != SASL_CONTINUE) + { + message("500 5.7.0 authentication failed"); + if (LogLevel > 9) + sm_syslog(LOG_ERR, e->e_id, + "AUTH failure (%s): %s (%d)", + p, + sasl_errstring(result, NULL, + NULL), + result); + break; + } + auth_type = newstr(p); + + if (result == SASL_OK) + { + /* ugly, but same code */ + goto authenticated; + /* authenticated by the initial response */ + } + + /* len is at least 2 */ + len = ENC64LEN(outlen); + out2 = xalloc(len); + result = sasl_encode64(out, outlen, out2, len, + (u_int *)&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\"]", + result, out); + + /* start over? */ + authenticating = SASL_NOT_AUTH; + } + else + { + message("334 %s", out2); + authenticating = SASL_PROC_AUTH; + } + + break; +# endif /* SASL */ + +# if STARTTLS + case CMDSTLS: /* starttls */ + if (*p != '\0') + { + message("501 5.5.2 Syntax error (no parameters allowed)"); + break; + } + if (!usetls) + { + message("503 5.5.0 TLS not available"); + break; + } + if (!tls_ok) + { + message("454 4.3.3 TLS not available after start"); + break; + } + if (gotmail) + { + message("503 5.5.0 TLS not permitted during a mail transaction"); + break; + } + if (tempfail) + { + if (LogLevel > 9) + sm_syslog(LOG_INFO, e->e_id, + "SMTP STARTTLS command (%.100s) from %.100s tempfailed (due to previous checks)", + p, CurSmtpClient); + usrerr("454 4.7.1 Please try again later"); + break; + } +# 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 (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"); + break; + } + if (SSL_set_rfd(srv_ssl, fileno(InChannel)) <= 0 || + SSL_set_wfd(srv_ssl, fileno(OutChannel)) <= 0) + { + message("454 4.3.3 TLS not available: error set fd"); + SSL_free(srv_ssl); + srv_ssl = NULL; + break; + } + message("220 2.0.0 Ready to start TLS"); + SSL_set_accept_state(srv_ssl); + +# define SSL_ACC(s) SSL_accept(s) + if ((r = SSL_ACC(srv_ssl)) <= 0) + { + int i; + + /* what to do in this case? */ + i = SSL_get_error(srv_ssl, r); + if (LogLevel > 5) + { + sm_syslog(LOG_WARNING, e->e_id, + "TLS: error: accept failed=%d (%d)", + r, i); + if (LogLevel > 9) + tlslogerr(); + } + tls_ok = FALSE; + SSL_free(srv_ssl); + srv_ssl = NULL; + + /* + ** according to the next draft of + ** RFC 2487 the connection should be dropped + */ + + /* arrange to ignore any current send list */ + e->e_sendqueue = NULL; + goto doquit; + } + + /* ignore return code for now, it's in {verify} */ + (void) tls_get_info(srv_ssl, &BlankEnvelope, TRUE, + CurSmtpClient); + + /* + ** call Stls_client to find out whether + ** to accept the connection from the client + */ + + saveQuickAbort = QuickAbort; + saveSuprErrs = SuprErrs; + SuprErrs = TRUE; + QuickAbort = FALSE; + if (rscheck("tls_client", + macvalue(macid("{verify}", NULL), e), + "STARTTLS", e, TRUE, TRUE, 6) != EX_OK || + Errors > 0) + { + extern char MsgBuf[]; + + if (MsgBuf[0] != '\0' && ISSMTPREPLY(MsgBuf)) + nullserver = newstr(MsgBuf); + else + nullserver = "503 5.7.0 Authentication required."; + } + QuickAbort = saveQuickAbort; + SuprErrs = saveSuprErrs; + + tls_ok = FALSE; /* don't offer STARTTLS again */ + gothello = FALSE; /* discard info */ + n_helo = 0; + OneXact = TRUE; /* only one xaction this run */ +# if SASL + if (sasl_ok) + { + char *s; + + if ((s = macvalue(macid("{cipher_bits}", NULL), e)) != NULL && + (ext_ssf.ssf = atoi(s)) > 0) + { +# if _FFR_EXT_MECH + ext_ssf.auth_id = macvalue(macid("{cert_subject}", + NULL), + e); +# endif /* _FFR_EXT_MECH */ + sasl_ok = sasl_setprop(conn, SASL_SSF_EXTERNAL, + &ext_ssf) == SASL_OK; + if (mechlist != NULL) + free(mechlist); + mechlist = NULL; + if (sasl_ok) + { + n_mechs = saslmechs(conn, + &mechlist); + sasl_ok = n_mechs > 0; + } + } + } +# 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; + else + { + /* + ** XXX this is an internal error + ** how to deal with it? + ** we can't generate an error message + ** since the other side switched to an + ** encrypted layer, but we could not... + ** just "hang up"? + */ + nullserver = "454 4.3.3 TLS not available: can't switch to encrypted layer"; + syserr("TLS: can't switch to encrypted layer"); + } + break; +# endif /* STARTTLS */ + case CMDHELO: /* hello -- introduce yourself */ case CMDEHLO: /* extended hello */ - if (c->cmdcode == CMDEHLO) + if (c->cmd_code == CMDEHLO) { protocol = "ESMTP"; SmtpPhase = "server EHLO"; @@ -347,7 +1141,8 @@ smtp(nullserver, e) } /* avoid denial-of-service */ - checksmtpattack(&n_helo, MAXHELOCOMMANDS, "HELO/EHLO", e); + (void) checksmtpattack(&n_helo, MAXHELOCOMMANDS, TRUE, + "HELO/EHLO", e); /* check for duplicate HELO/EHLO per RFC 1651 4.2 */ if (gothello) @@ -369,6 +1164,10 @@ smtp(nullserver, e) if (strlen(p) > MAXNAME) { usrerr("501 Invalid domain name"); + if (LogLevel > 9) + sm_syslog(LOG_INFO, CurEnv->e_id, + "invalid domain name (too long) from %.100s", + CurSmtpClient); break; } @@ -386,6 +1185,7 @@ smtp(nullserver, e) if (strchr("[].-_#", *q) == NULL) break; } + if (*q == '\0') { q = "pleased to meet you"; @@ -394,6 +1194,10 @@ smtp(nullserver, e) else if (!AllowBogusHELO) { usrerr("501 Invalid domain name"); + if (LogLevel > 9) + sm_syslog(LOG_INFO, CurEnv->e_id, + "invalid domain name (%.100s) from %.100s", + p, CurSmtpClient); break; } else @@ -402,9 +1206,36 @@ smtp(nullserver, e) } gothello = TRUE; - + +# if _FFR_MILTER + if (milterize && !bitset(EF_DISCARD, e->e_flags)) + { + char state; + char *response; + + response = milter_helo(p, e, &state); + switch (state) + { + case SMFIR_REPLYCODE: + nullserver = response; + milterize = FALSE; + break; + + case SMFIR_REJECT: + nullserver = "Command rejected"; + milterize = FALSE; + break; + + case SMFIR_TEMPFAIL: + tempfail = TRUE; + milterize = FALSE; + break; + } + } +# endif /* _FFR_MILTER */ + /* print HELO response message */ - if (c->cmdcode != CMDEHLO || nullserver != NULL) + if (c->cmd_code != CMDEHLO) { message("250 %s Hello %s, %s", MyHostName, CurSmtpClient, q); @@ -414,28 +1245,47 @@ smtp(nullserver, e) message("250-%s Hello %s, %s", MyHostName, CurSmtpClient, q); + /* offer ENHSC even for nullserver */ + if (nullserver != NULL) + { + message("250 ENHANCEDSTATUSCODES"); + break; + } + /* print EHLO features list */ + message("250-ENHANCEDSTATUSCODES"); if (!bitset(PRIV_NOEXPN, PrivacyFlags)) { message("250-EXPN"); if (!bitset(PRIV_NOVERB, PrivacyFlags)) message("250-VERB"); } -#if MIME8TO7 +# if MIME8TO7 message("250-8BITMIME"); -#endif +# endif /* MIME8TO7 */ if (MaxMessageSize > 0) message("250-SIZE %ld", MaxMessageSize); else message("250-SIZE"); -#if DSN - if (SendMIMEErrors) +# if DSN + if (SendMIMEErrors && + !bitset(PRIV_NORECEIPTS, PrivacyFlags)) message("250-DSN"); -#endif +# endif /* DSN */ message("250-ONEX"); - if (!bitset(PRIV_NOETRN, PrivacyFlags)) + if (!bitset(PRIV_NOETRN, PrivacyFlags) && + !bitnset(D_NOETRN, d_flags)) message("250-ETRN"); message("250-XUSR"); + +# if SASL + if (sasl_ok && mechlist != NULL && *mechlist != '\0') + message("250-AUTH %s", mechlist); +# endif /* SASL */ +# if STARTTLS + if (tls_ok && usetls) + message("250-STARTTLS"); +# endif /* STARTTLS */ message("250 HELP"); break; @@ -445,32 +1295,56 @@ smtp(nullserver, e) /* check for validity of this command */ if (!gothello && bitset(PRIV_NEEDMAILHELO, PrivacyFlags)) { - usrerr("503 Polite people say HELO first"); + usrerr("503 5.0.0 Polite people say HELO first"); break; } if (gotmail) { - usrerr("503 Sender already specified"); + usrerr("503 5.5.0 Sender already specified"); break; } if (InChild) { errno = 0; - syserr("503 Nested MAIL command: MAIL %s", p); + syserr("503 5.5.0 Nested MAIL command: MAIL %s", p); finis(TRUE, ExitStat); } +# if SASL + if (bitnset(D_AUTHREQ, d_flags) && + authenticating != SASL_IS_AUTH) + { + usrerr("530 5.7.0 Authentication required"); + break; + } +# endif /* SASL */ + + p = skipword(p, "from"); + if (p == NULL) + break; + if (tempfail) + { + if (LogLevel > 9) + sm_syslog(LOG_INFO, e->e_id, + "SMTP MAIL command (%.100s) from %.100s tempfailed (due to previous checks)", + p, CurSmtpClient); + usrerr("451 4.7.1 Please try again later"); + break; + } /* make sure we know who the sending host is */ if (sendinghost == NULL) sendinghost = peerhostname; - p = skipword(p, "from"); - if (p == NULL) - break; /* fork a subprocess to process this command */ - if (runinchild("SMTP-MAIL", e) > 0) + 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 (Errors > 0) goto undo_subproc_no_pm; if (!gothello) @@ -479,7 +1353,7 @@ smtp(nullserver, e) "%s didn't use HELO protocol", CurSmtpClient); } -#ifdef PICKY_HELO_CHECK +# ifdef PICKY_HELO_CHECK if (strcasecmp(sendinghost, peerhostname) != 0 && (strcasecmp(peerhostname, "localhost") != 0 || strcasecmp(sendinghost, MyHostName) != 0)) @@ -487,18 +1361,21 @@ smtp(nullserver, e) auth_warning(e, "Host %s claimed to be %s", CurSmtpClient, sendinghost); } -#endif +# endif /* PICKY_HELO_CHECK */ if (protocol == NULL) protocol = "SMTP"; define('r', protocol, e); define('s', sendinghost, e); - initsys(e); + if (Errors > 0) goto undo_subproc_no_pm; nrcpts = 0; - e->e_flags |= EF_LOGSENDER|EF_CLRQUEUE; - sm_setproctitle(TRUE, "%s %s: %.80s", e->e_id, CurSmtpClient, inp); + define(macid("{ntries}", NULL), "0", e); + e->e_flags |= EF_CLRQUEUE; + sm_setproctitle(TRUE, e, "%s %s: %.80s", + qid_printname(e), + CurSmtpClient, inp); /* child -- go do the processing */ if (setjmp(TopFrame) > 0) @@ -512,6 +1389,12 @@ smtp(nullserver, e) 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; @@ -526,16 +1409,37 @@ smtp(nullserver, e) if (Errors > 0) goto undo_subproc_no_pm; - /* do config file checking of the sender */ - if (rscheck("check_mail", p, NULL, e) != EX_OK || - Errors > 0) - goto undo_subproc_no_pm; + /* 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); + else + define(macid("{mail_mailer}", NULL), + NULL, e); + if (e->e_from.q_host != NULL) + define(macid("{mail_host}", NULL), + e->e_from.q_host, e); + else + define(macid("{mail_host}", NULL), + "localhost", e); + if (e->e_from.q_user != NULL) + define(macid("{mail_addr}", NULL), + e->e_from.q_user, e); + else + define(macid("{mail_addr}", NULL), + NULL, e); + if (Errors > 0) + goto undo_subproc_no_pm; /* check for possible spoofing */ if (RealUid != 0 && OpMode == MD_SMTP && !wordinclass(RealUserName, 't') && - !bitnset(M_LOCALMAILER, e->e_from.q_mailer->m_flags) && - strcmp(e->e_from.q_user, RealUserName) != 0) + (!bitnset(M_LOCALMAILER, + e->e_from.q_mailer->m_flags) || + strcmp(e->e_from.q_user, RealUserName) != 0)) { auth_warning(e, "%s owned process doing -bs", RealUserName); @@ -543,12 +1447,15 @@ smtp(nullserver, e) /* now parse ESMTP arguments */ e->e_msgsize = 0; + addr = p; + argno = 0; + args[argno++] = p; p = delimptr; while (p != NULL && *p != '\0') { char *kp; char *vp = NULL; - extern void mail_esmtp_args __P((char *, char *, ENVELOPE *)); + char *equal = NULL; /* locate the beginning of the keyword */ while (isascii(*p) && isspace(*p)) @@ -562,6 +1469,7 @@ smtp(nullserver, e) p++; if (*p == '=') { + equal = p; *p++ = '\0'; vp = p; @@ -576,44 +1484,90 @@ smtp(nullserver, e) *p++ = '\0'; if (tTd(19, 1)) - printf("MAIL: got arg %s=\"%s\"\n", kp, + dprintf("MAIL: got arg %s=\"%s\"\n", kp, vp == NULL ? "<null>" : vp); mail_esmtp_args(kp, vp, e); + if (equal != NULL) + *equal = '='; + args[argno++] = kp; + if (argno >= MAXSMTPARGS - 1) + usrerr("501 5.5.4 Too many parameters"); if (Errors > 0) goto undo_subproc_no_pm; } + args[argno] = NULL; if (Errors > 0) goto undo_subproc_no_pm; + /* do config file checking of the sender */ + if (rscheck("check_mail", addr, + NULL, e, TRUE, TRUE, 4) != EX_OK || + Errors > 0) + goto undo_subproc_no_pm; + if (MaxMessageSize > 0 && e->e_msgsize > MaxMessageSize) { - usrerr("552 Message size exceeds fixed maximum message size (%ld)", + usrerr("552 5.2.3 Message size exceeds fixed maximum message size (%ld)", MaxMessageSize); goto undo_subproc_no_pm; } - - if (!enoughdiskspace(e->e_msgsize)) + + if (!enoughdiskspace(e->e_msgsize, TRUE)) { - usrerr("452 Insufficient disk space; try again later"); + usrerr("452 4.4.5 Insufficient disk space; try again later"); goto undo_subproc_no_pm; } if (Errors > 0) goto undo_subproc_no_pm; - message("250 Sender ok"); + +# if _FFR_MILTER + LogUsrErrs = TRUE; + if (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 Try again later"); + break; + } + if (response != NULL) + free(response); + } +# endif /* _FFR_MILTER */ + if (Errors > 0) + goto undo_subproc_no_pm; + + message("250 2.1.0 Sender ok"); gotmail = TRUE; break; case CMDRCPT: /* rcpt -- designate recipient */ if (!gotmail) { - usrerr("503 Need MAIL before RCPT"); + usrerr("503 5.0.0 Need MAIL before RCPT"); break; } SmtpPhase = "server RCPT"; if (setjmp(TopFrame) > 0) { - e->e_flags &= ~EF_FATALERRS; + e->e_flags &= ~(EF_FATALERRS|EF_PM_NOTIFY); break; } QuickAbort = TRUE; @@ -622,40 +1576,80 @@ smtp(nullserver, e) /* limit flooding of our machine */ if (MaxRcptPerMsg > 0 && nrcpts >= MaxRcptPerMsg) { - usrerr("452 Too many recipients"); + usrerr("452 4.5.3 Too many recipients"); break; } if (e->e_sendmode != SM_DELIVER) e->e_flags |= EF_VRFYONLY; +# if _FFR_MILTER + /* + ** If the filter will be deleting recipients, + ** don't expand them at RCPT time (in the call + ** to recipient()). If they are expanded, it + ** is impossible for removefromlist() to figure + ** out the expanded members of the original + ** recipient and mark them as QS_DONTSEND. + */ + + if (milter_can_delrcpts()) + e->e_flags |= EF_VRFYONLY; +# endif /* _FFR_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 */ if (Errors > 0) break; if (a == NULL) { - usrerr("501 Missing recipient"); + usrerr("501 5.0.0 Missing recipient"); break; } if (delimptr != NULL && *delimptr != '\0') *delimptr++ = '\0'; - /* do config file checking of the recipient */ - if (rscheck("check_rcpt", p, NULL, e) != EX_OK || - Errors > 0) + /* put resulting triple from parseaddr() into macros */ + if (a->q_mailer != NULL) + define(macid("{rcpt_mailer}", NULL), + a->q_mailer->m_name, e); + else + define(macid("{rcpt_mailer}", NULL), + NULL, e); + if (a->q_host != NULL) + define(macid("{rcpt_host}", NULL), + a->q_host, e); + else + define(macid("{rcpt_host}", NULL), + "localhost", e); + if (a->q_user != NULL) + define(macid("{rcpt_addr}", NULL), + a->q_user, e); + else + define(macid("{rcpt_addr}", NULL), + NULL, e); + if (Errors > 0) break; /* now parse ESMTP arguments */ + addr = p; + argno = 0; + args[argno++] = p; p = delimptr; while (p != NULL && *p != '\0') { char *kp; char *vp = NULL; - extern void rcpt_esmtp_args __P((ADDRESS *, char *, char *, ENVELOPE *)); + char *equal = NULL; /* locate the beginning of the keyword */ while (isascii(*p) && isspace(*p)) @@ -669,6 +1663,7 @@ smtp(nullserver, e) p++; if (*p == '=') { + equal = p; *p++ = '\0'; vp = p; @@ -683,13 +1678,62 @@ smtp(nullserver, e) *p++ = '\0'; if (tTd(19, 1)) - printf("RCPT: got arg %s=\"%s\"\n", kp, + dprintf("RCPT: got arg %s=\"%s\"\n", kp, vp == NULL ? "<null>" : vp); rcpt_esmtp_args(a, kp, vp, e); + if (equal != NULL) + *equal = '='; + args[argno++] = kp; + if (argno >= MAXSMTPARGS - 1) + usrerr("501 5.5.4 Too many parameters"); if (Errors > 0) break; } + args[argno] = NULL; + if (Errors > 0) + break; + + /* do config file checking of the recipient */ + if (rscheck("check_rcpt", addr, + NULL, e, TRUE, TRUE, 4) != EX_OK || + Errors > 0) + break; + +# if _FFR_MILTER + if (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 Try again later"); + break; + } + if (response != NULL) + 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); if (Errors > 0) break; @@ -700,17 +1744,19 @@ smtp(nullserver, e) /* no errors during parsing, but might be a duplicate */ e->e_to = a->q_paddr; - if (!bitset(QBADADDR, a->q_flags)) + if (!QS_IS_BADADDR(a->q_state)) { - message("250 Recipient ok%s", - bitset(QQUEUEUP, a->q_flags) ? + if (e->e_queuedir == NOQDIR) + initsys(e); + message("250 2.1.5 Recipient ok%s", + QS_IS_QUEUEUP(a->q_state) ? " (will queue)" : ""); nrcpts++; } else { /* punt -- should keep message in ADDRESS.... */ - usrerr("550 Addressee unknown"); + usrerr("550 5.1.1 Addressee unknown"); } break; @@ -718,12 +1764,12 @@ smtp(nullserver, e) SmtpPhase = "server DATA"; if (!gotmail) { - usrerr("503 Need MAIL command"); + usrerr("503 5.0.0 Need MAIL command"); break; } else if (nrcpts <= 0) { - usrerr("503 Need RCPT (recipient)"); + usrerr("503 5.0.0 Need RCPT (recipient)"); break; } @@ -732,21 +1778,20 @@ smtp(nullserver, e) e->e_flags |= EF_DISCARD; /* check to see if we need to re-expand aliases */ - /* also reset QBADADDR on already-diagnosted addrs */ + /* also reset QS_BADADDR on already-diagnosted addrs */ doublequeue = FALSE; for (a = e->e_sendqueue; a != NULL; a = a->q_next) { - if (bitset(QVERIFIED, a->q_flags) && + if (QS_IS_VERIFIED(a->q_state) && !bitset(EF_DISCARD, e->e_flags)) { /* need to re-expand aliases */ doublequeue = TRUE; } - if (bitset(QBADADDR, a->q_flags)) + if (QS_IS_BADADDR(a->q_state)) { /* make this "go away" */ - a->q_flags |= QDONTSEND; - a->q_flags &= ~QBADADDR; + a->q_state = QS_DONTSEND; } } @@ -754,8 +1799,71 @@ smtp(nullserver, e) 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 Try again later"); + break; + } + if (response != NULL) + 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) + 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; @@ -787,9 +1895,19 @@ smtp(nullserver, e) */ SmtpPhase = "delivery"; - e->e_xfp = freopen(queuename(e, 'x'), "w", e->e_xfp); + (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 */ @@ -798,28 +1916,43 @@ smtp(nullserver, e) 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 %s Message accepted for delivery", id); + 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 = getla(); + 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(id, TRUE, TRUE, 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); @@ -832,14 +1965,24 @@ smtp(nullserver, 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 Test failure"); + message("451 4.0.0 Test failure"); else - message("250 Reset state"); + 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); @@ -852,28 +1995,39 @@ smtp(nullserver, e) case CMDVRFY: /* vrfy -- verify address */ case CMDEXPN: /* expn -- expand address */ - checksmtpattack(&nverifies, MAXVRFYCOMMANDS, - c->cmdcode == CMDVRFY ? "VRFY" : "EXPN", e); - vrfy = c->cmdcode == CMDVRFY; + 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", + p, CurSmtpClient); + usrerr("550 5.7.1 Please try again later"); + break; + } + wt = checksmtpattack(&nverifies, MAXVRFYCOMMANDS, FALSE, + c->cmd_code == CMDVRFY ? "VRFY" : "EXPN", e); + previous = curtime(); + vrfy = c->cmd_code == CMDVRFY; if (bitset(vrfy ? PRIV_NOVRFY : PRIV_NOEXPN, PrivacyFlags)) { if (vrfy) - message("252 Cannot VRFY user; try RCPT to attempt delivery (or try finger)"); + message("252 2.5.2 Cannot VRFY user; try RCPT to attempt delivery (or try finger)"); else - message("502 Sorry, we do not allow this operation"); + message("502 5.7.0 Sorry, we do not allow this operation"); if (LogLevel > 5) sm_syslog(LOG_INFO, e->e_id, - "%.100s: %s [rejected]", - CurSmtpClient, - shortenstring(inp, MAXSHORTSTR)); + "%.100s: %s [rejected]", + CurSmtpClient, + shortenstring(inp, MAXSHORTSTR)); break; } else if (!gothello && bitset(vrfy ? PRIV_NEEDVRFYHELO : PRIV_NEEDEXPNHELO, PrivacyFlags)) { - usrerr("503 I demand that you introduce yourself first"); + usrerr("503 5.0.0 I demand that you introduce yourself first"); break; } if (runinchild(vrfy ? "SMTP-VRFY" : "SMTP-EXPN", e) > 0) @@ -882,9 +2036,9 @@ smtp(nullserver, e) goto undo_subproc; if (LogLevel > 5) sm_syslog(LOG_INFO, e->e_id, - "%.100s: %s", - CurSmtpClient, - shortenstring(inp, MAXSHORTSTR)); + "%.100s: %s", + CurSmtpClient, + shortenstring(inp, MAXSHORTSTR)); if (setjmp(TopFrame) > 0) goto undo_subproc; QuickAbort = TRUE; @@ -895,133 +2049,199 @@ smtp(nullserver, e) p++; if (*p == '\0') { - usrerr("501 Argument required"); + usrerr("501 5.5.2 Argument required"); } else { + /* do config file checking of the address */ + if (rscheck(vrfy ? "check_vrfy" : "check_expn", + p, NULL, e, TRUE, FALSE, 4) + != EX_OK || Errors > 0) + goto undo_subproc; (void) sendtolist(p, NULLADDR, &vrfyqueue, 0, e); } + if (wt > 0) + (void) sleep(wt - (curtime() - previous)); if (Errors > 0) goto undo_subproc; if (vrfyqueue == NULL) { - usrerr("554 Nothing to %s", vrfy ? "VRFY" : "EXPN"); + usrerr("554 5.5.2 Nothing to %s", vrfy ? "VRFY" : "EXPN"); } while (vrfyqueue != NULL) { - extern void printvrfyaddr __P((ADDRESS *, bool, bool)); + if (!QS_IS_UNDELIVERED(vrfyqueue->q_state)) + { + vrfyqueue = vrfyqueue->q_next; + continue; + } + /* see if there is more in the vrfy list */ a = vrfyqueue; while ((a = a->q_next) != NULL && - bitset(QDONTSEND|QBADADDR, a->q_flags)) + (!QS_IS_UNDELIVERED(vrfyqueue->q_state))) continue; - if (!bitset(QDONTSEND|QBADADDR, vrfyqueue->q_flags)) - printvrfyaddr(vrfyqueue, a == NULL, vrfy); - vrfyqueue = vrfyqueue->q_next; + printvrfyaddr(vrfyqueue, a == NULL, vrfy); + vrfyqueue = a; } if (InChild) finis(TRUE, ExitStat); break; case CMDETRN: /* etrn -- force queue flush */ - if (bitset(PRIV_NOETRN, PrivacyFlags)) + if (bitset(PRIV_NOETRN, PrivacyFlags) || + bitnset(D_NOETRN, d_flags)) { - message("502 Sorry, we do not allow this operation"); + /* different message for MSA ? */ + message("502 5.7.0 Sorry, we do not allow this operation"); if (LogLevel > 5) sm_syslog(LOG_INFO, e->e_id, - "%.100s: %s [rejected]", - CurSmtpClient, - shortenstring(inp, MAXSHORTSTR)); + "%.100s: %s [rejected]", + CurSmtpClient, + shortenstring(inp, MAXSHORTSTR)); + break; + } + if (tempfail) + { + if (LogLevel > 9) + 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"); break; } if (strlen(p) <= 0) { - usrerr("500 Parameter required"); + usrerr("500 5.5.2 Parameter required"); break; } /* crude way to avoid denial-of-service attacks */ - checksmtpattack(&n_etrn, MAXETRNCOMMANDS, "ETRN", e); + (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) + != EX_OK || Errors > 0) + break; if (LogLevel > 5) sm_syslog(LOG_INFO, e->e_id, - "%.100s: ETRN %s", - CurSmtpClient, - shortenstring(p, MAXSHORTSTR)); + "%.100s: ETRN %s", + CurSmtpClient, + shortenstring(p, MAXSHORTSTR)); id = p; if (*id == '@') id++; else *--id = '@'; - + if ((new = (QUEUE_CHAR *)malloc(sizeof(QUEUE_CHAR))) == NULL) { - syserr("500 ETRN out of memory"); + syserr("500 5.5.0 ETRN out of memory"); break; } new->queue_match = id; new->queue_next = NULL; QueueLimitRecipient = new; - ok = runqueue(TRUE, TRUE); + ok = runqueue(TRUE, FALSE); free(QueueLimitRecipient); QueueLimitRecipient = NULL; if (ok && Errors == 0) - message("250 Queuing for node %s started", p); + message("250 2.0.0 Queuing for node %s started", p); break; case CMDHELP: /* help -- give user info */ - help(p); + help(p, e); break; case CMDNOOP: /* noop -- do nothing */ - checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "NOOP", e); - message("250 OK"); + (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, + "NOOP", e); + message("250 2.0.0 OK"); break; case CMDQUIT: /* quit -- leave mail */ - message("221 %s closing connection", MyHostName); + message("221 2.0.0 %s closing connection", MyHostName); -doquit: /* arrange to ignore any current send list */ e->e_sendqueue = NULL; +# if STARTTLS + /* shutdown TLS connection */ + if (tls_active) + { + (void) endtls(srv_ssl, "server"); + tls_active = FALSE; + } +# endif /* STARTTLS */ +# if SASL + if (authenticating == SASL_IS_AUTH) + { + sasl_dispose(&conn); + authenticating = SASL_NOT_AUTH; + } +# endif /* SASL */ + +doquit: /* avoid future 050 messages */ disconnect(1, e); +# if _FFR_MILTER + /* close out milter filters */ + milter_quit(e); +# endif /* _FFR_MILTER */ + if (InChild) ExitStat = EX_QUIT; + + if (LogLevel > 4 && bitset(EF_LOGSENDER, e->e_flags)) + logsender(e, NULL); + e->e_flags &= ~EF_LOGSENDER; + if (lognullconnection && LogLevel > 5) + { + char *d; + + d = macvalue(macid("{daemon_name}", NULL), e); + if (d == NULL) + d = "stdin"; sm_syslog(LOG_INFO, NULL, - "Null connection from %.100s", - CurSmtpClient); + "%.100s did not issue MAIL/EXPN/VRFY/ETRN during connection to %s", + CurSmtpClient, d); + } finis(TRUE, ExitStat); + /* NOTREACHED */ case CMDVERB: /* set verbose mode */ if (bitset(PRIV_NOEXPN, PrivacyFlags) || bitset(PRIV_NOVERB, PrivacyFlags)) { /* this would give out the same info */ - message("502 Verbose unavailable"); + message("502 5.7.0 Verbose unavailable"); break; } - checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "VERB", e); + (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, + "VERB", e); Verbose = 1; - e->e_sendmode = SM_DELIVER; - message("250 Verbose mode"); + set_delivery_mode(SM_DELIVER, e); + message("250 2.0.0 Verbose mode"); break; case CMDONEX: /* doing one transaction only */ - checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "ONEX", e); + (void) checksmtpattack(&n_noop, MAXNOOPCOMMANDS, TRUE, + "ONEX", e); OneXact = TRUE; - message("250 Only one transaction"); + message("250 2.0.0 Only one transaction"); break; case CMDXUSR: /* initial (user) submission */ - checksmtpattack(&n_noop, MAXNOOPCOMMANDS, "XUSR", e); - UserSubmission = TRUE; - message("250 Initial 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 @@ -1033,39 +2253,51 @@ doquit: case CMDDBGDEBUG: /* set debug mode */ tTsetup(tTdvect, sizeof tTdvect, "0-99.1"); tTflag(p); - message("200 Debug set"); + message("200 2.0.0 Debug set"); break; -# else /* not SMTPDEBUG */ +# else /* SMTPDEBUG */ case CMDDBGQSHOW: /* show queues */ case CMDDBGDEBUG: /* set debug mode */ # endif /* SMTPDEBUG */ case CMDLOGBOGUS: /* bogus command */ if (LogLevel > 0) sm_syslog(LOG_CRIT, e->e_id, - "\"%s\" command from %.100s (%.100s)", - c->cmdname, CurSmtpClient, - anynet_ntoa(&RealHostAddr)); - /* FALL THROUGH */ + "\"%s\" command from %.100s (%.100s)", + c->cmd_name, CurSmtpClient, + anynet_ntoa(&RealHostAddr)); + /* FALLTHROUGH */ case CMDERROR: /* unknown command */ if (++badcommands > MAXBADCOMMANDS) { - message("421 %s Too many bad commands; closing connection", + message("421 4.7.0 %s Too many bad commands; closing connection", MyHostName); + + /* arrange to ignore any current send list */ + e->e_sendqueue = NULL; goto doquit; } - usrerr("500 Command unrecognized: \"%s\"", - shortenstring(inp, MAXSHORTSTR)); + usrerr("500 5.5.1 Command unrecognized: \"%s\"", + shortenstring(inp, MAXSHORTSTR)); + break; + + case CMDUNIMPL: + usrerr("502 5.5.1 Command not implemented: \"%s\"", + shortenstring(inp, MAXSHORTSTR)); break; default: errno = 0; - syserr("500 smtp: unknown code %d", c->cmdcode); + syserr("500 5.5.0 smtp: unknown code %d", c->cmd_code); break; } +# if SASL + } +# endif /* SASL */ } + } /* ** CHECKSMTPATTACK -- check for denial-of-service attack by repetition @@ -1074,6 +2306,7 @@ doquit: ** pcounter -- pointer to a counter for this command. ** maxcount -- maximum value for this counter before we ** slow down. +** waitnow -- sleep now (in this routine)? ** cname -- command name for logging. ** e -- the current envelope. ** @@ -1084,23 +2317,38 @@ doquit: ** Slows down if we seem to be under attack. */ -void -checksmtpattack(pcounter, maxcount, cname, e) +static time_t +checksmtpattack(pcounter, maxcount, waitnow, cname, e) volatile int *pcounter; int maxcount; + bool waitnow; char *cname; ENVELOPE *e; { if (++(*pcounter) >= maxcount) { + time_t s; + if (*pcounter == maxcount && LogLevel > 5) { sm_syslog(LOG_INFO, e->e_id, - "%.100s: %.40s attack?", - CurSmtpClient, cname); + "%.100s: %.40s attack?", + CurSmtpClient, cname); } - sleep(*pcounter / maxcount); + s = 1 << (*pcounter - maxcount); + if (s >= MAXTIMEOUT) + s = MAXTIMEOUT; + /* sleep at least 1 second before returning */ + (void) sleep(*pcounter / maxcount); + s -= *pcounter / maxcount; + if (waitnow) + { + (void) sleep(s); + return(0); + } + return(s); } + return((time_t) 0); } /* ** SKIPWORD -- skip a fixed word. @@ -1138,9 +2386,9 @@ skipword(p, w) if (*p != ':') { syntax: - usrerr("501 Syntax error in parameters scanning \"%s\"", + usrerr("501 5.5.2 Syntax error in parameters scanning \"%s\"", shortenstring(firstp, MAXSHORTSTR)); - return (NULL); + return NULL; } *p++ = '\0'; while (isascii(*p) && isspace(*p)) @@ -1153,7 +2401,7 @@ skipword(p, w) if (strcasecmp(q, w)) goto syntax; - return (p); + return p; } /* ** MAIL_ESMTP_ARGS -- process ESMTP arguments from MAIL line @@ -1167,7 +2415,7 @@ skipword(p, w) ** none. */ -void +static void mail_esmtp_args(kp, vp, e) char *kp; char *vp; @@ -1177,20 +2425,21 @@ mail_esmtp_args(kp, vp, e) { if (vp == NULL) { - usrerr("501 SIZE requires a value"); + usrerr("501 5.5.2 SIZE requires a value"); /* NOTREACHED */ } + define(macid("{msg_size}", NULL), newstr(vp), e); # if defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY) e->e_msgsize = strtoul(vp, (char **) NULL, 10); -# else +# else /* defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY) */ e->e_msgsize = strtol(vp, (char **) NULL, 10); -# endif +# endif /* defined(__STDC__) && !defined(BROKEN_ANSI_LIBRARY) */ } else if (strcasecmp(kp, "body") == 0) { if (vp == NULL) { - usrerr("501 BODY requires a value"); + usrerr("501 5.5.2 BODY requires a value"); /* NOTREACHED */ } else if (strcasecmp(vp, "8bitmime") == 0) @@ -1203,7 +2452,7 @@ mail_esmtp_args(kp, vp, e) } else { - usrerr("501 Unknown BODY type %s", + usrerr("501 5.5.4 Unknown BODY type %s", vp); /* NOTREACHED */ } @@ -1211,33 +2460,44 @@ mail_esmtp_args(kp, vp, e) } else if (strcasecmp(kp, "envid") == 0) { + if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) + { + usrerr("504 5.7.0 Sorry, ENVID not supported, we do not allow DSN"); + /* NOTREACHED */ + } if (vp == NULL) { - usrerr("501 ENVID requires a value"); + usrerr("501 5.5.2 ENVID requires a value"); /* NOTREACHED */ } if (!xtextok(vp)) { - usrerr("501 Syntax error in ENVID parameter value"); + usrerr("501 5.5.4 Syntax error in ENVID parameter value"); /* NOTREACHED */ } if (e->e_envid != NULL) { - usrerr("501 Duplicate ENVID parameter"); + usrerr("501 5.5.0 Duplicate ENVID parameter"); /* NOTREACHED */ } e->e_envid = newstr(vp); + define(macid("{dsn_envid}", NULL), newstr(vp), e); } else if (strcasecmp(kp, "ret") == 0) { + if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) + { + usrerr("504 5.7.0 Sorry, RET not supported, we do not allow DSN"); + /* NOTREACHED */ + } if (vp == NULL) { - usrerr("501 RET requires a value"); + usrerr("501 5.5.2 RET requires a value"); /* NOTREACHED */ } if (bitset(EF_RET_PARAM, e->e_flags)) { - usrerr("501 Duplicate RET parameter"); + usrerr("501 5.5.0 Duplicate RET parameter"); /* NOTREACHED */ } e->e_flags |= EF_RET_PARAM; @@ -1245,13 +2505,89 @@ mail_esmtp_args(kp, vp, e) e->e_flags |= EF_NO_BODY_RETN; else if (strcasecmp(vp, "full") != 0) { - usrerr("501 Bad argument \"%s\" to RET", vp); + usrerr("501 5.5.2 Bad argument \"%s\" to RET", vp); + /* NOTREACHED */ + } + define(macid("{dsn_ret}", NULL), newstr(vp), e); + } +# if SASL + else if (strcasecmp(kp, "auth") == 0) + { + int len; + char *q; + char *auth_param; /* the value of the AUTH=x */ + bool saveQuickAbort = QuickAbort; + bool saveSuprErrs = SuprErrs; + char pbuf[256]; + + if (vp == NULL) + { + usrerr("501 5.5.2 AUTH= requires a value"); + /* NOTREACHED */ + } + if (e->e_auth_param != NULL) + { + usrerr("501 5.5.0 Duplicate AUTH parameter"); /* NOTREACHED */ } + if ((q = strchr(vp, ' ')) != NULL) + len = q - vp + 1; + else + len = strlen(vp) + 1; + auth_param = xalloc(len); + (void) strlcpy(auth_param, vp, len); + if (!xtextok(auth_param)) + { + usrerr("501 5.5.4 Syntax error in AUTH parameter value"); + /* just a warning? */ + /* NOTREACHED */ + } + + /* XXX this might be cut off */ + snprintf(pbuf, sizeof pbuf, "%s", xuntextify(auth_param)); + /* xalloc() the buffer instead? */ + + /* XXX define this always or only if trusted? */ + define(macid("{auth_author}", NULL), newstr(pbuf), e); + + /* + ** call Strust_auth to find out whether + ** auth_param is acceptable (trusted) + ** we shouldn't trust it if not authenticated + ** (required by RFC, leave it to ruleset?) + */ + + SuprErrs = TRUE; + QuickAbort = FALSE; + if (strcmp(auth_param, "<>") != 0 && + (rscheck("trust_auth", pbuf, NULL, e, TRUE, FALSE, 10) + != EX_OK || Errors > 0)) + { + if (tTd(95, 8)) + { + q = e->e_auth_param; + dprintf("auth=\"%.100s\" not trusted user=\"%.100s\"\n", + pbuf, (q == NULL) ? "" : q); + } + /* not trusted */ + e->e_auth_param = newstr("<>"); + } + else + { + if (tTd(95, 8)) + dprintf("auth=\"%.100s\" trusted\n", pbuf); + e->e_auth_param = newstr(auth_param); + } + free(auth_param); + /* reset values */ + Errors = 0; + QuickAbort = saveQuickAbort; + SuprErrs = saveSuprErrs; } +# endif /* SASL */ else { - usrerr("501 %s parameter unrecognized", kp); + usrerr("501 5.5.4 %s parameter unrecognized", kp); /* NOTREACHED */ } } @@ -1268,7 +2604,7 @@ mail_esmtp_args(kp, vp, e) ** none. */ -void +static void rcpt_esmtp_args(a, kp, vp, e) ADDRESS *a; char *kp; @@ -1279,13 +2615,20 @@ rcpt_esmtp_args(a, kp, vp, e) { char *p; + if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) + { + usrerr("504 5.7.0 Sorry, NOTIFY not supported, we do not allow DSN"); + /* NOTREACHED */ + } if (vp == NULL) { - usrerr("501 NOTIFY requires a value"); + usrerr("501 5.5.2 NOTIFY requires a value"); /* NOTREACHED */ } a->q_flags &= ~(QPINGONSUCCESS|QPINGONFAILURE|QPINGONDELAY); a->q_flags |= QHASNOTIFY; + define(macid("{dsn_notify}", NULL), newstr(vp), e); + if (strcasecmp(vp, "never") == 0) return; for (p = vp; p != NULL; vp = p) @@ -1301,7 +2644,7 @@ rcpt_esmtp_args(a, kp, vp, e) a->q_flags |= QPINGONDELAY; else { - usrerr("501 Bad argument \"%s\" to NOTIFY", + usrerr("501 5.5.4 Bad argument \"%s\" to NOTIFY", vp); /* NOTREACHED */ } @@ -1309,26 +2652,31 @@ rcpt_esmtp_args(a, kp, vp, e) } else if (strcasecmp(kp, "orcpt") == 0) { + if (bitset(PRIV_NORECEIPTS, PrivacyFlags)) + { + usrerr("504 5.7.0 Sorry, ORCPT not supported, we do not allow DSN"); + /* NOTREACHED */ + } if (vp == NULL) { - usrerr("501 ORCPT requires a value"); + usrerr("501 5.5.2 ORCPT requires a value"); /* NOTREACHED */ } if (strchr(vp, ';') == NULL || !xtextok(vp)) { - usrerr("501 Syntax error in ORCPT parameter value"); + usrerr("501 5.5.4 Syntax error in ORCPT parameter value"); /* NOTREACHED */ } if (a->q_orcpt != NULL) { - usrerr("501 Duplicate ORCPT parameter"); + usrerr("501 5.5.0 Duplicate ORCPT parameter"); /* NOTREACHED */ } a->q_orcpt = newstr(vp); } else { - usrerr("501 %s parameter unrecognized", kp); + usrerr("501 5.5.4 %s parameter unrecognized", kp); /* NOTREACHED */ } } @@ -1346,36 +2694,47 @@ rcpt_esmtp_args(a, kp, vp, e) ** Side Effects: ** Prints the appropriate 250 codes. */ +#define OFFF (3 + 1 + 5 + 1) /* offset in fmt: SMTP reply + enh. code */ -void +static void printvrfyaddr(a, last, vrfy) register ADDRESS *a; bool last; bool vrfy; { - char fmtbuf[20]; + char fmtbuf[30]; if (vrfy && a->q_mailer != NULL && !bitnset(M_VRFY250, a->q_mailer->m_flags)) - strcpy(fmtbuf, "252"); + (void) strlcpy(fmtbuf, "252", sizeof fmtbuf); else - strcpy(fmtbuf, "250"); + (void) strlcpy(fmtbuf, "250", sizeof fmtbuf); fmtbuf[3] = last ? ' ' : '-'; - + (void) strlcpy(&fmtbuf[4], "2.1.5 ", sizeof fmtbuf - 4); if (a->q_fullname == NULL) { - if (strchr(a->q_user, '@') == NULL) - strcpy(&fmtbuf[4], "<%s@%s>"); + if ((a->q_mailer == NULL || + a->q_mailer->m_addrtype == NULL || + strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && + strchr(a->q_user, '@') == NULL) + (void) strlcpy(&fmtbuf[OFFF], "<%s@%s>", + sizeof fmtbuf - OFFF); else - strcpy(&fmtbuf[4], "<%s>"); + (void) strlcpy(&fmtbuf[OFFF], "<%s>", + sizeof fmtbuf - OFFF); message(fmtbuf, a->q_user, MyHostName); } else { - if (strchr(a->q_user, '@') == NULL) - strcpy(&fmtbuf[4], "%s <%s@%s>"); + if ((a->q_mailer == NULL || + a->q_mailer->m_addrtype == NULL || + strcasecmp(a->q_mailer->m_addrtype, "rfc822") == 0) && + strchr(a->q_user, '@') == NULL) + (void) strlcpy(&fmtbuf[OFFF], "%s <%s@%s>", + sizeof fmtbuf - OFFF); else - strcpy(&fmtbuf[4], "%s <%s>"); + (void) strlcpy(&fmtbuf[OFFF], "%s <%s>", + sizeof fmtbuf - OFFF); message(fmtbuf, a->q_fullname, a->q_user, MyHostName); } } @@ -1386,14 +2745,15 @@ printvrfyaddr(a, last, vrfy) ** label -- a string used in error messages ** ** Returns: -** zero in the child -** one in the parent +** RIC_INCHILD in the child +** RIC_INPARENT in the parent +** RIC_TEMPFAIL tempfail condition ** ** Side Effects: ** none. */ -int +static int runinchild(label, e) char *label; register ENVELOPE *e; @@ -1402,8 +2762,19 @@ runinchild(label, e) 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 preceeded + ** Disable child process reaping, in case ETRN has preceded ** MAIL command, and then fork. */ @@ -1412,24 +2783,28 @@ runinchild(label, e) childpid = dofork(); if (childpid < 0) { - syserr("451 %s: cannot fork", label); + syserr("451 4.3.0 %s: cannot fork", label); (void) releasesignal(SIGCHLD); - return (1); + return RIC_INPARENT; } if (childpid > 0) { auto int st; /* parent -- wait for child to complete */ - sm_setproctitle(TRUE, "server %s child wait", CurSmtpClient); + sm_setproctitle(TRUE, e, "server %s child wait", + CurSmtpClient); st = waitfor(childpid); if (st == -1) - syserr("451 %s: lost child", label); + syserr("451 4.3.0 %s: lost child", label); else if (!WIFEXITED(st)) - syserr("451 %s: died on signal %d", - label, st & 0177); + { + syserr("451 4.3.0 %s: died on signal %d", + label, st & 0177); + return RIC_TEMPFAIL; + } - /* if we exited on a QUIT command, complete the process */ + /* if exited on a QUIT command, complete the process */ if (WEXITSTATUS(st) == EX_QUIT) { disconnect(1, e); @@ -1439,27 +2814,1300 @@ runinchild(label, e) /* restore the child signal */ (void) releasesignal(SIGCHLD); - return (1); + return RIC_INPARENT; } else { /* child */ InChild = TRUE; QuickAbort = FALSE; + clearstats(); clearenvelope(e, FALSE); + assign_queueid(e); (void) setsignal(SIGCHLD, SIG_DFL); (void) releasesignal(SIGCHLD); } } - return (0); + return RIC_INCHILD; } -# endif /* SMTP */ +# if SASL + +/* +** SASLMECHS -- get list of possible AUTH mechanisms +** +** Parameters: +** conn -- SASL connection info +** mechlist -- output parameter for list of mechanisms +** +** Returns: +** number of mechs +*/ + +static int +saslmechs(conn, mechlist) + sasl_conn_t *conn; + char **mechlist; +{ + int len, num, result; + + /* "user" is currently unused */ + result = sasl_listmech(conn, "user", /* XXX */ + "", " ", "", mechlist, + (u_int *)&len, (u_int *)&num); + if (result == SASL_OK && num > 0) + { + if (LogLevel > 11) + sm_syslog(LOG_INFO, NOQID, + "SASL: available mech=%s, allowed mech=%s", + *mechlist, AuthMechanisms); + *mechlist = intersect(AuthMechanisms, *mechlist); + } + else + { + if (LogLevel > 9) + sm_syslog(LOG_WARNING, NOQID, + "SASL error: listmech=%d, num=%d", + result, num); + } + 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) +** +** Returns: +** ok? +*/ + +int +proxy_policy(context, auth_identity, requested_user, user, errstr) + void *context; + const char *auth_identity; + const char *requested_user; + const char **user; + const char **errstr; +{ + if (user == NULL || auth_identity == NULL) + return SASL_FAIL; + *user = newstr(auth_identity); + return SASL_OK; +} + +# endif /* SASL */ + +# 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: +** None. (not yet, maybe it should return success/failure?) +** +** Side Effects: +** initializes PRNG for tls library. +*/ + +#define MIN_RAND_BYTES 16 /* 128 bits */ + +void +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 */ + + bool ok; + int randdef; + + /* + ** initialize PRNG + */ + + ok = FALSE; + 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"); + } +# endif /* !HASURANDOMDEV */ +} + +/* +** 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; \ + } + +/* +** INITTLS -- initialize TLS +** +** 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 +** +** Returns: +** succeeded? +*/ + +bool +inittls(ctx, req, srv, certfile, keyfile, cacertpath, cacertfile, dhparam) + SSL_CTX **ctx; + u_long req; + bool srv; + char *certfile, *keyfile, *cacertpath, *cacertfile, *dhparam; +{ +# 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; + 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); + free(dhparam); + dhparam = NULL; + } + } + if (dhparam == NULL) + dhparam = srv ? newstr("1") : newstr("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"); + 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"); + 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 */ + + +# 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 */ + + /* + ** load private key + ** XXX change this for DSA-only version + */ + 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) + { + if (LogLevel > 7) + { + 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) + { + 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 (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) + { + sm_syslog(LOG_WARNING, NOQID, + "TLS: error: %s: %d load verify locs %s, %s", + who, r, cacertpath, cacertfile); + if (LogLevel > 9) + tlslogerr(); + } + 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) + { + sm_syslog(LOG_WARNING, NOQID, + "TLS: error: %s: SSL_CTX_set_cipher_list(%s) failed, list ignored", + who, CipherList); + + if (LogLevel > 9) + tlslogerr(); + } + /* failure if setting to this list is required? */ + } + } +# 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; +} +/* +** INITSRVTLS -- initialize server side TLS +** +** Parameters: +** none. +** +** Returns: +** succeeded? +*/ + +bool +initsrvtls() +{ + + tls_ok = inittls(&srv_ctx, TLS_I_SRV, TRUE, SrvCERTfile, Srvkeyfile, + CACERTpath, CACERTfile, DHParams); + return tls_ok; +} +/* +** 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 +** +** Returns: +** result of authentication. +** +** Side Effects: +** sets ${cipher}, ${tls_version}, ${verify}, ${cipher_bits}, +** ${cert} +*/ + +int +tls_get_info(ssl, e, srv, host) + SSL *ssl; + ENVELOPE *e; + bool srv; + char *host; +{ + 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 (LogLevel >= 14) + sm_syslog(LOG_INFO, e->e_id, + "TLS: get_verify in %s: %d get_peer: 0x%x", + srv ? "srv" : "clt", + SSL_get_verify_result(ssl), 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 (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 ** ** Returns: ** none. @@ -1467,21 +4115,28 @@ runinchild(label, e) ** Side Effects: ** outputs the help file to message output. */ +#define HELPVSTR "#vers " +#define HELPVERSION 2 void -help(topic) +help(topic, e) char *topic; + ENVELOPE *e; { register FILE *hf; + register char *p; int len; bool noinfo; - int sff = SFF_OPENASROOT|SFF_REGONLY; + bool first = TRUE; + long sff = SFF_OPENASROOT|SFF_REGONLY; char buf[MAXLINE]; + char inp[MAXLINE]; + static int foundvers = -1; extern char Version[]; if (DontLockReadFiles) sff |= SFF_NOLOCK; - if (!bitset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail)) + if (!bitnset(DBS_HELPFILEINUNSAFEDIRPATH, DontBlameSendmail)) sff |= SFF_SAFEDIRPATH; if (HelpFile == NULL || @@ -1489,14 +4144,14 @@ help(topic) { /* no help */ errno = 0; - message("502 Sendmail %s -- HELP not implemented", Version); + message("502 5.3.0 Sendmail %s -- HELP not implemented", + Version); return; } if (topic == NULL || *topic == '\0') { topic = "smtp"; - message("214-This is Sendmail version %s", Version); noinfo = FALSE; } else @@ -1509,24 +4164,61 @@ help(topic) while (fgets(buf, sizeof buf, hf) != NULL) { + if (buf[0] == '#') + { + if (foundvers < 0 && + strncmp(buf, HELPVSTR, strlen(HELPVSTR)) == 0) + { + int h; + + if (sscanf(buf + strlen(HELPVSTR), "%d", + &h) == 1) + foundvers = h; + } + continue; + } if (strncmp(buf, topic, len) == 0) { - register char *p; + if (first) + { + first = FALSE; - p = strchr(buf, '\t'); + /* print version if no/old vers# in file */ + if (foundvers < 2 && !noinfo) + message("214-2.0.0 This is Sendmail version %s", Version); + } + p = strpbrk(buf, " \t"); if (p == NULL) - p = buf; + p = buf + strlen(buf) - 1; else p++; fixcrlf(p, TRUE); - message("214-%s", p); + if (foundvers >= 2) + { + translate_dollars(p); + expand(p, inp, sizeof inp, e); + p = inp; + } + message("214-2.0.0 %s", p); noinfo = FALSE; } } if (noinfo) - message("504 HELP topic \"%.10s\" unknown", topic); + message("504 5.3.0 HELP topic \"%.10s\" unknown", topic); else - message("214 End of HELP info"); + message("214 2.0.0 End of HELP info"); + + if (foundvers != 0 && foundvers < HELPVERSION) + { + if (LogLevel > 1) + sm_syslog(LOG_WARNING, e->e_id, + "%s too old (require version %d)", + HelpFile, HELPVERSION); + + /* avoid log next time */ + foundvers = 0; + } + (void) fclose(hf); } |