diff options
Diffstat (limited to '')
-rw-r--r-- | src/smtpd/smtpd.c | 6517 |
1 files changed, 6517 insertions, 0 deletions
diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c new file mode 100644 index 0000000..bb369d2 --- /dev/null +++ b/src/smtpd/smtpd.c @@ -0,0 +1,6517 @@ +/*++ +/* NAME +/* smtpd 8 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* \fBsmtpd\fR [generic Postfix daemon options] +/* +/* \fBsendmail -bs\fR +/* DESCRIPTION +/* The SMTP server accepts network connection requests +/* and performs zero or more SMTP transactions per connection. +/* Each received message is piped through the \fBcleanup\fR(8) +/* daemon, and is placed into the \fBincoming\fR queue as one +/* single queue file. For this mode of operation, the program +/* expects to be run from the \fBmaster\fR(8) process manager. +/* +/* Alternatively, the SMTP server be can run in stand-alone +/* mode; this is traditionally obtained with "\fBsendmail +/* -bs\fR". When the SMTP server runs stand-alone with non +/* $\fBmail_owner\fR privileges, it receives mail even while +/* the mail system is not running, deposits messages directly +/* into the \fBmaildrop\fR queue, and disables the SMTP server's +/* access policies. As of Postfix version 2.3, the SMTP server +/* refuses to receive mail from the network when it runs with +/* non $\fBmail_owner\fR privileges. +/* +/* The SMTP server implements a variety of policies for connection +/* requests, and for parameters given to \fBHELO, ETRN, MAIL FROM, VRFY\fR +/* and \fBRCPT TO\fR commands. They are detailed below and in the +/* \fBmain.cf\fR configuration file. +/* SECURITY +/* .ad +/* .fi +/* The SMTP server is moderately security-sensitive. It talks to SMTP +/* clients and to DNS servers on the network. The SMTP server can be +/* run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 1123 (Host requirements) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1869 (SMTP service extensions) +/* RFC 1870 (Message size declaration) +/* RFC 1985 (ETRN command) +/* RFC 2034 (SMTP enhanced status codes) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN extension) +/* RFC 3463 (Enhanced status codes) +/* RFC 3848 (ESMTP transmission types) +/* RFC 4409 (Message submission) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7505 ("Null MX" No Service Resource Record) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, +/* policy violations, and of other trouble. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtpd\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* The following parameters work around implementation errors in other +/* software, and/or allow you to override standards in order to prevent +/* undesirable use. +/* .ad +/* .fi +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBdisable_vrfy_command (no)\fR" +/* Disable the SMTP VRFY command. +/* .IP "\fBsmtpd_noop_commands (empty)\fR" +/* List of commands that the Postfix SMTP server replies to with "250 +/* Ok", without doing any syntax checks and without changing state. +/* .IP "\fBstrict_rfc821_envelopes (no)\fR" +/* Require that addresses received in SMTP MAIL FROM and RCPT TO +/* commands are enclosed with <>, and that those addresses do +/* not contain RFC 822 style comments or phrases. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP client address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP server will not send in the EHLO response +/* to a +/* remote SMTP client. +/* .IP "\fBsmtpd_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP server will not send in the EHLO +/* response +/* to a remote SMTP client. +/* .IP "\fBsmtpd_delay_open_until_valid_rcpt (yes)\fR" +/* Postpone the start of an SMTP mail transaction until a valid +/* RCPT TO command is received. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtpd_command_filter (empty)\fR" +/* A mechanism to transform commands from remote SMTP clients. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP server DNS lookup results. +/* ADDRESS REWRITING CONTROLS +/* .ad +/* .fi +/* See the ADDRESS_REWRITING_README document for a detailed +/* discussion of Postfix address rewriting. +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR" +/* Rewrite message header addresses in mail from these clients and +/* update incomplete addresses with the domain name in $myorigin or +/* $mydomain; either don't rewrite message headers from other clients +/* at all, or rewrite message headers and update incomplete addresses +/* with the domain specified in the remote_header_rewrite_domain +/* parameter. +/* BEFORE-SMTPD PROXY AGENT +/* .ad +/* .fi +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_upstream_proxy_protocol (empty)\fR" +/* The name of the proxy protocol used by an optional before-smtpd +/* proxy agent. +/* .IP "\fBsmtpd_upstream_proxy_timeout (5s)\fR" +/* The time limit for the proxy protocol specified with the +/* smtpd_upstream_proxy_protocol parameter. +/* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 1.0, Postfix can be configured to send new mail to +/* an external content filter AFTER the mail is queued. This content +/* filter is expected to inject mail back into a (Postfix or other) +/* MTA for further delivery. See the FILTER_README document for details. +/* .IP "\fBcontent_filter (empty)\fR" +/* After the message is queued, send the entire message to the +/* specified \fItransport:destination\fR. +/* BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, the Postfix SMTP server can be configured +/* to send incoming mail to a real-time SMTP-based content filter +/* BEFORE mail is queued. This content filter is expected to inject +/* mail back into Postfix. See the SMTPD_PROXY_README document for +/* details on how to configure and operate this feature. +/* .IP "\fBsmtpd_proxy_filter (empty)\fR" +/* The hostname and TCP port of the mail filtering proxy server. +/* .IP "\fBsmtpd_proxy_ehlo ($myhostname)\fR" +/* How the Postfix SMTP server announces itself to the proxy filter. +/* .IP "\fBsmtpd_proxy_options (empty)\fR" +/* List of options that control how the Postfix SMTP server +/* communicates with a before-queue content filter. +/* .IP "\fBsmtpd_proxy_timeout (100s)\fR" +/* The time limit for connecting to a proxy filter and for sending or +/* receiving information. +/* BEFORE QUEUE MILTER CONTROLS +/* .ad +/* .fi +/* As of version 2.3, Postfix supports the Sendmail version 8 +/* Milter (mail filter) protocol. These content filters run +/* outside Postfix. They can inspect the SMTP command stream +/* and the message content, and can request modifications before +/* mail is queued. For details see the MILTER_README document. +/* .IP "\fBsmtpd_milters (empty)\fR" +/* A list of Milter (mail filter) applications for new mail that +/* arrives via the Postfix \fBsmtpd\fR(8) server. +/* .IP "\fBmilter_protocol (6)\fR" +/* The mail filter protocol version and optional protocol extensions +/* for communication with a Milter application; prior to Postfix 2.6 +/* the default protocol is 2. +/* .IP "\fBmilter_default_action (tempfail)\fR" +/* The default action when a Milter (mail filter) application is +/* unavailable or mis-configured. +/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR" +/* The {daemon_name} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR" +/* The {v} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_connect_timeout (30s)\fR" +/* The time limit for connecting to a Milter (mail filter) +/* application, and for negotiating protocol options. +/* .IP "\fBmilter_command_timeout (30s)\fR" +/* The time limit for sending an SMTP command to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_content_timeout (300s)\fR" +/* The time limit for sending message content to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after completion of an SMTP connection. +/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP HELO or EHLO command. +/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP MAIL FROM command. +/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP RCPT TO command. +/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 4 or higher Milter (mail +/* filter) applications after the SMTP DATA command. +/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 3 or higher Milter (mail +/* filter) applications after an unknown SMTP command. +/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the end of the message header. +/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the message end-of-data. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBmilter_macro_defaults (empty)\fR" +/* Optional list of \fIname=value\fR pairs that specify default +/* values for arbitrary macros that Postfix may send to Milter +/* applications. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBsmtpd_milter_maps (empty)\fR" +/* Lookup tables with Milter settings per remote SMTP client IP +/* address. +/* GENERAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both built-in +/* and external content filters. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both before-queue +/* and after-queue content filtering. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xforward_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XFORWARD feature. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* Postfix SASL support (RFC 4954) can be used to authenticate remote +/* SMTP clients to the Postfix SMTP server, and to authenticate the +/* Postfix SMTP client to a remote SMTP server. +/* See the SASL_README document for details. +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBsmtpd_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP server. +/* .IP "\fBsmtpd_sasl_local_domain (empty)\fR" +/* The name of the Postfix SMTP server's local SASL authentication +/* realm. +/* .IP "\fBsmtpd_sasl_security_options (noanonymous)\fR" +/* Postfix SMTP server SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL server implementation that is selected +/* with \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sender_login_maps (empty)\fR" +/* Optional lookup table with the SASL login names that own the sender +/* (MAIL FROM) addresses. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.1 and 2.2: +/* .IP "\fBsmtpd_sasl_application_name (smtpd)\fR" +/* The application name that the Postfix SMTP server uses for SASL +/* server initialization. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_sasl_authenticated_header (no)\fR" +/* Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received +/* message header. +/* .IP "\fBsmtpd_sasl_path (smtpd)\fR" +/* Implementation-specific information that the Postfix SMTP server +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP server should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBcyrus_sasl_config_path (empty)\fR" +/* Search path for Cyrus SASL application configuration files, +/* currently used only to locate the $smtpd_sasl_path.conf file. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtpd_sasl_service (smtp)\fR" +/* The service name that is passed to the SASL plug-in that is +/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR" +/* The maximum length of a SASL client's response to a server challenge. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be +/* found in the TLS_README document. +/* .IP "\fBsmtpd_tls_security_level (empty)\fR" +/* The SMTP TLS security level for the Postfix SMTP server; when +/* a non-empty value is specified, this overrides the obsolete parameters +/* smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBsmtpd_sasl_tls_security_options ($smtpd_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* server uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtpd_starttls_timeout (see 'postconf -d' output)\fR" +/* The time limit for Postfix SMTP server write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtpd_tls_CAfile (empty)\fR" +/* A file containing (PEM format) CA certificates of root CAs trusted +/* to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_CApath (empty)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .IP "\fBsmtpd_tls_ask_ccert (no)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBsmtpd_tls_auth_only (no)\fR" +/* When TLS encryption is optional in the Postfix SMTP server, do +/* not announce or accept SASL authentication over unencrypted +/* connections. +/* .IP "\fBsmtpd_tls_ccert_verifydepth (9)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBsmtpd_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP server RSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the SMTP server +/* cipher list at all TLS security levels. +/* .IP "\fBsmtpd_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP server DSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_dh1024_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with non-export EDH ciphers. +/* .IP "\fBsmtpd_tls_dh512_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with export-grade EDH ciphers. +/* .IP "\fBsmtpd_tls_dkey_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix SMTP server DSA private key in PEM format. +/* .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix SMTP server RSA private key in PEM format. +/* .IP "\fBsmtpd_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP server logging of TLS activity. +/* .IP "\fBsmtpd_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server will +/* use with mandatory TLS encryption. +/* .IP "\fBsmtpd_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP server cipher list at mandatory TLS security levels. +/* .IP "\fBsmtpd_tls_mandatory_protocols (!SSLv2, !SSLv3)\fR" +/* The SSL/TLS protocols accepted by the Postfix SMTP server with +/* mandatory TLS encryption. +/* .IP "\fBsmtpd_tls_received_header (no)\fR" +/* Request that the Postfix SMTP server produces Received: message +/* headers that include information about the protocol and cipher used, +/* as well as the remote SMTP client CommonName and client certificate issuer +/* CommonName. +/* .IP "\fBsmtpd_tls_req_ccert (no)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP client +/* certificate in order to allow TLS connections to proceed. +/* .IP "\fBsmtpd_tls_wrappermode (no)\fR" +/* Run the Postfix SMTP server in the non-standard "wrapper" mode, +/* instead of using the STARTTLS command. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_tls_fingerprint_digest (md5)\fR" +/* The message digest algorithm to construct remote SMTP +/* client-certificate +/* fingerprints or public key fingerprints (Postfix 2.9 and later) +/* for \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtpd_tls_protocols (!SSLv2, !SSLv3)\fR" +/* List of TLS protocols that the Postfix SMTP server will exclude +/* or include with opportunistic TLS encryption. +/* .IP "\fBsmtpd_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtpd_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP server ECDSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix SMTP server ECDSA private key in PEM format. +/* .IP "\fBsmtpd_tls_eecdh_grade (see 'postconf -d' output)\fR" +/* The Postfix SMTP server security grade for ephemeral elliptic-curve +/* Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* .PP +/* Introduced with Postfix 3.4.6, 3.3.5, 3.2.10, and 3.1.13: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shuting +/* down a TLS session, until Postfix times out. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtpd_use_tls (no)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBsmtpd_enforce_tls (no)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, +/* and require that clients use TLS encryption. +/* .IP "\fBsmtpd_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS +/* cipher list. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBstrict_smtputf8 (no)\fR" +/* Enable stricter enforcement of the SMTPUTF8 protocol. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* VERP SUPPORT CONTROLS +/* .ad +/* .fi +/* With VERP style delivery, each recipient of a message receives a +/* customized copy of the message with his/her own recipient address +/* encoded in the envelope sender address. The VERP_README file +/* describes configuration and operation details of Postfix support +/* for variable envelope return path addresses. VERP style delivery +/* is requested with the SMTP XVERP command or with the "sendmail +/* -V" command-line option and is available in Postfix version 1.1 +/* and later. +/* .IP "\fBdefault_verp_delimiters (+=)\fR" +/* The two default VERP delimiter characters. +/* .IP "\fBverp_delimiter_filter (-=+)\fR" +/* The characters Postfix accepts as VERP delimiter characters on the +/* Postfix \fBsendmail\fR(1) command line and in SMTP commands. +/* .PP +/* Available in Postfix version 1.1 and 2.0: +/* .IP "\fBauthorized_verp_clients ($mynetworks)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_verp_clients ($authorized_verp_clients)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* The DEBUG_README document describes how to debug parts of the +/* Postfix mail system. The methods vary from making the software log +/* a lot of detail, to running some daemon processes under control of +/* a call tracer or debugger. +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a remote client or +/* server matches a pattern in the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of remote client or server hostname or network +/* address patterns that cause the verbose logging level to increase +/* by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* .IP "\fBsmtpd_reject_footer (empty)\fR" +/* Optional information that is appended after each Postfix SMTP +/* server +/* 4XX or 5XX response. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xclient_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XCLIENT feature. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_log_access_permit_actions (empty)\fR" +/* Enable logging of the named "permit" actions in SMTP server +/* access lists (by default, the SMTP server logs "reject" actions but +/* not "permit" actions). +/* KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS +/* .ad +/* .fi +/* As of Postfix version 2.0, the SMTP server rejects mail for +/* unknown recipients. This prevents the mail queue from clogging up +/* with undeliverable MAILER-DAEMON messages. Additional information +/* on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README +/* documents. +/* .IP "\fBshow_user_unknown_table_name (yes)\fR" +/* Display the name of the recipient table in the "User unknown" +/* responses. +/* .IP "\fBcanonical_maps (empty)\fR" +/* Optional address mapping lookup tables for message headers and +/* envelopes. +/* .IP "\fBrecipient_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* recipient addresses. +/* .IP "\fBsender_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* sender addresses. +/* .PP +/* Parameters concerning known/unknown local recipients: +/* .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR" +/* The list of domains that are delivered via the $local_transport +/* mail delivery transport. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBinet_protocols (all)\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR" +/* Lookup tables with all names or addresses of local recipients: +/* a recipient address is local when its domain matches $mydestination, +/* $inet_interfaces or $proxy_interfaces. +/* .IP "\fBunknown_local_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is local, and $local_recipient_maps specifies a list of +/* lookup tables that does not match the recipient. +/* .PP +/* Parameters concerning known/unknown recipients of relay destinations: +/* .IP "\fBrelay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)\fR" +/* What destination domains (and subdomains thereof) this system +/* will relay mail to. +/* .IP "\fBrelay_recipient_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains +/* that match $relay_domains. +/* .IP "\fBunknown_relay_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server reply code when a recipient +/* address matches $relay_domains, and relay_recipient_maps specifies +/* a list of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual alias +/* domains: +/* .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR" +/* Postfix is final destination for the specified list of virtual +/* alias domains, that is, domains for which all addresses are aliased +/* to addresses in other local or remote domains. +/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR" +/* Optional lookup tables that alias specific mail addresses or domains +/* to other local or remote address. +/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_alias_domains, and $virtual_alias_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual mailbox +/* domains: +/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR" +/* Postfix is final destination for the specified list of domains; +/* mail is delivered via the $virtual_transport mail delivery transport. +/* .IP "\fBvirtual_mailbox_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains that +/* match $virtual_mailbox_domains. +/* .IP "\fBunknown_virtual_mailbox_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* The following parameters limit resource usage by the SMTP +/* server and/or control client request rates. +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBqueue_minfree (0)\fR" +/* The minimal amount of free space in bytes in the queue file system +/* that is needed to receive mail. +/* .IP "\fBmessage_size_limit (10240000)\fR" +/* The maximal size in bytes of a message, including envelope information. +/* .IP "\fBsmtpd_recipient_limit (1000)\fR" +/* The maximal number of recipients that the Postfix SMTP server +/* accepts per message delivery request. +/* .IP "\fBsmtpd_timeout (normal: 300s, overload: 10s)\fR" +/* The time limit for sending a Postfix SMTP server response and for +/* receiving a remote SMTP client request. +/* .IP "\fBsmtpd_history_flush_threshold (100)\fR" +/* The maximal number of lines in the Postfix SMTP server command history +/* before it is flushed upon receipt of EHLO, RSET, or end of DATA. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_peername_lookup (yes)\fR" +/* Attempt to look up the remote SMTP client hostname, and verify that +/* the name matches the client IP address. +/* .PP +/* The per SMTP client connection count and request rate limits are +/* implemented in co-operation with the \fBanvil\fR(8) service, and +/* are available in Postfix version 2.2 and later. +/* .IP "\fBsmtpd_client_connection_count_limit (50)\fR" +/* How many simultaneous connections any client is allowed to +/* make to this service. +/* .IP "\fBsmtpd_client_connection_rate_limit (0)\fR" +/* The maximal number of connection attempts any client is allowed to +/* make to this service per time unit. +/* .IP "\fBsmtpd_client_message_rate_limit (0)\fR" +/* The maximal number of message delivery requests that any client is +/* allowed to make to this service per time unit, regardless of whether +/* or not Postfix actually accepts those messages. +/* .IP "\fBsmtpd_client_recipient_rate_limit (0)\fR" +/* The maximal number of recipient addresses that any client is allowed +/* to send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those recipients. +/* .IP "\fBsmtpd_client_event_limit_exceptions ($mynetworks)\fR" +/* Clients that are excluded from smtpd_client_*_count/rate_limit +/* restrictions. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_client_new_tls_session_rate_limit (0)\fR" +/* The maximal number of new (i.e., uncached) TLS sessions that a +/* remote SMTP client is allowed to negotiate with this service per +/* time unit. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR" +/* The maximal number of AUTH commands that any client is allowed to +/* send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those commands. +/* TARPIT CONTROLS +/* .ad +/* .fi +/* When a remote SMTP client makes errors, the Postfix SMTP server +/* can insert delays before responding. This can help to slow down +/* run-away software. The behavior is controlled by an error counter +/* that counts the number of errors within an SMTP session that a +/* client makes without delivering mail. +/* .IP "\fBsmtpd_error_sleep_time (1s)\fR" +/* With Postfix version 2.1 and later: the SMTP server response delay after +/* a client has made more than $smtpd_soft_error_limit errors, and +/* fewer than $smtpd_hard_error_limit errors, without delivering mail. +/* .IP "\fBsmtpd_soft_error_limit (10)\fR" +/* The number of errors a remote SMTP client is allowed to make without +/* delivering mail before the Postfix SMTP server slows down all its +/* responses. +/* .IP "\fBsmtpd_hard_error_limit (normal: 20, overload: 1)\fR" +/* The maximal number of errors a remote SMTP client is allowed to +/* make without delivering mail. +/* .IP "\fBsmtpd_junk_command_limit (normal: 100, overload: 1)\fR" +/* The number of junk commands (NOOP, VRFY, ETRN or RSET) that a remote +/* SMTP client can send before the Postfix SMTP server starts to +/* increment the error counter with each junk command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_recipient_overshoot_limit (1000)\fR" +/* The number of recipients that a remote SMTP client can send in +/* excess of the limit specified with $smtpd_recipient_limit, before +/* the Postfix SMTP server increments the per-session error count +/* for each excess recipient. +/* ACCESS POLICY DELEGATION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, Postfix can be configured to delegate access +/* policy decisions to an external server that runs outside Postfix. +/* See the file SMTPD_POLICY_README for more information. +/* .IP "\fBsmtpd_policy_service_max_idle (300s)\fR" +/* The time after which an idle SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_max_ttl (1000s)\fR" +/* The time after which an active SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_timeout (100s)\fR" +/* The time limit for connecting to, writing to, or receiving from a +/* delegated SMTPD policy server. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_policy_service_default_action (451 4.3.5 Server configuration problem)\fR" +/* The default action when an SMTPD policy service request fails. +/* .IP "\fBsmtpd_policy_service_request_limit (0)\fR" +/* The maximal number of requests per SMTPD policy service connection, +/* or zero (no limit). +/* .IP "\fBsmtpd_policy_service_try_limit (2)\fR" +/* The maximal number of attempts to send an SMTPD policy service +/* request before giving up. +/* .IP "\fBsmtpd_policy_service_retry_delay (1s)\fR" +/* The delay between attempts to resend a failed SMTPD policy +/* service request. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_policy_service_policy_context (empty)\fR" +/* Optional information that the Postfix SMTP server specifies in +/* the "policy_context" attribute of a policy service request (originally, +/* to share the same service endpoint among multiple check_policy_service +/* clients). +/* ACCESS CONTROLS +/* .ad +/* .fi +/* The SMTPD_ACCESS_README document gives an introduction to all the +/* SMTP server access control features. +/* .IP "\fBsmtpd_delay_reject (yes)\fR" +/* Wait until the RCPT TO command before evaluating +/* $smtpd_client_restrictions, $smtpd_helo_restrictions and +/* $smtpd_sender_restrictions, or wait until the ETRN command before +/* evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions. +/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR" +/* A list of Postfix features where the pattern "example.com" also +/* matches subdomains of example.com, +/* instead of requiring an explicit ".example.com" pattern. +/* .IP "\fBsmtpd_client_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client connection request. +/* .IP "\fBsmtpd_helo_required (no)\fR" +/* Require that a remote SMTP client introduces itself with the HELO +/* or EHLO command before sending the MAIL command or other commands +/* that require EHLO negotiation. +/* .IP "\fBsmtpd_helo_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client HELO command. +/* .IP "\fBsmtpd_sender_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client MAIL FROM command. +/* .IP "\fBsmtpd_recipient_restrictions (see 'postconf -d' output)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client RCPT TO command, after smtpd_relay_restrictions. +/* .IP "\fBsmtpd_etrn_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client ETRN command. +/* .IP "\fBallow_untrusted_routing (no)\fR" +/* Forward mail with sender-specified routing (user[@%!]remote[@%!]site) +/* from untrusted clients to destinations matching $relay_domains. +/* .IP "\fBsmtpd_restriction_classes (empty)\fR" +/* User-defined aliases for groups of access restrictions. +/* .IP "\fBsmtpd_null_access_lookup_key (<>)\fR" +/* The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the +/* null sender address. +/* .IP "\fBpermit_mx_backup_networks (empty)\fR" +/* Restrict the use of the permit_mx_backup SMTP access feature to +/* only domains whose primary MX hosts match the listed networks. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBsmtpd_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server applies +/* in the context of the SMTP DATA command. +/* .IP "\fBsmtpd_expansion_filter (see 'postconf -d' output)\fR" +/* What characters are allowed in $name expansions of RBL reply +/* templates. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_reject_unlisted_recipient (yes)\fR" +/* Request that the Postfix SMTP server rejects mail for unknown +/* recipient addresses, even when no explicit reject_unlisted_recipient +/* access restriction is specified. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_end_of_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server +/* applies in the context of the SMTP END-OF-DATA command. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)\fR" +/* Access restrictions for mail relay control that the Postfix +/* SMTP server applies in the context of the RCPT TO command, before +/* smtpd_recipient_restrictions. +/* SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS +/* .ad +/* .fi +/* Postfix version 2.1 introduces sender and recipient address verification. +/* This feature is implemented by sending probe email messages that +/* are not actually delivered. +/* This feature is requested via the reject_unverified_sender and +/* reject_unverified_recipient access restrictions. The status of +/* verification probes is maintained by the \fBverify\fR(8) server. +/* See the file ADDRESS_VERIFICATION_README for information +/* about how to configure and operate the Postfix sender/recipient +/* address verification service. +/* .IP "\fBaddress_verify_poll_count (normal: 3, overload: 1)\fR" +/* How many times to query the \fBverify\fR(8) service for the completion +/* of an address verification request in progress. +/* .IP "\fBaddress_verify_poll_delay (3s)\fR" +/* The delay between queries for the completion of an address +/* verification request in progress. +/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR" +/* The sender address to use in address verification probes; prior +/* to Postfix 2.5 the default was "postmaster". +/* .IP "\fBunverified_sender_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is rejected by the reject_unverified_sender restriction. +/* .IP "\fBunverified_recipient_reject_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* is rejected by the reject_unverified_recipient restriction. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBunverified_sender_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a sender address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_defer_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_sender_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_sender. +/* .IP "\fBunverified_recipient_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_recipient. +/* .IP "\fBunverified_sender_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_sender +/* fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_recipient +/* fails due to a temporary error condition. +/* .PP +/* Available with Postfix 2.9 and later: +/* .IP "\fBaddress_verify_sender_ttl (0s)\fR" +/* The time between changes in the time-dependent portion of address +/* verification probe sender addresses. +/* ACCESS CONTROL RESPONSES +/* .ad +/* .fi +/* The following parameters control numerical SMTP reply codes +/* and/or text responses. +/* .IP "\fBaccess_map_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "reject" action. +/* .IP "\fBdefer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "defer" restriction. +/* .IP "\fBinvalid_hostname_reject_code (501)\fR" +/* The numerical Postfix SMTP server response code when the client +/* HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname +/* restriction. +/* .IP "\fBmaps_rbl_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_rbl_client, reject_rhsbl_client, +/* reject_rhsbl_reverse_client, reject_rhsbl_sender or +/* reject_rhsbl_recipient restriction. +/* .IP "\fBnon_fqdn_reject_code (504)\fR" +/* The numerical Postfix SMTP server reply code when a client request +/* is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender +/* or reject_non_fqdn_recipient restriction. +/* .IP "\fBplaintext_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a request +/* is rejected by the \fBreject_plaintext_session\fR restriction. +/* .IP "\fBreject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "reject" restriction. +/* .IP "\fBrelay_domains_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a client +/* request is rejected by the reject_unauth_destination recipient +/* restriction. +/* .IP "\fBunknown_address_reject_code (450)\fR" +/* The numerical response code when the Postfix SMTP server rejects a +/* sender or recipient address because its domain is unknown. +/* .IP "\fBunknown_client_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a client +/* without valid address <=> name mapping is rejected by the +/* reject_unknown_client_hostname restriction. +/* .IP "\fBunknown_hostname_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when the hostname +/* specified with the HELO or EHLO command is rejected by the +/* reject_unknown_helo_hostname restriction. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdefault_rbl_reply (see 'postconf -d' output)\fR" +/* The default Postfix SMTP server response template for a request that is +/* rejected by an RBL-based restriction. +/* .IP "\fBmulti_recipient_bounce_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_multi_recipient_bounce +/* restriction. +/* .IP "\fBrbl_reply_maps (empty)\fR" +/* Optional lookup tables with RBL response templates. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBaccess_map_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "defer" action, including "defer_if_permit" +/* or "defer_if_reject". +/* .IP "\fBreject_tempfail_action (defer_if_permit)\fR" +/* The Postfix SMTP server's action when a reject-type restriction +/* fails due to a temporary error condition. +/* .IP "\fBunknown_helo_hostname_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_helo_hostname +/* fails due to a temporary error condition. +/* .IP "\fBunknown_address_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_sender_domain +/* or reject_unknown_recipient_domain fail due to a temporary error +/* condition. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBdouble_bounce_sender (double-bounce)\fR" +/* The sender address of postmaster notifications that are generated +/* by the mail system. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_name (Postfix)\fR" +/* The mail system name that is displayed in Received: headers, in +/* the SMTP greeting banner, and in bounced mail. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBmyhostname (see 'postconf -d' output)\fR" +/* The internet hostname of this mail system. +/* .IP "\fBmynetworks (see 'postconf -d' output)\fR" +/* The list of "trusted" remote SMTP clients that have more privileges than +/* "strangers". +/* .IP "\fBmyorigin ($myhostname)\fR" +/* The domain name that locally-posted mail appears to come +/* from, and that locally posted mail is delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate a user name from its +/* extension (example: user+foo), or a .forward file name from its +/* extension (example: .forward+foo). +/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR" +/* The text that follows the 220 status code in the SMTP greeting +/* banner. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_forbidden_commands (CONNECT, GET, POST)\fR" +/* List of commands that cause the Postfix SMTP server to immediately +/* terminate the session with a 221 code. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_client_port_logging (no)\fR" +/* Enable logging of the remote SMTP client port in addition to +/* the hostname and IP address. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.4 and later: +/* .IP "\fBsmtpd_reject_footer_maps (empty)\fR" +/* Lookup tables, indexed by the complete Postfix SMTP server 4xx or +/* 5xx response, with reject footer templates. +/* SEE ALSO +/* anvil(8), connection/rate limiting +/* cleanup(8), message canonicalization +/* tlsmgr(8), TLS session and PRNG management +/* trivial-rewrite(8), address resolver +/* verify(8), address verification service +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_CLASS_README, blocking unknown hosted or relay recipients +/* ADDRESS_REWRITING_README, Postfix address manipulation +/* BDAT_README, Postfix CHUNKING support +/* FILTER_README, external after-queue content filter +/* LOCAL_RECIPIENT_README, blocking unknown local recipients +/* MILTER_README, before-queue mail filter applications +/* SMTPD_ACCESS_README, built-in access policies +/* SMTPD_POLICY_README, external policy server +/* SMTPD_PROXY_README, external before-queue content filter +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* VERP_README, Postfix XVERP extension +/* XCLIENT_README, Postfix XCLIENT extension +/* XFORWARD_README, Postfix XFORWARD extension +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <string.h> +#include <stdio.h> /* remove() */ +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <stddef.h> /* offsetof() */ + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <events.h> +#include <smtp_stream.h> +#include <valid_hostname.h> +#include <dict.h> +#include <watchdog.h> +#include <iostuff.h> +#include <split_at.h> +#include <name_code.h> +#include <inet_proto.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_version.h> /* milter_macro_v */ +#include <record.h> +#include <rec_type.h> +#include <mail_proto.h> +#include <cleanup_user.h> +#include <mail_date.h> +#include <mail_conf.h> +#include <off_cvt.h> +#include <debug_peer.h> +#include <mail_error.h> +#include <flush_clnt.h> +#include <mail_stream.h> +#include <mail_queue.h> +#include <tok822.h> +#include <verp_sender.h> +#include <string_list.h> +#include <quote_822_local.h> +#include <lex_822.h> +#include <namadr_list.h> +#include <input_transp.h> +#include <is_header.h> +#include <anvil_clnt.h> +#include <flush_clnt.h> +#include <ehlo_mask.h> /* ehlo filter */ +#include <maps.h> /* ehlo filter */ +#include <valid_mailhost_addr.h> +#include <dsn_mask.h> +#include <xtext.h> +#include <uxtext.h> +#include <tls_proxy.h> +#include <verify_sender_addr.h> +#include <smtputf8.h> +#include <match_parent_style.h> + +/* Single-threaded server skeleton. */ + +#include <mail_server.h> + +/* Mail filter library. */ + +#include <milter.h> + +/* DNS library. */ + +#include <dns.h> + +/* Application-specific */ + +#include <smtpd_token.h> +#include <smtpd.h> +#include <smtpd_check.h> +#include <smtpd_chat.h> +#include <smtpd_sasl_proto.h> +#include <smtpd_sasl_glue.h> +#include <smtpd_proxy.h> +#include <smtpd_milter.h> +#include <smtpd_expand.h> + + /* + * Tunable parameters. Make sure that there is some bound on the length of + * an SMTP command, so that the mail system stays in control even when a + * malicious client sends commands of unreasonable length (qmail-dos-1). + * Make sure there is some bound on the number of recipients, so that the + * mail system stays in control even when a malicious client sends an + * unreasonable number of recipients (qmail-dos-2). + */ +int var_smtpd_rcpt_limit; +int var_smtpd_tmout; +int var_smtpd_soft_erlim; +int var_smtpd_hard_erlim; +int var_queue_minfree; /* XXX use off_t */ +char *var_smtpd_banner; +char *var_notify_classes; +char *var_client_checks; +char *var_helo_checks; +char *var_mail_checks; +char *var_relay_checks; +char *var_rcpt_checks; +char *var_etrn_checks; +char *var_data_checks; +char *var_eod_checks; +int var_unk_client_code; +int var_bad_name_code; +int var_unk_name_code; +int var_unk_addr_code; +int var_relay_code; +int var_maps_rbl_code; +int var_map_reject_code; +int var_map_defer_code; +char *var_maps_rbl_domains; +char *var_rbl_reply_maps; +int var_helo_required; +int var_reject_code; +int var_defer_code; +int var_smtpd_err_sleep; +int var_non_fqdn_code; +char *var_bounce_rcpt; +char *var_error_rcpt; +int var_smtpd_delay_reject; +char *var_rest_classes; +int var_strict_rfc821_env; +bool var_disable_vrfy_cmd; +char *var_canonical_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_virt_alias_maps; +char *var_virt_mailbox_maps; +char *var_alias_maps; +char *var_local_rcpt_maps; +bool var_allow_untrust_route; +int var_smtpd_junk_cmd_limit; +int var_smtpd_rcpt_overlim; +bool var_smtpd_sasl_enable; +bool var_smtpd_sasl_auth_hdr; +char *var_smtpd_sasl_opts; +char *var_smtpd_sasl_path; +char *var_smtpd_sasl_service; +char *var_cyrus_conf_path; +char *var_smtpd_sasl_realm; +int var_smtpd_sasl_resp_limit; +char *var_smtpd_sasl_exceptions_networks; +char *var_smtpd_sasl_type; +char *var_filter_xport; +bool var_broken_auth_clients; +char *var_perm_mx_networks; +char *var_smtpd_snd_auth_maps; +char *var_smtpd_noop_cmds; +char *var_smtpd_null_key; +int var_smtpd_hist_thrsh; +char *var_smtpd_exp_filter; +char *var_def_rbl_reply; +int var_unv_from_rcode; +int var_unv_rcpt_rcode; +int var_unv_from_dcode; +int var_unv_rcpt_dcode; +char *var_unv_from_why; +char *var_unv_rcpt_why; +int var_mul_rcpt_code; +char *var_relay_rcpt_maps; +int var_local_rcpt_code; +int var_virt_alias_code; +int var_virt_mailbox_code; +int var_relay_rcpt_code; +char *var_verp_clients; +int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; +char *var_smtpd_proxy_filt; +int var_smtpd_proxy_tmout; +char *var_smtpd_proxy_ehlo; +char *var_smtpd_proxy_opts; +char *var_input_transp; +int var_smtpd_policy_tmout; +int var_smtpd_policy_req_limit; +int var_smtpd_policy_try_limit; +int var_smtpd_policy_try_delay; +char *var_smtpd_policy_def_action; +char *var_smtpd_policy_context; +int var_smtpd_policy_idle; +int var_smtpd_policy_ttl; +char *var_xclient_hosts; +char *var_xforward_hosts; +bool var_smtpd_rej_unl_from; +bool var_smtpd_rej_unl_rcpt; +char *var_smtpd_forbid_cmds; +int var_smtpd_crate_limit; +int var_smtpd_cconn_limit; +int var_smtpd_cmail_limit; +int var_smtpd_crcpt_limit; +int var_smtpd_cntls_limit; +int var_smtpd_cauth_limit; +char *var_smtpd_hoggers; +char *var_local_rwr_clients; +char *var_smtpd_ehlo_dis_words; +char *var_smtpd_ehlo_dis_maps; + +char *var_smtpd_tls_level; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_wrappermode; +bool var_smtpd_tls_auth_only; +char *var_smtpd_cmd_filter; +char *var_smtpd_rej_footer; +char *var_smtpd_rej_ftr_maps; +char *var_smtpd_acl_perm_log; +char *var_smtpd_dns_re_filter; + +#ifdef USE_TLS +char *var_smtpd_relay_ccerts; +char *var_smtpd_sasl_tls_opts; +int var_smtpd_starttls_tmout; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +bool var_smtpd_tls_ask_ccert; +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_loglevel; +char *var_smtpd_tls_mand_proto; +bool var_smtpd_tls_received_header; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_chain_files; + +#endif + +bool var_smtpd_peername_lookup; +int var_plaintext_code; +bool var_smtpd_delay_open; +char *var_smtpd_milters; +char *var_smtpd_milter_maps; +int var_milt_conn_time; +int var_milt_cmd_time; +int var_milt_msg_time; +char *var_milt_protocol; +char *var_milt_def_action; +char *var_milt_daemon_name; +char *var_milt_v; +char *var_milt_conn_macros; +char *var_milt_helo_macros; +char *var_milt_mail_macros; +char *var_milt_rcpt_macros; +char *var_milt_data_macros; +char *var_milt_eoh_macros; +char *var_milt_eod_macros; +char *var_milt_unk_macros; +char *var_milt_macro_deflts; +bool var_smtpd_client_port_log; +char *var_stress; + +char *var_reject_tmpf_act; +char *var_unk_name_tf_act; +char *var_unk_addr_tf_act; +char *var_unv_rcpt_tf_act; +char *var_unv_from_tf_act; +bool var_smtpd_rec_deadline; + +int smtpd_proxy_opts; + +#ifdef USE_TLSPROXY +char *var_tlsproxy_service; + +#endif + +char *var_smtpd_uproxy_proto; +int var_smtpd_uproxy_tmout; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * EHLO keyword filter + */ +static MAPS *ehlo_discard_maps; + + /* + * Per-client Milter support. + */ +static MAPS *smtpd_milter_maps; +static void setup_milters(SMTPD_STATE *); +static void teardown_milters(SMTPD_STATE *); + + /* + * VERP command name. + */ +#define VERP_CMD "XVERP" +#define VERP_CMD_LEN 5 + +static NAMADR_LIST *verp_clients; + + /* + * XCLIENT command. Access control is cached, so that XCLIENT can't override + * its own access control. + */ +static NAMADR_LIST *xclient_hosts; +static int xclient_allowed; /* XXX should be SMTPD_STATE member */ + + /* + * XFORWARD command. Access control is cached. + */ +static NAMADR_LIST *xforward_hosts; +static int xforward_allowed; /* XXX should be SMTPD_STATE member */ + + /* + * Client connection and rate limiting. + */ +ANVIL_CLNT *anvil_clnt; +static NAMADR_LIST *hogger_list; + + /* + * Other application-specific globals. + */ +int smtpd_input_transp_mask; + + /* + * Forward declarations. + */ +static void helo_reset(SMTPD_STATE *); +static void mail_reset(SMTPD_STATE *); +static void rcpt_reset(SMTPD_STATE *); +static void chat_reset(SMTPD_STATE *, int); + +#ifdef USE_TLS +static void tls_reset(SMTPD_STATE *); + +#endif + + /* + * This filter is applied after printable(). + */ +#define NEUTER_CHARACTERS " <>()\\\";@" + + /* + * Reasons for losing the client. + */ +#define REASON_TIMEOUT "timeout" +#define REASON_LOST_CONNECTION "lost connection" +#define REASON_ERROR_LIMIT "too many errors" + +#ifdef USE_TLS + + /* + * TLS initialization status. + */ +#ifndef USE_TLSPROXY +static TLS_APPL_STATE *smtpd_tls_ctx; +static int ask_client_cert; + +#endif /* USE_TLSPROXY */ +#endif + + /* + * SMTP command mapping for broken clients. + */ +static DICT *smtpd_cmd_filter; + +#ifdef USE_SASL_AUTH + + /* + * SASL exceptions. + */ +static NAMADR_LIST *sasl_exceptions_networks; + +/* sasl_client_exception - can we offer AUTH for this client */ + +static int sasl_client_exception(SMTPD_STATE *state) +{ + int match; + + /* + * This is to work around a Netscape mail client bug where it tries to + * use AUTH if available, even if user has not configured it. Returns + * TRUE if AUTH should be offered in the EHLO. + */ + if (sasl_exceptions_networks == 0) + return (0); + + if ((match = namadr_list_match(sasl_exceptions_networks, + state->name, state->addr)) == 0) + match = sasl_exceptions_networks->error; + + if (msg_verbose) + msg_info("sasl_exceptions: %s, match=%d", + state->namaddr, match); + + return (match); +} + +#endif + +/* smtpd_whatsup - gather available evidence for logging */ + +static const char *smtpd_whatsup(SMTPD_STATE *state) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(100); + else + VSTRING_RESET(buf); + if (state->sender) + vstring_sprintf_append(buf, " from=<%s>", state->sender); + if (state->recipient) + vstring_sprintf_append(buf, " to=<%s>", state->recipient); + if (state->protocol) + vstring_sprintf_append(buf, " proto=%s", state->protocol); + if (state->helo_name) + vstring_sprintf_append(buf, " helo=<%s>", state->helo_name); +#ifdef USE_SASL_AUTH + if (state->sasl_username) + vstring_sprintf_append(buf, " sasl_username=<%s>", + state->sasl_username); +#endif + return (STR(buf)); +} + +/* collapse_args - put arguments together again */ + +static void collapse_args(int argc, SMTPD_TOKEN *argv) +{ + int i; + + for (i = 1; i < argc; i++) { + vstring_strcat(argv[0].vstrval, " "); + vstring_strcat(argv[0].vstrval, argv[i].strval); + } + argv[0].strval = STR(argv[0].vstrval); +} + +/* check_milter_reply - process reply from Milter */ + +static const char *check_milter_reply(SMTPD_STATE *state, const char *reply) +{ + const char *queue_id = state->queue_id ? state->queue_id : "NOQUEUE"; + const char *action; + const char *text; + + /* + * The syntax of user-specified SMTP replies is checked by the Milter + * module, because the replies are also used in the cleanup server. + * Automatically disconnect after 421 (shutdown) reply. The Sendmail 8 + * Milter quarantine action is not final, so it is not included in + * MILTER_SKIP_FLAGS. + */ +#define MILTER_SKIP_FLAGS (CLEANUP_FLAG_DISCARD) + + switch (reply[0]) { + case 'H': + state->saved_flags |= CLEANUP_FLAG_HOLD; + action = "milter-hold"; + reply = 0; + text = "milter triggers HOLD action"; + break; + case 'D': + state->saved_flags |= CLEANUP_FLAG_DISCARD; + action = "milter-discard"; + reply = 0; + text = "milter triggers DISCARD action"; + break; + case 'S': + state->error_mask |= MAIL_ERROR_POLICY; + action = "milter-reject"; + reply = "421 4.7.0 Server closing connection"; + text = 0; + break; + case '4': + case '5': + state->error_mask |= MAIL_ERROR_POLICY; + action = "milter-reject"; + text = 0; + break; + default: + state->error_mask |= MAIL_ERROR_SOFTWARE; + action = "reject"; + reply = "421 4.3.5 Server configuration error"; + text = 0; + break; + } + msg_info("%s: %s: %s from %s: %s;%s", queue_id, action, state->where, + state->namaddr, reply ? reply : text, smtpd_whatsup(state)); + return (reply); +} + +/* helo_cmd - process HELO command */ + +static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + + /* + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other + * than the initial greeting and any response to HELO or EHLO are + * prefaced with a status code as defined in RFC 3463. + */ + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: HELO hostname"); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_helo(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + + /* + * XXX Sendmail compatibility: if a Milter rejects CONNECT, EHLO, or + * HELO, reply with 250 except in case of 421 (disconnect). The reply + * persists so it will apply to MAIL FROM and to other commands such as + * AUTH, STARTTLS, and VRFY. + */ +#define PUSH_STRING(old, curr, new) { char *old = (curr); (curr) = (new); +#define POP_STRING(old, curr) (curr) = old; } + + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_helo_event(state->milters, argv[1].strval, 0)) != 0) { + /* Log reject etc. with correct HELO information. */ + PUSH_STRING(saved_helo, state->helo_name, argv[1].strval); + err = check_milter_reply(state, err); + POP_STRING(saved_helo, state->helo_name); + if (err != 0 && strncmp(err, "421", 3) == 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (state->helo_name != 0) + helo_reset(state); + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + state->helo_name = mystrdup(printable(argv[1].strval, '?')); + neuter(state->helo_name, NEUTER_CHARACTERS, '?'); + /* Downgrading the protocol name breaks the unauthorized pipelining test. */ + if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0 + && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_SMTP); + } + smtpd_chat_reply(state, "250 %s", var_myhostname); + return (0); +} + +/* cant_announce_feature - explain and terminate this session */ + +static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature) +{ + msg_warn("don't know if EHLO feature %s should be announced to %s", + feature, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + +/* cant_permit_command - explain and terminate this session */ + +static NORETURN cant_permit_command(SMTPD_STATE *state, const char *command) +{ + msg_warn("don't know if command %s should be allowed from %s", + command, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + +/* ehlo_cmd - process EHLO command */ + +static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + int discard_mask; + char **cpp; + + /* + * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear + * all buffers and reset the state exactly as if a RSET command had been + * issued. + * + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other + * than the initial greeting and any response to HELO or EHLO are + * prefaced with a status code as defined in RFC 3463. + */ + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: EHLO hostname"); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_helo(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + + /* + * XXX Sendmail compatibility: if a Milter 5xx rejects CONNECT, EHLO, or + * HELO, reply with ENHANCEDSTATUSCODES except in case of immediate + * disconnect. The reply persists so it will apply to MAIL FROM and to + * other commands such as AUTH, STARTTLS, and VRFY. + */ + err = 0; + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_helo_event(state->milters, argv[1].strval, 1)) != 0) { + /* Log reject etc. with correct HELO information. */ + PUSH_STRING(saved_helo, state->helo_name, argv[1].strval); + err = check_milter_reply(state, err); + POP_STRING(saved_helo, state->helo_name); + if (err != 0 && strncmp(err, "421", 3) == 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (state->helo_name != 0) + helo_reset(state); + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + state->helo_name = mystrdup(printable(argv[1].strval, '?')); + neuter(state->helo_name, NEUTER_CHARACTERS, '?'); + + /* + * XXX reject_unauth_pipelining depends on the following. If the user + * sends EHLO then we announce PIPELINING and we can't accuse them of + * using pipelining in places where it is allowed. + * + * XXX The reject_unauth_pipelining test needs to change and also account + * for mechanisms that disable PIPELINING selectively. + */ + if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_ESMTP); + } + + /* + * Build the EHLO response, producing no output until we know what to + * send - this simplifies exception handling. The CRLF record boundaries + * don't exist at this level in the code, so we represent multi-line + * output as an array of single-line responses. + */ +#define EHLO_APPEND(state, cmd) \ + do { \ + vstring_sprintf((state)->ehlo_buf, (cmd)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ + } while (0) + +#define EHLO_APPEND1(state, cmd, arg) \ + do { \ + vstring_sprintf((state)->ehlo_buf, (cmd), (arg)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ + } while (0) + + /* + * XXX Sendmail compatibility: if a Milter 5XX rejects CONNECT, EHLO, or + * HELO, reply with ENHANCEDSTATUSCODES only. The reply persists so it + * will apply to MAIL FROM, but we currently don't have a proper + * mechanism to apply Milter rejects to AUTH, STARTTLS, VRFY, and other + * commands while still allowing HELO/EHLO. + */ + discard_mask = state->ehlo_discard_mask; + if (err != 0 && err[0] == '5') + discard_mask |= ~EHLO_MASK_ENHANCEDSTATUSCODES; + if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask)); + if (ehlo_discard_maps && ehlo_discard_maps->error) { + msg_warn("don't know what EHLO features to announce to %s", + state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + + /* + * These may still exist after a prior exception. + */ + if (state->ehlo_argv == 0) { + state->ehlo_argv = argv_alloc(10); + state->ehlo_buf = vstring_alloc(10); + } else + argv_truncate(state->ehlo_argv, 0); + + EHLO_APPEND1(state, "%s", var_myhostname); + if ((discard_mask & EHLO_MASK_PIPELINING) == 0) + EHLO_APPEND(state, "PIPELINING"); + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + if (var_message_limit) + EHLO_APPEND1(state, "SIZE %lu", + (unsigned long) var_message_limit); /* XXX */ + else + EHLO_APPEND(state, "SIZE"); + } + if ((discard_mask & EHLO_MASK_VRFY) == 0) + if (var_disable_vrfy_cmd == 0) + EHLO_APPEND(state, SMTPD_CMD_VRFY); + if ((discard_mask & EHLO_MASK_ETRN) == 0) + EHLO_APPEND(state, SMTPD_CMD_ETRN); +#ifdef USE_TLS + if ((discard_mask & EHLO_MASK_STARTTLS) == 0) + if (var_smtpd_use_tls && (!state->tls_context)) + EHLO_APPEND(state, SMTPD_CMD_STARTTLS); +#endif +#ifdef USE_SASL_AUTH +#ifndef AUTH_CMD +#define AUTH_CMD "AUTH" +#endif + if ((discard_mask & EHLO_MASK_AUTH) == 0) { + if (smtpd_sasl_is_active(state) && !sasl_client_exception(state)) { + EHLO_APPEND1(state, "AUTH %s", state->sasl_mechanism_list); + if (var_broken_auth_clients) + EHLO_APPEND1(state, "AUTH=%s", state->sasl_mechanism_list); + } else if (sasl_exceptions_networks && sasl_exceptions_networks->error) + cant_announce_feature(state, AUTH_CMD); + } +#define XCLIENT_LOGIN_KLUDGE " " XCLIENT_LOGIN +#else +#define XCLIENT_LOGIN_KLUDGE "" +#endif + if ((discard_mask & EHLO_MASK_VERP) == 0) { + if (namadr_list_match(verp_clients, state->name, state->addr)) + EHLO_APPEND(state, VERP_CMD); + else if (verp_clients && verp_clients->error) + cant_announce_feature(state, VERP_CMD); + } + /* XCLIENT must not override its own access control. */ + if ((discard_mask & EHLO_MASK_XCLIENT) == 0) { + if (xclient_allowed) + EHLO_APPEND(state, XCLIENT_CMD + " " XCLIENT_NAME " " XCLIENT_ADDR + " " XCLIENT_PROTO " " XCLIENT_HELO + " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT + XCLIENT_LOGIN_KLUDGE + " " XCLIENT_DESTADDR + " " XCLIENT_DESTPORT); + else if (xclient_hosts && xclient_hosts->error) + cant_announce_feature(state, XCLIENT_CMD); + } + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) { + if (xforward_allowed) + EHLO_APPEND(state, XFORWARD_CMD + " " XFORWARD_NAME " " XFORWARD_ADDR + " " XFORWARD_PROTO " " XFORWARD_HELO + " " XFORWARD_DOMAIN " " XFORWARD_PORT + " " XFORWARD_IDENT); + else if (xforward_hosts && xforward_hosts->error) + cant_announce_feature(state, XFORWARD_CMD); + } + if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) + EHLO_APPEND(state, "ENHANCEDSTATUSCODES"); + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + EHLO_APPEND(state, "8BITMIME"); + if ((discard_mask & EHLO_MASK_DSN) == 0) + EHLO_APPEND(state, "DSN"); + if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0) + EHLO_APPEND(state, "SMTPUTF8"); + if ((discard_mask & EHLO_MASK_CHUNKING) == 0) + EHLO_APPEND(state, "CHUNKING"); + + /* + * Send the reply. + */ + for (cpp = state->ehlo_argv->argv; *cpp; cpp++) + smtpd_chat_reply(state, "250%c%s", cpp[1] ? '-' : ' ', *cpp); + + /* + * Clean up. + */ + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; + + return (0); +} + +/* helo_reset - reset HELO/EHLO command stuff */ + +static void helo_reset(SMTPD_STATE *state) +{ + if (state->helo_name) { + myfree(state->helo_name); + state->helo_name = 0; + if (state->milters != 0) + milter_abort(state->milters); + } + if (state->ehlo_argv) { + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + } + if (state->ehlo_buf) { + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; + } +} + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_auth_cmd_wrapper - smtpd_sasl_auth_cmd front-end */ + +static int smtpd_sasl_auth_cmd_wrapper(SMTPD_STATE *state, int argc, + SMTPD_TOKEN *argv) +{ + int rate; + + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_cauth_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_auth(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cauth_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("AUTH command rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, + "450 4.7.1 Error: too many AUTH commands from %s", + state->addr); + return (-1); + } + return (smtpd_sasl_auth_cmd(state, argc, argv)); +} + +#endif + +/* mail_open_stream - open mail queue file or IPC stream */ + +static int mail_open_stream(SMTPD_STATE *state) +{ + + /* + * Connect to the before-queue filter when one is configured. The MAIL + * FROM and RCPT TO commands are forwarded as received (including DSN + * attributes), with the exception that the before-filter smtpd process + * handles all authentication, encryption, access control and relay + * control, and that the before-filter smtpd process does not forward + * blocked commands. If the after-filter smtp server does not support + * some of Postfix's ESMTP features, then they must be turned off in the + * before-filter smtpd process with the smtpd_discard_ehlo_keywords + * feature. + */ + if (state->proxy_mail) { + if (smtpd_proxy_create(state, smtpd_proxy_opts, var_smtpd_proxy_filt, + var_smtpd_proxy_tmout, var_smtpd_proxy_ehlo, + state->proxy_mail) != 0) { + smtpd_chat_reply(state, "%s", STR(state->proxy->reply)); + smtpd_proxy_free(state); + return (-1); + } + } + + /* + * If running from the master or from inetd, connect to the cleanup + * service. + * + * XXX 2821: An SMTP server is not allowed to "clean up" mail except in the + * case of original submissions. + * + * We implement this by distinguishing between mail that we are willing to + * rewrite (the local rewrite context) and mail from elsewhere. + */ + else if (SMTPD_STAND_ALONE(state) == 0) { + int cleanup_flags; + + cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL, + smtpd_input_transp_mask) + | CLEANUP_FLAG_SMTP_REPLY; + if (state->flags & SMTPD_FLAG_SMTPUTF8) + cleanup_flags |= CLEANUP_FLAG_SMTPUTF8; + else + cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SMTPD); + state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, + var_cleanup_service); + if (state->dest == 0 + || attr_print(state->dest->stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags), + ATTR_TYPE_END) != 0) + msg_fatal("unable to connect to the %s %s service", + MAIL_CLASS_PUBLIC, var_cleanup_service); + } + + /* + * Otherwise, pipe the message through the privileged postdrop helper. + * XXX Make postdrop a manifest constant. + */ + else { + char *postdrop_command; + + postdrop_command = concatenate(var_command_dir, "/postdrop", + msg_verbose ? " -v" : (char *) 0, (char *) 0); + state->dest = mail_stream_command(postdrop_command); + if (state->dest == 0) + msg_fatal("unable to execute %s", postdrop_command); + myfree(postdrop_command); + } + + /* + * Record the time of arrival, the SASL-related stuff if applicable, the + * sender envelope address, some session information, and some additional + * attributes. + * + * XXX Send Milter information first, because this will hang when cleanup + * goes into "throw away" mode. Also, cleanup needs to know early on + * whether or not it has to do its own SMTP event emulation. + * + * XXX At this point we send only dummy information to keep the cleanup + * server from using its non_smtpd_milters settings. We have to send + * up-to-date Milter information after DATA so that the cleanup server + * knows the actual Milter state. + */ + if (state->dest) { + state->cleanup = state->dest->stream; + state->queue_id = mystrdup(state->dest->id); + if (SMTPD_STAND_ALONE(state) == 0) { + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) + /* Send place-holder smtpd_milters list. */ + (void) milter_dummy(state->milters, state->cleanup); + rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(state->arrival_time)); + if (*var_filter_xport) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); + if (FORWARD_IDENT(state)) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_IDENT, FORWARD_IDENT(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_RWR_CONTEXT, FORWARD_DOMAIN(state)); +#ifdef USE_SASL_AUTH + /* Make external authentication painless (e.g., XCLIENT). */ + if (state->sasl_method) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_METHOD, state->sasl_method); + if (state->sasl_username) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_USERNAME, state->sasl_username); + if (state->sasl_sender) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_SENDER, state->sasl_sender); +#endif + + /* + * Record DSN related information that was received with the MAIL + * FROM command. + * + * RFC 3461 Section 5.2.1. If no ENVID parameter was included in the + * MAIL command when the message was received, the ENVID + * parameter MUST NOT be supplied when the message is relayed. + * Ditto for the RET parameter. + * + * In other words, we can't simply make up our default ENVID or RET + * values. We have to remember whether the client sent any. + * + * We store DSN information as named attribute records so that we + * don't have to pollute the queue file with records that are + * incompatible with past Postfix versions. Preferably, people + * should be able to back out from an upgrade without losing + * mail. + */ + if (state->dsn_envid) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, state->dsn_envid); + if (state->dsn_ret) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, state->dsn_ret); + } + rec_fputs(state->cleanup, REC_TYPE_FROM, state->sender); + if (state->encoding != 0) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ENCODING, state->encoding); + + /* + * Store client attributes. + */ + if (SMTPD_STAND_ALONE(state) == 0) { + + /* + * Attributes for logging, also used for XFORWARD. + * + * We store all client attributes, including ones with unknown + * values. Otherwise, an unknown client hostname would be treated + * as a non-existent hostname (i.e. local submission). + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_NAME, FORWARD_NAME(state)); + /* XXX Note: state->rfc_addr, not state->addr. */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_ADDR, FORWARD_ADDR(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_PORT, FORWARD_PORT(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_ORIGIN, FORWARD_NAMADDR(state)); + if (FORWARD_HELO(state)) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_HELO_NAME, FORWARD_HELO(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_PROTO_NAME, FORWARD_PROTO(state)); + + /* + * Attributes with actual client information. These are used by + * the smtpd Milter client for policy decisions. Mail that is + * requeued with "postsuper -r" is not subject to processing by + * the cleanup Milter client, because a) it has already been + * filtered, and b) we don't have sufficient information to + * reproduce the exact same SMTP events and Sendmail macros that + * the smtpd Milter client received when the message originally + * arrived in Postfix. + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_NAME, state->name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, state->reverse_name); + /* XXX Note: state->addr, not state->rfc_addr. */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_ADDR, state->addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_PORT, state->port); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_SERVER_ADDR, state->dest_addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_SERVER_PORT, state->dest_port); + if (state->helo_name) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_HELO_NAME, state->helo_name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_PROTO_NAME, state->protocol); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u", + MAIL_ATTR_ACT_CLIENT_AF, state->addr_family); + + /* + * Don't send client certificate down the pipeline unless it is + * a) verified or b) just a fingerprint. + */ + } + if (state->verp_delims) + rec_fputs(state->cleanup, REC_TYPE_VERP, state->verp_delims); + } + + /* + * Log the queue ID with the message origin. + */ +#define PRINT_OR_NULL(cond, str) \ + ((cond) ? (str) : "") +#define PRINT2_OR_NULL(cond, name, value) \ + PRINT_OR_NULL((cond), (name)), PRINT_OR_NULL((cond), (value)) + + msg_info("%s: client=%s%s%s%s%s%s%s%s%s%s%s", + (state->queue_id ? state->queue_id : "NOQUEUE"), + state->namaddr, +#ifdef USE_SASL_AUTH + PRINT2_OR_NULL(state->sasl_method, + ", sasl_method=", state->sasl_method), + PRINT2_OR_NULL(state->sasl_username, + ", sasl_username=", state->sasl_username), + PRINT2_OR_NULL(state->sasl_sender, + ", sasl_sender=", state->sasl_sender), +#else + "", "", "", "", "", "", +#endif + /* Insert transaction TLS status here. */ + PRINT2_OR_NULL(HAVE_FORWARDED_IDENT(state), + ", orig_queue_id=", FORWARD_IDENT(state)), + PRINT2_OR_NULL(HAVE_FORWARDED_CLIENT_ATTR(state), + ", orig_client=", FORWARD_NAMADDR(state))); + return (0); +} + +/* extract_addr - extract address from rubble */ + +static int extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg, + int allow_empty_addr, int strict_rfc821, + int smtputf8) +{ + const char *myname = "extract_addr"; + TOK822 *tree; + TOK822 *tp; + TOK822 *addr = 0; + int naddr; + int non_addr; + int err = 0; + char *junk = 0; + char *text; + char *colon; + + /* + * Special case. + */ +#define PERMIT_EMPTY_ADDR 1 +#define REJECT_EMPTY_ADDR 0 + + /* + * Some mailers send RFC822-style address forms (with comments and such) + * in SMTP envelopes. We cannot blame users for this: the blame is with + * programmers violating the RFC, and with sendmail for being permissive. + * + * XXX The SMTP command tokenizer must leave the address in externalized + * (quoted) form, so that the address parser can correctly extract the + * address from surrounding junk. + * + * XXX We have only one address parser, written according to the rules of + * RFC 822. That standard differs subtly from RFC 821. + */ + if (msg_verbose) + msg_info("%s: input: %s", myname, STR(arg->vstrval)); + if (STR(arg->vstrval)[0] == '<' + && STR(arg->vstrval)[LEN(arg->vstrval) - 1] == '>') { + junk = text = mystrndup(STR(arg->vstrval) + 1, LEN(arg->vstrval) - 2); + } else + text = STR(arg->vstrval); + + /* + * Truncate deprecated route address form. + */ + if (*text == '@' && (colon = strchr(text, ':')) != 0) + text = colon + 1; + tree = tok822_parse(text); + + if (junk) + myfree(junk); + + /* + * Find trouble. + */ + for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + addr = tp; + naddr += 1; /* count address forms */ + } else if (tp->type == '<' || tp->type == '>') { + /* void */ ; /* ignore brackets */ + } else { + non_addr += 1; /* count non-address forms */ + } + } + + /* + * Report trouble. XXX Should log a warning only if we are going to + * sleep+reject so that attackers can't flood our logfiles. + * + * XXX Unfortunately, the sleep-before-reject feature had to be abandoned + * (at least for small error counts) because servers were DOS-ing + * themselves when flooded by backscatter traffic. + */ + if (naddr > 1 + || (strict_rfc821 && (non_addr || *STR(arg->vstrval) != '<'))) { + msg_warn("Illegal address syntax from %s in %s command: %s", + state->namaddr, state->where, + printable(STR(arg->vstrval), '?')); + err = 1; + } + + /* + * Don't overwrite the input with the extracted address. We need the + * original (external) form in case the client does not send ORCPT + * information; and error messages are more accurate if we log the + * unmodified form. We need the internal form for all other purposes. + */ + if (addr) + tok822_internalize(state->addr_buf, addr->head, TOK822_STR_DEFL); + else + vstring_strcpy(state->addr_buf, ""); + + /* + * Report trouble. XXX Should log a warning only if we are going to + * sleep+reject so that attackers can't flood our logfiles. Log the + * original address. + */ + if (err == 0) + if ((STR(state->addr_buf)[0] == 0 && !allow_empty_addr) + || (strict_rfc821 && STR(state->addr_buf)[0] == '@') + || (SMTPD_STAND_ALONE(state) == 0 + && smtpd_check_addr(strcmp(state->where, SMTPD_CMD_MAIL) == 0 ? + state->recipient : state->sender, + STR(state->addr_buf), smtputf8) != 0)) { + msg_warn("Illegal address syntax from %s in %s command: %s", + state->namaddr, state->where, + printable(STR(arg->vstrval), '?')); + err = 1; + } + + /* + * Cleanup. + */ + tok822_free_tree(tree); + if (msg_verbose) + msg_info("%s: in: %s, result: %s", + myname, STR(arg->vstrval), STR(state->addr_buf)); + return (err); +} + +/* milter_argv - impedance adapter */ + +static const char **milter_argv(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + int n; + ssize_t len = argc + 1; + + if (state->milter_argc < len) { + if (state->milter_argc > 0) + state->milter_argv = (const char **) + myrealloc((void *) state->milter_argv, + sizeof(const char *) * len); + else + state->milter_argv = (const char **) + mymalloc(sizeof(const char *) * len); + state->milter_argc = len; + } + for (n = 0; n < argc; n++) + state->milter_argv[n] = argv[n].strval; + state->milter_argv[n] = 0; + return (state->milter_argv); +} + +/* mail_cmd - process MAIL command */ + +static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + int narg; + char *arg; + char *verp_delims = 0; + int rate; + int dsn_envid = 0; + + state->flags &= ~SMTPD_FLAG_SMTPUTF8; + state->encoding = 0; + state->dsn_ret = 0; + + /* + * Sanity checks. + * + * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a + * command in which invalid character codes have been employed, and for + * which there are no other reasons for rejection, MUST reject that + * command with a 501 response. Postfix attempts to be 8-bit clean. + */ + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first"); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command"); + return (-1); + } + /* Don't accept MAIL after out-of-order BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT"); + return (-1); + } + if (argc < 3 + || strcasecmp(argv[1].strval, "from:") != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: MAIL FROM:<address>"); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_cmail_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_mail(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cmail_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "450 4.7.1 Error: too much mail from %s", + state->addr); + msg_warn("Message delivery request rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + return (-1); + } + if (argv[2].tokval == SMTPD_TOK_ERROR) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax"); + return (-1); + } + + /* + * XXX The sender address comes first, but the optional SMTPUTF8 + * parameter determines what address syntax is permitted. We must process + * this parameter early. + */ + if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0) { + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */ + /* Fix 20161206: allow UTF8 in smtpd_sender_restrictions. */ + state->flags |= SMTPD_FLAG_SMTPUTF8; + break; + } + } + } + if (extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR, + var_strict_rfc821_env, + state->flags & SMTPD_FLAG_SMTPUTF8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax"); + return (-1); + } + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strcasecmp(arg, "BODY=8BITMIME") == 0) { /* RFC 1652 */ + state->encoding = MAIL_ATTR_ENC_8BIT; + } else if (strcasecmp(arg, "BODY=7BIT") == 0) { /* RFC 1652 */ + state->encoding = MAIL_ATTR_ENC_7BIT; + } else if (strncasecmp(arg, "SIZE=", 5) == 0) { /* RFC 1870 */ + /* Reject non-numeric size. */ + if (!alldig(arg + 5)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad message size syntax"); + return (-1); + } + /* Reject size overflow. */ + if ((state->msg_size = off_cvt_string(arg + 5)) < 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "552 5.3.4 Message size exceeds file system imposed limit"); + return (-1); + } + } else if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0 + && strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */ + /* Already processed early. */ ; +#ifdef USE_SASL_AUTH + } else if (strncasecmp(arg, "AUTH=", 5) == 0) { + if ((err = smtpd_sasl_mail_opt(state, arg + 5)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } +#endif + } else if (namadr_list_match(verp_clients, state->name, state->addr) + && strncasecmp(arg, VERP_CMD, VERP_CMD_LEN) == 0 + && (arg[VERP_CMD_LEN] == '=' || arg[VERP_CMD_LEN] == 0)) { + if (arg[VERP_CMD_LEN] == 0) { + verp_delims = var_verp_delims; + } else { + verp_delims = arg + VERP_CMD_LEN + 1; + if (verp_delims_verify(verp_delims) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: %s needs two characters from %s", + VERP_CMD, var_verp_filter); + return (-1); + } + } + } else if (strncasecmp(arg, "RET=", 4) == 0) { /* RFC 3461 */ + /* Sanitized on input. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (state->dsn_ret + || (state->dsn_ret = dsn_ret_code(arg + 4)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Bad RET parameter syntax"); + return (-1); + } + } else if (strncasecmp(arg, "ENVID=", 6) == 0) { /* RFC 3461 */ + /* Sanitized by bounce server. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (dsn_envid + || xtext_unquote(state->dsn_buf, arg + 6) == 0 + || !allprint(STR(state->dsn_buf))) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad ENVID parameter syntax"); + return (-1); + } + dsn_envid = 1; + } else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg); + return (-1); + } + } + /* Fix 20161205: show the envelope sender in reject logging. */ + PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf)); + err = smtpd_check_size(state, state->msg_size); + POP_STRING(saved_sender, state->sender); + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (verp_delims && STR(state->addr_buf)[0] == 0) { + smtpd_chat_reply(state, "503 5.5.4 Error: %s requires non-null sender", + VERP_CMD); + return (-1); + } + if (SMTPD_STAND_ALONE(state) == 0) { + const char *verify_sender; + + /* + * XXX Don't reject the address when we're probed with our own + * address verification sender address. Otherwise, some timeout or + * some UCE block may result in mutual negative caching, making it + * painful to get the mail through. Unfortunately we still have to + * send the address to the Milters otherwise they may bail out with a + * "missing recipient" protocol error. + */ + verify_sender = valid_verify_sender_addr(STR(state->addr_buf)); + if (verify_sender != 0) + vstring_strcpy(state->addr_buf, verify_sender); + } + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_mail(state, STR(state->addr_buf))) != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) { + state->flags |= SMTPD_FLAG_NEED_MILTER_ABORT; + PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf)); + err = milter_mail_event(state->milters, + milter_argv(state, argc - 2, argv + 2)); + if (err != 0) { + /* Log reject etc. with correct sender information. */ + err = check_milter_reply(state, err); + } + POP_STRING(saved_sender, state->sender); + if (err != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (SMTPD_STAND_ALONE(state) == 0) { + err = smtpd_check_rewrite(state); + if (err != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * Historically, Postfix does not forbid 8-bit envelope localparts. + * Changing this would be a compatibility break. That can't happen in the + * foreseeable future. + */ + if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable) + && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0 + && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + if (var_strict_smtputf8) { + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to " + "send unicode address"); + return (-1); + } + + /* + * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a + * warning message is logged, so that they don't suddenly start to + * lose mail after Postfix is built with EAI support. + */ + if (warn_compat_break_smtputf8_enable) + msg_info("using backwards-compatible default setting " + VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII sender " + "address \"%s\" from %s", STR(state->addr_buf), + state->namaddr); + } + + /* + * Check the queue file space, if applicable. The optional before-filter + * speed-adjust buffers use disk space. However, we don't know if they + * compete for storage space with the after-filter queue, so we can't + * simply bump up the free space requirement to 2.5 * message_size_limit. + */ + if (!USE_SMTPD_PROXY(state) + || (smtpd_proxy_opts & SMTPD_PROXY_FLAG_SPEED_ADJUST)) { + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_queue(state)) != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * No more early returns. The mail transaction is in progress. + */ + GETTIMEOFDAY(&state->arrival_time); + state->sender = mystrdup(STR(state->addr_buf)); + vstring_sprintf(state->instance, "%x.%lx.%lx.%x", + var_pid, (unsigned long) state->arrival_time.tv_sec, + (unsigned long) state->arrival_time.tv_usec, state->seqno++); + if (verp_delims) + state->verp_delims = mystrdup(verp_delims); + if (dsn_envid) + state->dsn_envid = mystrdup(STR(state->dsn_buf)); + if (USE_SMTPD_PROXY(state)) + state->proxy_mail = mystrdup(STR(state->buffer)); + if (var_smtpd_delay_open == 0 && mail_open_stream(state) < 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + return (-1); + } + smtpd_chat_reply(state, "250 2.1.0 Ok"); + return (0); +} + +/* mail_reset - reset MAIL command stuff */ + +static void mail_reset(SMTPD_STATE *state) +{ + state->msg_size = 0; + state->act_size = 0; + state->flags &= SMTPD_MASK_MAIL_KEEP; + + /* + * Unceremoniously close the pipe to the cleanup service. The cleanup + * service will delete the queue file when it detects a premature + * end-of-file condition on input. + */ + if (state->cleanup != 0) { + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + state->err = 0; + if (state->queue_id != 0) { + myfree(state->queue_id); + state->queue_id = 0; + } + if (state->sender) { + myfree(state->sender); + state->sender = 0; + } + /* WeiYu Wu: need to undo milter_mail_event() state change. */ + if (state->flags & SMTPD_FLAG_NEED_MILTER_ABORT) { + milter_abort(state->milters); + state->flags &= ~SMTPD_FLAG_NEED_MILTER_ABORT; + } + if (state->verp_delims) { + myfree(state->verp_delims); + state->verp_delims = 0; + } + if (state->proxy_mail) { + myfree(state->proxy_mail); + state->proxy_mail = 0; + } + if (state->saved_filter) { + myfree(state->saved_filter); + state->saved_filter = 0; + } + if (state->saved_redirect) { + myfree(state->saved_redirect); + state->saved_redirect = 0; + } + if (state->saved_bcc) { + argv_free(state->saved_bcc); + state->saved_bcc = 0; + } + state->saved_flags = 0; +#ifdef DELAY_ACTION + state->saved_delay = 0; +#endif +#ifdef USE_SASL_AUTH + if (state->sasl_sender) + smtpd_sasl_mail_reset(state); +#endif + state->discard = 0; + VSTRING_RESET(state->instance); + VSTRING_TERMINATE(state->instance); + + if (state->proxy) + smtpd_proxy_free(state); + if (state->xforward.flags) + smtpd_xforward_reset(state); + if (state->prepend) + state->prepend = argv_free(state->prepend); + if (state->dsn_envid) { + myfree(state->dsn_envid); + state->dsn_envid = 0; + } + if (state->milter_argv) { + myfree((void *) state->milter_argv); + state->milter_argv = 0; + state->milter_argc = 0; + } + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + if (state->bdat_get_stream) { + (void) vstream_fclose(state->bdat_get_stream); + state->bdat_get_stream = 0; + } + if (state->bdat_get_buffer) + VSTRING_RESET(state->bdat_get_buffer); +} + +/* rcpt_cmd - process RCPT TO command */ + +static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + int narg; + char *arg; + int rate; + const char *dsn_orcpt_addr = 0; + ssize_t dsn_orcpt_addr_len = 0; + const char *dsn_orcpt_type = 0; + int dsn_notify = 0; + const char *coded_addr; + const char *milter_err; + + /* + * Sanity checks. + * + * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a + * command in which invalid character codes have been employed, and for + * which there are no other reasons for rejection, MUST reject that + * command with a 501 response. So much for the principle of "be liberal + * in what you accept, be strict in what you send". + */ + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command"); + return (-1); + } + /* Don't accept RCPT after BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT"); + return (-1); + } + if (argc < 3 + || strcasecmp(argv[1].strval, "to:") != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: RCPT TO:<address>"); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_crcpt_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_crcpt_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Recipient address rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s", + state->addr); + return (-1); + } + if (argv[2].tokval == SMTPD_TOK_ERROR) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + if (extract_addr(state, argv + 2, REJECT_EMPTY_ADDR, var_strict_rfc821_env, + state->flags & SMTPD_FLAG_SMTPUTF8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */ + /* Sanitized on input. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: Bad NOTIFY parameter syntax"); + return (-1); + } + } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */ + /* Sanitized by bounce server. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + vstring_strcpy(state->dsn_orcpt_buf, arg + 6); + if (dsn_orcpt_addr + || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0 + || *(dsn_orcpt_type = STR(state->dsn_orcpt_buf)) == 0 + || (strcasecmp(dsn_orcpt_type, "utf-8") == 0 ? + uxtext_unquote(state->dsn_buf, coded_addr) == 0 : + xtext_unquote(state->dsn_buf, coded_addr) == 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: Bad ORCPT parameter syntax"); + return (-1); + } + dsn_orcpt_addr = STR(state->dsn_buf); + dsn_orcpt_addr_len = LEN(state->dsn_buf); + } else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg); + return (-1); + } + } + if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) { + smtpd_chat_reply(state, "452 4.5.3 Error: too many recipients"); + if (state->rcpt_overshoot++ < var_smtpd_rcpt_overlim) + return (0); + state->error_mask |= MAIL_ERROR_POLICY; + return (-1); + } + + /* + * Historically, Postfix does not forbid 8-bit envelope localparts. + * Changing this would be a compatibility break. That can't happen in the + * foreseeable future. + */ + if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable) + && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0 + && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + if (var_strict_smtputf8) { + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to " + "send unicode address"); + return (-1); + } + + /* + * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a + * warning message is logged, so that they don't suddenly start to + * lose mail after Postfix is built with EAI support. + */ + if (warn_compat_break_smtputf8_enable) + msg_info("using backwards-compatible default setting " + VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII recipient " + "address \"%s\" from %s", STR(state->addr_buf), + state->namaddr); + } + if (SMTPD_STAND_ALONE(state) == 0) { + const char *verify_sender; + + /* + * XXX Don't reject the address when we're probed with our own + * address verification sender address. Otherwise, some timeout or + * some UCE block may result in mutual negative caching, making it + * painful to get the mail through. Unfortunately we still have to + * send the address to the Milters otherwise they may bail out with a + * "missing recipient" protocol error. + */ + verify_sender = valid_verify_sender_addr(STR(state->addr_buf)); + if (verify_sender != 0) { + vstring_strcpy(state->addr_buf, verify_sender); + err = 0; + } else { + err = smtpd_check_rcpt(state, STR(state->addr_buf)); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) { + PUSH_STRING(saved_rcpt, state->recipient, STR(state->addr_buf)); + state->milter_reject_text = err; + milter_err = milter_rcpt_event(state->milters, + err == 0 ? MILTER_FLAG_NONE : + MILTER_FLAG_WANT_RCPT_REJ, + milter_argv(state, argc - 2, argv + 2)); + if (err == 0 && milter_err != 0) { + /* Log reject etc. with correct recipient information. */ + err = check_milter_reply(state, milter_err); + } + POP_STRING(saved_rcpt, state->recipient); + } + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * Don't access the proxy, queue file, or queue file writer process until + * we have a valid recipient address. + */ + if (state->proxy == 0 && state->cleanup == 0 && mail_open_stream(state) < 0) + return (-1); + + /* + * Proxy the recipient. OK, so we lied. If the real-time proxy rejects + * the recipient then we can have a proxy connection without having + * accepted a recipient. + */ + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_OK, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + return (-1); + } + + /* + * Store the recipient. Remember the first one. + * + * Flush recipients to maintain a stiffer coupling with the next stage and + * to better utilize parallelism. + * + * RFC 3461 Section 5.2.1: If the NOTIFY parameter was not supplied for a + * recipient when the message was received, the NOTIFY parameter MUST NOT + * be supplied for that recipient when the message is relayed. + * + * In other words, we can't simply make up our default NOTIFY value. We have + * to remember whether the client sent any. + * + * RFC 3461 Section 5.2.1: If no ORCPT parameter was present when the + * message was received, an ORCPT parameter MAY be added to the RCPT + * command when the message is relayed. If an ORCPT parameter is added + * by the relaying MTA, it MUST contain the recipient address from the + * RCPT command used when the message was received by that MTA. + * + * In other words, it is OK to make up our own DSN original recipient when + * the client didn't send one. Although the RFC mentions mail relaying + * only, we also make up our own original recipient for the purpose of + * final delivery. For now, we do this here, rather than on the fly. + * + * XXX We use REC_TYPE_ATTR for DSN-related recipient attributes even though + * 1) REC_TYPE_ATTR is not meant for multiple instances of the same named + * attribute, and 2) mixing REC_TYPE_ATTR with REC_TYPE_(not attr) + * requires that we map attributes with rec_attr_map() in order to + * simplify the recipient record processing loops in the cleanup and qmgr + * servers. + * + * Another possibility, yet to be explored, is to leave the additional + * recipient information in the queue file and just pass queue file + * offsets along with the delivery request. This is a trade off between + * memory allocation versus numeric conversion overhead. + * + * Since we have no record grouping mechanism, all recipient-specific + * parameters must be sent to the cleanup server before the actual + * recipient address. + */ + state->rcpt_count++; + if (state->recipient == 0) + state->recipient = mystrdup(STR(state->addr_buf)); + if (state->cleanup) { + /* Note: RFC(2)821 externalized address! */ + if (dsn_orcpt_addr == 0) { + dsn_orcpt_type = "rfc822"; + dsn_orcpt_addr = argv[2].strval; + dsn_orcpt_addr_len = strlen(argv[2].strval); + if (dsn_orcpt_addr[0] == '<' + && dsn_orcpt_addr[dsn_orcpt_addr_len - 1] == '>') { + dsn_orcpt_addr += 1; + dsn_orcpt_addr_len -= 2; + } + } + if (dsn_notify) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s;%.*s", + MAIL_ATTR_DSN_ORCPT, dsn_orcpt_type, + (int) dsn_orcpt_addr_len, dsn_orcpt_addr); + rec_fputs(state->cleanup, REC_TYPE_RCPT, STR(state->addr_buf)); + vstream_fflush(state->cleanup); + } + smtpd_chat_reply(state, "250 2.1.5 Ok"); + return (0); +} + +/* rcpt_reset - reset RCPT stuff */ + +static void rcpt_reset(SMTPD_STATE *state) +{ + if (state->recipient) { + myfree(state->recipient); + state->recipient = 0; + } + state->rcpt_count = 0; + /* XXX Must flush the command history. */ + state->rcpt_overshoot = 0; +} + +#if 0 + +/* rfc2047_comment_encode - encode comment string */ + +static VSTRING *rfc2047_comment_encode(const char *str, const char *charset) +{ + VSTRING *buf = vstring_alloc(30); + const unsigned char *cp; + int ch; + + /* + * XXX This is problematic code. + * + * XXX Most of the RFC 2047 "especials" are not special in RFC*822 comments, + * but we encode them anyway to avoid complaints. + * + * XXX In Received: header comments we enclose peer and issuer common names + * with "" quotes (inherited from the Lutz Jaenicke patch). This is the + * cause of several quirks. + * + * 1) We encode text that contains the " character, even though that + * character is not special for RFC*822 comments. + * + * 2) We ignore the recommended limit of 75 characters per encoded word, + * because long comments look ugly when folded in-between quotes. + * + * 3) We encode the enclosing quotes, to avoid producing invalid encoded + * words. Microsoft abuses RFC 2047 encoding with attachment names, but + * we have no information on what decoders do with malformed encoding in + * comments. This means the comments are Jaenicke-compatible only after + * decoding. + */ +#define ESPECIALS "()<>@,;:\"/[]?.=" /* Special in RFC 2047 */ +#define QSPECIALS "_" ESPECIALS /* Special in RFC 2047 'Q' */ +#define CSPECIALS "\\\"()" /* Special in our comments */ + + /* Don't encode if not needed. */ + for (cp = (unsigned char *) str; /* see below */ ; ++cp) { + if ((ch = *cp) == 0) { + vstring_sprintf(buf, "\"%s\"", str); + return (buf); + } + if (!ISPRINT(ch) || strchr(CSPECIALS, ch)) + break; + } + + /* + * Use quoted-printable (like) encoding with spaces mapped to underscore. + */ + vstring_sprintf(buf, "=?%s?Q?=%02X", charset, '"'); + for (cp = (unsigned char *) str; (ch = *cp) != 0; ++cp) { + if (!ISPRINT(ch) || strchr(QSPECIALS CSPECIALS, ch)) { + vstring_sprintf_append(buf, "=%02X", ch); + } else if (ch == ' ') { + VSTRING_ADDCH(buf, '_'); + } else { + VSTRING_ADDCH(buf, ch); + } + } + vstring_sprintf_append(buf, "=%02X?=", '"'); + return (buf); +} + +#endif + +/* comment_sanitize - clean up comment string */ + +static void comment_sanitize(VSTRING *comment_string) +{ + unsigned char *cp; + int ch; + int pc; + + /* + * Postfix Received: headers can be configured to include a comment with + * the CN (CommonName) of the peer and its issuer, or the login name of a + * SASL authenticated user. To avoid problems with RFC 822 etc. syntax, + * we limit this information to printable ASCII text, and neutralize + * characters that affect comment parsing: the backslash and unbalanced + * parentheses. + */ + for (pc = 0, cp = (unsigned char *) STR(comment_string); (ch = *cp) != 0; cp++) { + if (!ISASCII(ch) || !ISPRINT(ch) || ch == '\\') { + *cp = '?'; + } else if (ch == '(') { + pc++; + } else if (ch == ')') { + if (pc > 0) + pc--; + else + *cp = '?'; + } + } + while (pc-- > 0) + VSTRING_ADDCH(comment_string, ')'); + VSTRING_TERMINATE(comment_string); +} + +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static int common_post_message_handling(SMTPD_STATE *state); + +/* data_cmd - process DATA command */ + +static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + SMTPD_PROXY *proxy; + const char *err; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Sanity checks. With ESMTP command pipelining the client can send DATA + * before all recipients are rejected, so don't report that as a protocol + * error. + */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT"); + return (-1); + } + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: need RCPT command"); + } else { + smtpd_chat_reply(state, "554 5.5.1 Error: no valid recipients"); + } + return (-1); + } + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: DATA"); + return (-1); + } + if (SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_data(state)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + return (-1); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. + */ + if (proxy) { + out_stream = proxy->stream; + out_record = proxy->rec_put; + out_fprintf = proxy->rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>"); + state->where = SMTPD_AFTER_DATA; + receive_data_message(state, out_record, out_fprintf, out_stream, out_error); + return common_post_message_handling(state); +} + +/* common_pre_message_handling - finish envelope and open message segment */ + +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char **cpp; + const char *rfc3848_sess; + const char *rfc3848_auth; + const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ? + "UTF8SMTP" : state->protocol; + +#ifdef USE_TLS + VSTRING *peer_CN; + VSTRING *issuer_CN; + +#endif +#ifdef USE_SASL_AUTH + VSTRING *username; + +#endif + + /* + * Flush out a first batch of access table actions that are delegated to + * the cleanup server, and that may trigger before we accept the first + * valid recipient. There will be more after end-of-data. + * + * Terminate the message envelope segment. Start the message content + * segment, and prepend our own Received: header. If there is only one + * recipient, list the recipient address. + */ + if (state->cleanup) { + if (SMTPD_STAND_ALONE(state) == 0) { + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) + /* Send actual smtpd_milters list. */ + (void) milter_send(state->milters, state->cleanup); + if (state->saved_flags) + rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d", + state->saved_flags); + } + rec_fputs(state->cleanup, REC_TYPE_MESG, ""); + } + + /* + * PREPEND message headers above our own Received: header. + */ + if (state->prepend) + for (cpp = state->prepend->argv; *cpp; cpp++) + out_fprintf(out_stream, REC_TYPE_NORM, "%s", *cpp); + + /* + * Suppress our own Received: header in the unlikely case that we are an + * intermediate proxy. + */ + if (!proxy || state->xforward.flags == 0) { + out_fprintf(out_stream, REC_TYPE_NORM, + "Received: from %s (%s [%s])", + state->helo_name ? state->helo_name : state->name, + state->name, state->rfc_addr); + +#define VSTRING_STRDUP(s) vstring_strcpy(vstring_alloc(strlen(s) + 1), (s)) + +#ifdef USE_TLS + if (var_smtpd_tls_received_header && state->tls_context) { + int cont = 0; + + vstring_sprintf(state->buffer, + "\t(using %s with cipher %s (%d/%d bits)", + state->tls_context->protocol, + state->tls_context->cipher_name, + state->tls_context->cipher_usebits, + state->tls_context->cipher_algbits); + if (state->tls_context->kex_name && *state->tls_context->kex_name) { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t key-exchange %s", + state->tls_context->kex_name); + if (state->tls_context->kex_curve + && *state->tls_context->kex_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->kex_curve); + else if (state->tls_context->kex_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->kex_bits); + cont = 1; + } + if (state->tls_context->srvr_sig_name + && *state->tls_context->srvr_sig_name) { + if (cont) { + vstring_sprintf_append(state->buffer, " server-signature %s", + state->tls_context->srvr_sig_name); + } else { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t server-signature %s", + state->tls_context->srvr_sig_name); + } + if (state->tls_context->srvr_sig_curve + && *state->tls_context->srvr_sig_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->srvr_sig_curve); + else if (state->tls_context->srvr_sig_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->srvr_sig_bits); + if (state->tls_context->srvr_sig_dgst + && *state->tls_context->srvr_sig_dgst) + vstring_sprintf_append(state->buffer, " server-digest %s", + state->tls_context->srvr_sig_dgst); + } + if (state->tls_context->clnt_sig_name + && *state->tls_context->clnt_sig_name) { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t client-signature %s", + state->tls_context->clnt_sig_name); + if (state->tls_context->clnt_sig_curve + && *state->tls_context->clnt_sig_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->clnt_sig_curve); + else if (state->tls_context->clnt_sig_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->clnt_sig_bits); + if (state->tls_context->clnt_sig_dgst + && *state->tls_context->clnt_sig_dgst) + vstring_sprintf_append(state->buffer, " client-digest %s", + state->tls_context->clnt_sig_dgst); + } + out_fprintf(out_stream, REC_TYPE_NORM, "%s)", STR(state->buffer)); + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + peer_CN = VSTRING_STRDUP(state->tls_context->peer_CN); + comment_sanitize(peer_CN); + issuer_CN = VSTRING_STRDUP(state->tls_context->issuer_CN ? + state->tls_context->issuer_CN : ""); + comment_sanitize(issuer_CN); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (%s))", + STR(peer_CN), STR(issuer_CN), + TLS_CERT_IS_TRUSTED(state->tls_context) ? + "verified OK" : "not verified"); + vstring_free(issuer_CN); + vstring_free(peer_CN); + } else if (var_smtpd_tls_ask_ccert) + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Client did not present a certificate)"); + else + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(No client certificate requested)"); + } + /* RFC 3848 is defined for ESMTP only. */ + if (state->tls_context != 0 + && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0) + rfc3848_sess = "S"; + else +#endif + rfc3848_sess = ""; +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_auth_hdr && state->sasl_username) { + username = VSTRING_STRDUP(state->sasl_username); + comment_sanitize(username); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Authenticated sender: %s)", STR(username)); + vstring_free(username); + } + /* RFC 3848 is defined for ESMTP only. */ + if (state->sasl_username + && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0) + rfc3848_auth = "A"; + else +#endif + rfc3848_auth = ""; + if (state->rcpt_count == 1 && state->recipient) { + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s%s%s id %s" : + "\tby %s (%s) with %s%s%s", + var_myhostname, var_mail_name, + with_protocol, rfc3848_sess, + rfc3848_auth, state->queue_id); + quote_822_local(state->buffer, state->recipient); + out_fprintf(out_stream, REC_TYPE_NORM, + "\tfor <%s>; %s", STR(state->buffer), + mail_date(state->arrival_time.tv_sec)); + } else { + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s%s%s id %s;" : + "\tby %s (%s) with %s%s%s;", + var_myhostname, var_mail_name, + with_protocol, rfc3848_sess, + rfc3848_auth, state->queue_id); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t%s", mail_date(state->arrival_time.tv_sec)); + } +#ifdef RECEIVED_ENVELOPE_FROM + quote_822_local(state->buffer, state->sender); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(envelope-from %s)", STR(state->buffer)); +#endif + } +} + +/* receive_data_message - finish envelope and open message segment */ + +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char *start; + int len; + int curr_rec_type; + int prev_rec_type; + int first = 1; + + /* + * Copy the message content. If the cleanup process has a problem, keep + * reading until the remote stops sending, then complain. Produce typed + * records from the SMTP stream so we can handle data that spans buffers. + * + * XXX Force an empty record when the queue file content begins with + * whitespace, so that it won't be considered as being part of our own + * Received: header. What an ugly Kluge. + * + * XXX Deal with UNIX-style From_ lines at the start of message content + * because sendmail permits it. + */ + for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type) { + if (smtp_get(state->buffer, state->client, var_line_limit, + SMTP_GET_FLAG_NONE) == '\n') + curr_rec_type = REC_TYPE_NORM; + else + curr_rec_type = REC_TYPE_CONT; + start = vstring_str(state->buffer); + len = VSTRING_LEN(state->buffer); + if (first) { + if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { + out_fprintf(out_stream, curr_rec_type, + "X-Mailbox-Line: %s", start); + continue; + } + first = 0; + if (len > 0 && IS_SPACE_TAB(start[0])) + out_record(out_stream, REC_TYPE_NORM, "", 0); + } + if (prev_rec_type != REC_TYPE_CONT && *start == '.' + && (proxy == 0 ? (++start, --len) == 0 : len == 1)) + break; + if (state->err == CLEANUP_STAT_OK) { + if (var_message_limit > 0 && var_message_limit - state->act_size < len + 2) { + state->err = CLEANUP_STAT_SIZE; + msg_warn("%s: queue file size limit exceeded", + state->queue_id ? state->queue_id : "NOQUEUE"); + } else { + state->act_size += len + 2; + if (out_record(out_stream, curr_rec_type, start, len) < 0) + state->err = out_error; + } + } + } + state->where = SMTPD_AFTER_EOM; +} + +/* common_post_message_handling - commit message or report error */ + +static int common_post_message_handling(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + const char *err; + VSTRING *why = 0; + int saved_err; + const CLEANUP_STAT_DETAIL *detail; + +#define IS_SMTP_REJECT(s) \ + (((s)[0] == '4' || (s)[0] == '5') \ + && ISDIGIT((s)[1]) && ISDIGIT((s)[2]) \ + && ((s)[3] == '\0' || (s)[3] == ' ' || (s)[3] == '-')) + + if (state->err == CLEANUP_STAT_OK + && SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_eod(state)) != 0) { + smtpd_chat_reply(state, "%s", err); + if (proxy) { + smtpd_proxy_close(state); + } else { + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + return (-1); + } + + /* + * Send the end of DATA and finish the proxy connection. Set the + * CLEANUP_STAT_PROXY error flag in case of trouble. + */ + if (proxy) { + if (state->err == CLEANUP_STAT_OK) { + (void) proxy->cmd(state, SMTPD_PROX_WANT_ANY, "."); + if (state->err == CLEANUP_STAT_OK && + *STR(proxy->reply) != '2') + state->err = CLEANUP_STAT_CONT; + } + } + + /* + * Flush out access table actions that are delegated to the cleanup + * server. There is similar code at the beginning of the DATA command. + * + * Send the end-of-segment markers and finish the queue file record stream. + */ + else { + if (state->err == CLEANUP_STAT_OK) { + rec_fputs(state->cleanup, REC_TYPE_XTRA, ""); + if (state->saved_filter) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", + state->saved_filter); + if (state->saved_redirect) + rec_fprintf(state->cleanup, REC_TYPE_RDR, "%s", + state->saved_redirect); + if (state->saved_bcc) { + char **cpp; + + for (cpp = state->saved_bcc->argv; *cpp; cpp++) { + rec_fprintf(state->cleanup, REC_TYPE_RCPT, "%s", + *cpp); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, DSN_NOTIFY_NEVER); + } + } + if (state->saved_flags) + rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d", + state->saved_flags); +#ifdef DELAY_ACTION + if (state->saved_delay) + rec_fprintf(state->cleanup, REC_TYPE_DELAY, "%d", + state->saved_delay); +#endif + if (vstream_ferror(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + } + if (state->err == CLEANUP_STAT_OK) + if (rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 + || vstream_fflush(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + if (state->err == 0) { + why = vstring_alloc(10); + state->err = mail_stream_finish(state->dest, why); + if (IS_SMTP_REJECT(STR(why))) + printable_except(STR(why), ' ', "\r\n"); + else + printable(STR(why), ' '); + } else + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + + /* + * XXX If we lose the cleanup server while it is editing a queue file, + * the Postfix SMTP server will be out of sync with Milter applications. + * Sending an ABORT to the Milters is not sufficient to restore + * synchronization, because there may be any number of Milter replies + * already in flight. Destroying and recreating the Milters (and faking + * the connect and ehlo events) is too much trouble for testing and + * maintenance. Workaround: force the Postfix SMTP server to hang up with + * a 421 response in the rare case that the cleanup server breaks AND + * that the remote SMTP client continues the session after end-of-data. + * + * XXX Should use something other than CLEANUP_STAT_WRITE when we lose + * contact with the cleanup server. This requires changes to the + * mail_stream module and its users (smtpd, qmqpd, perhaps sendmail). + * + * XXX See exception below in code that overrides state->access_denied for + * compliance with RFC 2821 Sec 3.1. + */ + if (state->milters != 0 && (state->err & CLEANUP_STAT_WRITE) != 0) + state->access_denied = mystrdup("421 4.3.0 Mail system error"); + + /* + * Handle any errors. One message may suffer from multiple errors, so + * complain only about the most severe error. Forgive any previous client + * errors when a message was received successfully. + * + * See also: qmqpd.c + */ + if (state->err == CLEANUP_STAT_OK) { + state->error_count = 0; + state->error_mask = 0; + state->junk_cmds = 0; + if (proxy) + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + else if (SMTPD_PROCESSING_BDAT(state)) + smtpd_chat_reply(state, + "250 2.0.0 Ok: %ld bytes queued as %s", + (long) state->act_size, state->queue_id); + else + smtpd_chat_reply(state, + "250 2.0.0 Ok: queued as %s", state->queue_id); + } else if (why && IS_SMTP_REJECT(STR(why))) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", STR(why)); + } else if ((state->err & CLEANUP_STAT_DEFER) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_DEFER); + if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_BAD) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } else if ((state->err & CLEANUP_STAT_SIZE) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_SIZE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_HOPS) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_HOPS); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_CONT) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_CONT); + if (proxy) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { + state->error_mask |= MAIL_ERROR_RESOURCE; + detail = cleanup_stat_detail(CLEANUP_STAT_WRITE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_PROXY) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } + + /* + * By popular command: the proxy's end-of-data reply. + */ + if (proxy) + msg_info("proxy-%s: %s: %s;%s", + (state->err == CLEANUP_STAT_OK) ? "accept" : "reject", + state->where, STR(proxy->reply), smtpd_whatsup(state)); + + /* + * Cleanup. The client may send another MAIL command. + */ + saved_err = state->err; + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + if (why) + vstring_free(why); + return (saved_err); +} + +/* skip_bdat - skip content and respond to BDAT error */ + +static int skip_bdat(SMTPD_STATE *state, off_t chunk_size, + bool final_chunk, const char *format,...) +{ + va_list ap; + off_t done; + off_t len; + + /* + * Read and discard content from the remote SMTP client. TODO: drop the + * connection in case of overload. + */ + for (done = 0; done < chunk_size; done += len) { + if ((len = chunk_size - done) > VSTREAM_BUFSIZE) + len = VSTREAM_BUFSIZE; + smtp_fread_buf(state->buffer, len, state->client); + } + + /* + * Send the response to the remote SMTP client. + */ + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); + + /* + * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET. + */ + if (final_chunk) + mail_reset(state); + else + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); +} + +/* bdat_cmd - process BDAT command */ + +static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + off_t chunk_size; + bool final_chunk; + off_t done; + off_t read_len; + char *start; + int len; + int curr_rec_type; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Hang up if the BDAT command is disabled. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented"); + return (-1); + } + + /* + * Hang up if the BDAT command is malformed. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (argc < 2 || argc > 3 || !alldig(argv[1].strval) + || (chunk_size = off_cvt_string(argv[1].strval)) < 0 + || ((final_chunk = (argc == 3)) + && strcasecmp(argv[2].strval, "LAST") != 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + msg_warn("%s: malformed BDAT command syntax from %s: %.100s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr, printable(vstring_str(state->buffer), '?')); + smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]"); + return (-1); + } + + /* + * Block abuse involving empty chunks (alternatively, we could count + * "BDAT 0" as a "NOOP", but then we would have to refactor the code that + * enforces the junk command limit). Clients that send a message as a + * sequence of "BDAT 1" should not be a problem: the Postfix BDAT + * implementation should be efficient enough to handle that. + */ + if (chunk_size == 0 && !final_chunk) { + msg_warn("%s: null BDAT request from %s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "551 5.7.1 Null BDAT request"); + } + + /* + * BDAT commands may be pipelined within a MAIL transaction. After a BDAT + * request fails, keep accepting BDAT requests and skipping BDAT payloads + * to maintain synchronization with the remote SMTP client, until the + * client sends BDAT LAST or RSET. + */ + if (state->bdat_state == SMTPD_BDAT_STAT_ERROR) + return skip_bdat(state, chunk_size, final_chunk, + "551 5.0.0 Discarded %ld bytes after earlier error", + (long) chunk_size); + + /* + * Special handling for the first BDAT command in a MAIL transaction, + * treating it as a kind of "DATA" command for the purpose of policy + * evaluation. + */ + if (!SMTPD_PROCESSING_BDAT(state)) { + + /* + * With ESMTP command pipelining a client may send BDAT before the + * server has replied to all RCPT commands. For this reason we cannot + * treat BDAT without valid recipients as a protocol error. Worse, + * RFC 3030 does not discuss the role of BDAT commands in RFC 2920 + * command groups (batches of commands that may be sent without + * waiting for a response to each individual command). Therefore we + * have to allow for clients that pipeline the entire SMTP session + * after EHLO, including multiple MAIL transactions. + */ + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + /* TODO: maybe remove this from the DATA and BDAT handlers. */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + return skip_bdat(state, chunk_size, final_chunk, + "503 5.5.1 Error: need RCPT command"); + } else { + return skip_bdat(state, chunk_size, final_chunk, + "554 5.5.1 Error: no valid recipients"); + } + } + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_data(state)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + SMTPD_CMD_DATA) != 0) { + return skip_bdat(state, chunk_size, final_chunk, + "%s", STR(proxy->reply)); + } + } + /* Block too large chunks. */ + if (var_message_limit > 0 + && state->act_size > var_message_limit - chunk_size) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("%s: BDAT request from %s exceeds message size limit", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "552 5.3.4 Chunk exceeds message size limit"); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. TODO: store this in its own data structure, or in + * SMTPD_STATE. + */ + proxy = state->proxy; + if (proxy) { + out_stream = proxy->stream; + out_record = proxy->rec_put; + out_fprintf = proxy->rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } + if (!SMTPD_PROCESSING_BDAT(state)) { + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + if (state->bdat_get_buffer == 0) + state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE); + else + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = 0; + } + state->bdat_state = SMTPD_BDAT_STAT_OK; + state->where = SMTPD_AFTER_BDAT; + + /* + * Copy the message content. If the cleanup process has a problem, keep + * reading until the remote stops sending, then complain. Produce typed + * records from the SMTP stream so we can handle data that spans buffers. + */ + + /* + * Instead of reading the entire BDAT chunk into memory, read the chunk + * one fragment at a time. The loops below always make one iteration, to + * avoid code duplication for the "BDAT 0 LAST" case (empty chunk). + */ + done = 0; + do { + + /* + * Do not skip the smtp_fread_buf() call if read_len == 0. We still + * need the side effects which include resetting the buffer write + * position. Skipping the call would invalidate the buffer state. + * + * Caution: smtp_fread_buf() will long jump after EOF or timeout. + */ + if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE) + read_len = VSTREAM_BUFSIZE; + smtp_fread_buf(state->buffer, read_len, state->client); + state->bdat_get_stream = vstream_memreopen( + state->bdat_get_stream, state->buffer, O_RDONLY); + + /* + * Read lines from the fragment. The last line may continue in the + * next fragment, or in the next chunk. + */ + do { + if (smtp_get_noexcept(state->bdat_get_buffer, + state->bdat_get_stream, + var_line_limit, + SMTP_GET_FLAG_APPEND) == '\n') { + /* Stopped at end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else if (!vstream_feof(state->bdat_get_stream)) { + /* Stopped at var_line_limit. */ + curr_rec_type = REC_TYPE_CONT; + } else if (VSTRING_LEN(state->bdat_get_buffer) > 0 + && final_chunk && read_len == chunk_size - done) { + /* Stopped at final chunk end; handle missing end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else { + /* Stopped at fragment end; empty buffer or not at chunk end. */ + /* Skip the out_record() and VSTRING_RESET() calls below. */ + break; + } + start = vstring_str(state->bdat_get_buffer); + len = VSTRING_LEN(state->bdat_get_buffer); + if (state->err == CLEANUP_STAT_OK) { + if (var_message_limit > 0 + && var_message_limit - state->act_size < len + 2) { + state->err = CLEANUP_STAT_SIZE; + msg_warn("%s: queue file size limit exceeded", + state->queue_id ? state->queue_id : "NOQUEUE"); + } else { + state->act_size += len + 2; + if (*start == '.' && proxy != 0 + && state->bdat_prev_rec_type != REC_TYPE_CONT) + if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0) + state->err = out_error; + if (state->err == CLEANUP_STAT_OK + && out_record(out_stream, curr_rec_type, + vstring_str(state->bdat_get_buffer), + VSTRING_LEN(state->bdat_get_buffer)) < 0) + state->err = out_error; + } + } + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = curr_rec_type; + } while (!vstream_feof(state->bdat_get_stream)); + done += read_len; + } while (done < chunk_size); + + /* + * Special handling for BDAT LAST (successful or unsuccessful). + */ + if (final_chunk) { + state->where = SMTPD_AFTER_EOM; + return common_post_message_handling(state); + } + + /* + * Unsuccessful non-final BDAT command. common_post_message_handling() + * resets all MAIL transaction state including BDAT state. To avoid + * useless error messages due to pipelined BDAT commands, enter the + * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT + * payloads. + */ + else if (state->err != CLEANUP_STAT_OK) { + /* NOT: state->where = SMTPD_AFTER_EOM; */ + (void) common_post_message_handling(state); + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); + } + + /* + * Successful non-final BDAT command. + */ + else { + smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size); + return (0); + } +} + +/* rset_cmd - process RSET */ + +static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * Sanity checks. + */ + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: RSET"); + return (-1); + } + + /* + * Restore state to right after HELO/EHLO command. + */ + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* noop_cmd - process NOOP */ + +static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * XXX 2821 incompatibility: Section 4.1.1.9 says that NOOP can have a + * parameter string which is to be ignored. NOOP instructions with + * parameters? Go figure. + * + * RFC 2821 violates RFC 821, which says that NOOP takes no parameters. + */ +#ifdef RFC821_SYNTAX + + /* + * Sanity checks. + */ + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: NOOP"); + return (-1); + } +#endif + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* vrfy_cmd - process VRFY */ + +static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err = 0; + int rate; + int smtputf8 = 0; + int saved_flags; + + /* + * The SMTP standard (RFC 821) disallows unquoted special characters in + * the VRFY argument. Common practice violates the standard, however. + * Postfix accommodates common practice where it violates the standard. + * + * XXX Impedance mismatch! The SMTP command tokenizer preserves quoting, + * whereas the recipient restrictions checks expect unquoted (internal) + * address forms. Therefore we must parse out the address, or we must + * stop doing recipient restriction checks and lose the opportunity to + * say "user unknown" at the SMTP port. + * + * XXX 2821 incompatibility and brain damage: Section 4.5.1 requires that + * VRFY is implemented. RFC 821 specifies that VRFY is optional. It gets + * even worse: section 3.5.3 says that a 502 (command recognized but not + * implemented) reply is not fully compliant. + * + * Thus, an RFC 2821 compliant implementation cannot refuse to supply + * information in reply to VRFY queries. That is simply bogus. The only + * reply we could supply is a generic 252 reply. This causes spammers to + * add tons of bogus addresses to their mailing lists (spam harvesting by + * trying out large lists of potential recipient names with VRFY). + */ +#define SLOPPY 0 + + if (var_disable_vrfy_cmd) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "502 5.5.1 VRFY command is disabled"); + return (-1); + } + /* Fix 20140707: handle missing address. */ + if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0 + && argc > 1 && strcasecmp(argv[argc - 1].strval, "SMTPUTF8") == 0) { + argc--; /* RFC 6531 */ + smtputf8 = 1; + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: VRFY address%s", + var_smtputf8_enable ? " [SMTPUTF8]" : ""); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_crcpt_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_crcpt_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Recipient address rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s", + state->addr); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0 + && (err[0] == '5' || err[0] == '4')) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (extract_addr(state, argv + 1, REJECT_EMPTY_ADDR, SLOPPY, smtputf8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + /* Fix 20140707: Check the VRFY command. */ + if (smtputf8 == 0 && var_strict_smtputf8) { + if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + mail_reset(state); + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address"); + return (-1); + } + } + /* Use state->addr_buf, with the unquoted result from extract_addr() */ + if (SMTPD_STAND_ALONE(state) == 0) { + /* Fix 20161206: allow UTF8 in smtpd_recipient_restrictions. */ + saved_flags = state->flags; + if (smtputf8) + state->flags |= SMTPD_FLAG_SMTPUTF8; + err = smtpd_check_rcpt(state, STR(state->addr_buf)); + state->flags = saved_flags; + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * XXX 2821 new feature: Section 3.5.1 requires that the VRFY response is + * either "full name <user@domain>" or "user@domain". Postfix replies + * with the string that was provided by the client, whether or not it is + * in fully qualified domain form and the address is in <>. + * + * Reply code 250 is reserved for the case where the address is verified; + * reply code 252 should be used when no definitive certainty exists. + */ + smtpd_chat_reply(state, "252 2.0.0 %s", argv[1].strval); + return (0); +} + +/* etrn_cmd - process ETRN command */ + +static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + + /* + * Sanity checks. + */ + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 Error: send HELO/EHLO first"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0 + && (err[0] == '5' || err[0] == '4')) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 Error: MAIL transaction in progress"); + return (-1); + } + if (argc != 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 Syntax: ETRN domain"); + return (-1); + } + if (argv[1].strval[0] == '@' || argv[1].strval[0] == '#') + argv[1].strval++; + + /* + * As an extension to RFC 1985 we also allow an RFC 2821 address literal + * enclosed in []. + * + * XXX There does not appear to be an ETRN parameter to indicate that the + * domain name is UTF-8. + */ + if (!valid_hostname(argv[1].strval, DONT_GRIPE) + && !valid_mailhost_literal(argv[1].strval, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Error: invalid parameter syntax"); + return (-1); + } + + /* + * XXX The implementation borrows heavily from the code that implements + * UCE restrictions. These typically return 450 or 550 when a request is + * rejected. RFC 1985 requires that 459 be sent when the server refuses + * to perform the request. + */ + if (SMTPD_STAND_ALONE(state)) { + msg_warn("do not use ETRN in \"sendmail -bs\" mode"); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + } + if ((err = smtpd_check_etrn(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + switch (flush_send_site(argv[1].strval)) { + case FLUSH_STAT_OK: + smtpd_chat_reply(state, "250 Queuing started"); + return (0); + case FLUSH_STAT_DENY: + msg_warn("reject: ETRN %.100s... from %s", + argv[1].strval, state->namaddr); + smtpd_chat_reply(state, "459 <%s>: service unavailable", + argv[1].strval); + return (-1); + case FLUSH_STAT_BAD: + msg_warn("bad ETRN %.100s... from %s", argv[1].strval, state->namaddr); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + default: + msg_warn("unable to talk to fast flush service"); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + } +} + +/* quit_cmd - process QUIT command */ + +static int quit_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv) +{ + int out_pending = vstream_bufstat(state->client, VSTREAM_BST_OUT_PEND); + + /* + * Don't bother checking the syntax. + */ + smtpd_chat_reply(state, "221 2.0.0 Bye"); + + /* + * When the "." and quit replies are pipelined, make sure they are + * flushed now, to avoid repeated mail deliveries in case of a crash in + * the "clean up before disconnect" code. + * + * XXX When this was added in Postfix 2.1 we used vstream_fflush(). As of + * Postfix 2.3 we use smtp_flush() for better error reporting. + */ + if (out_pending > 0) + smtp_flush(state->client); + return (0); +} + +/* xclient_cmd - override SMTP client attributes */ + +static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_TOKEN *argp; + char *raw_value; + char *attr_value; + const char *bare_value; + char *attr_name; + int update_namaddr = 0; + int name_status; + static const NAME_CODE peer_codes[] = { + XCLIENT_UNAVAILABLE, SMTPD_PEER_CODE_PERM, + XCLIENT_TEMPORARY, SMTPD_PEER_CODE_TEMP, + 0, SMTPD_PEER_CODE_OK, + }; + static const NAME_CODE proto_names[] = { + MAIL_PROTO_SMTP, 1, + MAIL_PROTO_ESMTP, 2, + 0, -1, + }; + int got_helo = 0; + int got_proto = 0; + +#ifdef USE_SASL_AUTH + int got_login = 0; + char *saved_username; + +#endif + + /* + * Sanity checks. + * + * XXX The XCLIENT command will override its own access control, so that + * connection count/rate restrictions can be correctly simulated. + */ + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...", + XCLIENT_CMD); + return (-1); + } + if (xclient_hosts && xclient_hosts->error) + cant_permit_command(state, XCLIENT_CMD); + if (!xclient_allowed) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); + return (-1); + } +#define STREQ(x,y) (strcasecmp((x), (y)) == 0) +#define UPDATE_STR(s, v) do { \ + const char *_v = (v); \ + if (s) myfree(s); \ + s = (_v) ? mystrdup(_v) : 0; \ + } while(0) + + /* + * Initialize. + */ + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + + /* + * Iterate over all attribute=value elements. + */ + for (argp = argv + 1; argp < argv + argc; argp++) { + attr_name = argp->strval; + + if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected"); + return (-1); + } + if (strlen(raw_value) > 255) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long"); + return (-1); + } + + /* + * Backwards compatibility: Postfix prior to version 2.3 does not + * xtext encode attribute values. + */ + attr_value = xtext_unquote(state->expand_buf, raw_value) ? + STR(state->expand_buf) : raw_value; + + /* + * For safety's sake mask non-printable characters. We'll do more + * specific censoring later. + */ + printable(attr_value, '?'); + + /* + * NAME=substitute SMTP client hostname (and reverse/forward name, in + * case of success). Also updates the client hostname lookup status + * code. + */ + if (STREQ(attr_name, XCLIENT_NAME)) { + name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value); + if (name_status != SMTPD_PEER_CODE_OK) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_NAME, attr_value); + return (-1); + } + } + state->name_status = name_status; + UPDATE_STR(state->name, attr_value); + update_namaddr = 1; + if (name_status == SMTPD_PEER_CODE_OK) { + UPDATE_STR(state->reverse_name, attr_value); + state->reverse_name_status = name_status; + } + } + + /* + * REVERSE_NAME=substitute SMTP client reverse hostname. Also updates + * the client reverse hostname lookup status code. + */ + else if (STREQ(attr_name, XCLIENT_REVERSE_NAME)) { + name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value); + if (name_status != SMTPD_PEER_CODE_OK) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_REVERSE_NAME, attr_value); + return (-1); + } + } + state->reverse_name_status = name_status; + UPDATE_STR(state->reverse_name, attr_value); + } + + /* + * ADDR=substitute SMTP client network address. + */ + else if (STREQ(attr_name, XCLIENT_ADDR)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_ADDR_UNKNOWN; + bare_value = attr_value; + } else { + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_ADDR, attr_value); + return (-1); + } + } + UPDATE_STR(state->addr, bare_value); + UPDATE_STR(state->rfc_addr, attr_value); +#ifdef HAS_IPV6 + if (strncasecmp(attr_value, INET_PROTO_NAME_IPV6 ":", + sizeof(INET_PROTO_NAME_IPV6 ":") - 1) == 0) + state->addr_family = AF_INET6; + else +#endif + state->addr_family = AF_INET; + update_namaddr = 1; + } + + /* + * PORT=substitute SMTP client port number. + */ + else if (STREQ(attr_name, XCLIENT_PORT)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_PORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->port, attr_value); + update_namaddr = 1; + } + + /* + * HELO=substitute SMTP client HELO parameter. Censor special + * characters that could mess up message headers. + */ + else if (STREQ(attr_name, XCLIENT_HELO)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_HELO_UNKNOWN; + } else { + if (strlen(attr_value) > VALID_HOSTNAME_LEN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_HELO, attr_value); + return (-1); + } + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->helo_name, attr_value); + got_helo = 1; + } + + /* + * PROTO=SMTP protocol name. + */ + else if (STREQ(attr_name, XCLIENT_PROTO)) { + if (name_code(proto_names, NAME_CODE_FLAG_NONE, attr_value) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_PROTO, attr_value); + return (-1); + } + UPDATE_STR(state->protocol, uppercase(attr_value)); + got_proto = 1; + } + + /* + * LOGIN=sasl_username. Sets the authentication method as XCLIENT. + * This can be used even if SASL authentication is turned off in + * main.cf. We can't make it easier than that. + */ +#ifdef USE_SASL_AUTH + else if (STREQ(attr_name, XCLIENT_LOGIN)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE) == 0) { + smtpd_sasl_auth_extern(state, attr_value, XCLIENT_CMD); + got_login = 1; + } + } +#endif + + /* + * DESTADDR=substitute SMTP server network address. + */ + else if (STREQ(attr_name, XCLIENT_DESTADDR)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = SERVER_ADDR_UNKNOWN; + bare_value = attr_value; + } else { + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_DESTADDR, attr_value); + return (-1); + } + } + UPDATE_STR(state->dest_addr, bare_value); + /* XXX Require same address family as client address. */ + } + + /* + * DESTPORT=substitute SMTP server port number. + */ + else if (STREQ(attr_name, XCLIENT_DESTPORT)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = SERVER_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_DESTPORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->dest_port, attr_value); + } + + /* + * Unknown attribute name. Complain. + */ + else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s", + XCLIENT_CMD, attr_name); + return (-1); + } + } + + /* + * Update the combined name and address when either has changed. + */ + if (update_namaddr) { + if (state->namaddr) + myfree(state->namaddr); + state->namaddr = + SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, state->port); + } + + /* + * XXX Compatibility: when the client issues XCLIENT then we have to go + * back to initial server greeting stage, otherwise we can't correctly + * simulate smtpd_client_restrictions (with smtpd_delay_reject=0) and + * Milter connect restrictions. + * + * XXX Compatibility: for accurate simulation we must also reset the HELO + * information. We keep the information if it was specified in the + * XCLIENT command. + * + * XXX The client connection count/rate control must be consistent in its + * use of client address information in connect and disconnect events. We + * re-evaluate xclient so that we correctly simulate connection + * concurrency and connection rate restrictions. + * + * XXX Duplicated from smtpd_proto(). + */ + xclient_allowed = + namadr_list_match(xclient_hosts, state->name, state->addr); + /* NOT: tls_reset() */ + if (got_helo == 0) + helo_reset(state); + if (got_proto == 0 && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_SMTP); + } +#ifdef USE_SASL_AUTH + /* XXX What if they send the parameters via multiple commands? */ + if (got_login == 0) + smtpd_sasl_auth_reset(state); + if (smtpd_sasl_is_active(state)) { + if (got_login) + saved_username = mystrdup(state->sasl_username); + smtpd_sasl_deactivate(state); +#ifdef USE_TLS + if (state->tls_context != 0) /* TLS from XCLIENT proxy? */ + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + else +#endif + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); + if (got_login) { + smtpd_sasl_auth_extern(state, saved_username, XCLIENT_CMD); + myfree(saved_username); + } + } +#endif + chat_reset(state, 0); + mail_reset(state); + rcpt_reset(state); + if (state->milters) + milter_disc_event(state->milters); + /* Following duplicates the top-level connect/disconnect handler. */ + teardown_milters(state); + setup_milters(state); + vstream_longjmp(state->client, SMTP_ERR_NONE); + return (0); +} + +/* xforward_cmd - forward logging attributes */ + +static int xforward_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_TOKEN *argp; + char *raw_value; + char *attr_value; + const char *bare_value; + char *attr_name; + int updated = 0; + static const NAME_CODE xforward_flags[] = { + XFORWARD_NAME, SMTPD_STATE_XFORWARD_NAME, + XFORWARD_ADDR, SMTPD_STATE_XFORWARD_ADDR, + XFORWARD_PORT, SMTPD_STATE_XFORWARD_PORT, + XFORWARD_PROTO, SMTPD_STATE_XFORWARD_PROTO, + XFORWARD_HELO, SMTPD_STATE_XFORWARD_HELO, + XFORWARD_IDENT, SMTPD_STATE_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTPD_STATE_XFORWARD_DOMAIN, + 0, 0, + }; + static const char *context_name[] = { + MAIL_ATTR_RWR_LOCAL, /* Postfix internal form */ + MAIL_ATTR_RWR_REMOTE, /* Postfix internal form */ + }; + static const NAME_CODE xforward_to_context[] = { + XFORWARD_DOM_LOCAL, 0, /* XFORWARD representation */ + XFORWARD_DOM_REMOTE, 1, /* XFORWARD representation */ + 0, -1, + }; + int flag; + int context_code; + + /* + * Sanity checks. + */ + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...", + XFORWARD_CMD); + return (-1); + } + if (xforward_hosts && xforward_hosts->error) + cant_permit_command(state, XFORWARD_CMD); + if (!xforward_allowed) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); + return (-1); + } + + /* + * Initialize. + */ + if (state->xforward.flags == 0) + smtpd_xforward_preset(state); + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + + /* + * Iterate over all attribute=value elements. + */ + for (argp = argv + 1; argp < argv + argc; argp++) { + attr_name = argp->strval; + + if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected"); + return (-1); + } + if (strlen(raw_value) > 255) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long"); + return (-1); + } + + /* + * Backwards compatibility: Postfix prior to version 2.3 does not + * xtext encode attribute values. + */ + attr_value = xtext_unquote(state->expand_buf, raw_value) ? + STR(state->expand_buf) : raw_value; + + /* + * For safety's sake mask non-printable characters. We'll do more + * specific censoring later. + */ + printable(attr_value, '?'); + + flag = name_code(xforward_flags, NAME_CODE_FLAG_NONE, attr_name); + switch (flag) { + + /* + * NAME=up-stream host name, not necessarily in the DNS. Censor + * special characters that could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_NAME: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_NAME, attr_value); + return (-1); + } + } + UPDATE_STR(state->xforward.name, attr_value); + break; + + /* + * ADDR=up-stream host network address, not necessarily on the + * Internet. Censor special characters that could mess up message + * headers. + */ + case SMTPD_STATE_XFORWARD_ADDR: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_ADDR_UNKNOWN; + bare_value = attr_value; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if ((bare_value = valid_mailhost_addr(attr_value, DONT_GRIPE)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_ADDR, attr_value); + return (-1); + } + } + UPDATE_STR(state->xforward.addr, bare_value); + UPDATE_STR(state->xforward.rfc_addr, attr_value); + break; + + /* + * PORT=up-stream port number. + */ + case SMTPD_STATE_XFORWARD_PORT: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_PORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->xforward.port, attr_value); + break; + + /* + * HELO=hostname that the up-stream MTA introduced itself with + * (not necessarily SMTP HELO). Censor special characters that + * could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_HELO: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_HELO_UNKNOWN; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.helo_name, attr_value); + break; + + /* + * PROTO=up-stream protocol, not necessarily SMTP or ESMTP. + * Censor special characters that could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_PROTO: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_PROTO_UNKNOWN; + } else { + if (strlen(attr_value) > 64) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_PROTO, attr_value); + return (-1); + } + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.protocol, attr_value); + break; + + /* + * IDENT=local message identifier on the up-stream MTA. Censor + * special characters that could mess up logging or macro + * expansions. + */ + case SMTPD_STATE_XFORWARD_IDENT: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_IDENT_UNKNOWN; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.ident, attr_value); + break; + + /* + * DOMAIN=local or remote. + */ + case SMTPD_STATE_XFORWARD_DOMAIN: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) + attr_value = XFORWARD_DOM_LOCAL; + if ((context_code = name_code(xforward_to_context, + NAME_CODE_FLAG_NONE, + attr_value)) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_DOMAIN, attr_value); + return (-1); + } + UPDATE_STR(state->xforward.domain, context_name[context_code]); + break; + + /* + * Unknown attribute name. Complain. + */ + default: + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s", + XFORWARD_CMD, attr_name); + return (-1); + } + updated |= flag; + } + state->xforward.flags |= updated; + + /* + * Update the combined name and address when either has changed. Use only + * the name when no address is available. + */ + if (updated & (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR)) { + if (state->xforward.namaddr) + myfree(state->xforward.namaddr); + state->xforward.namaddr = + IS_AVAIL_CLIENT_ADDR(state->xforward.addr) ? + SMTPD_BUILD_NAMADDRPORT(state->xforward.name, + state->xforward.addr, + state->xforward.port) : + mystrdup(state->xforward.name); + } + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* chat_reset - notify postmaster and reset conversation log */ + +static void chat_reset(SMTPD_STATE *state, int threshold) +{ + + /* + * Notify the postmaster if there were errors. This usually indicates a + * client configuration problem, or that someone is trying nasty things. + * Either is significant enough to bother the postmaster. XXX Can't + * report problems when running in stand-alone mode: postmaster notices + * require availability of the cleanup service. + */ + if (state->history != 0 && state->history->argc > threshold) { + if (SMTPD_STAND_ALONE(state) == 0 + && (state->error_mask & state->notify_mask)) + smtpd_chat_notify(state); + state->error_mask = 0; + smtpd_chat_reset(state); + } +} + +#ifdef USE_TLS + +/* smtpd_start_tls - turn on TLS or force disconnect */ + +static void smtpd_start_tls(SMTPD_STATE *state) +{ + int rate; + int cert_present; + int requirecert; + +#ifdef USE_TLSPROXY + + /* + * This is non-production code, for tlsproxy(8) load testing only. It + * implements enough to enable some Postfix features that depend on TLS + * encryption. + * + * To insert tlsproxy(8) between this process and the SMTP client, we swap + * the file descriptors between the state->tlsproxy and state->client + * VSTREAMS, so that we don't lose all the user-configurable + * state->client attributes (such as longjump buffers or timeouts). + * + * As we implement tlsproxy support in the Postfix SMTP client we should + * develop a usable abstraction that encapsulates this stream plumbing in + * a library module. + */ + vstream_control(state->tlsproxy, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); + vstream_control(state->client, CA_VSTREAM_CTL_SWAP_FD(state->tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(state->tlsproxy); /* direct-to-client stream! */ + state->tlsproxy = 0; + + /* + * After plumbing the plaintext stream, receive the TLS context object. + * For this we must use the same VSTREAM buffer that we also use to + * receive subsequent SMTP commands. The attribute protocol is robust + * enough that an adversary cannot inject their own bogus TLS context + * attributes into the stream. + */ + state->tls_context = tls_proxy_context_receive(state->client); + + /* + * XXX Maybe it is better to send this information to tlsproxy(8) when + * requesting service, effectively making a remote tls_server_start() + * call. + */ + requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls); + +#else /* USE_TLSPROXY */ + TLS_SERVER_START_PROPS props; + static char *cipher_grade; + static VSTRING *cipher_exclusions; + + /* + * Wrapper mode uses a dedicated port and always requires TLS. + * + * XXX In non-wrapper mode, it is possible to require client certificate + * verification without requiring TLS. Since certificates can be verified + * only while TLS is turned on, this means that Postfix will happily + * perform SMTP transactions when the client does not use the STARTTLS + * command. For this reason, Postfix does not require client certificate + * verification unless TLS is required. + * + * The cipher grade and exclusions don't change between sessions. Compute + * just once and cache. + */ +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + if (cipher_grade == 0) { + cipher_grade = var_smtpd_enforce_tls ? + var_smtpd_tls_mand_ciph : var_smtpd_tls_ciph; + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_excl_ciph); + if (var_smtpd_enforce_tls) + ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_mand_excl); + if (ask_client_cert) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + } + + /* + * Perform the TLS handshake now. Check the client certificate + * requirements later, if necessary. + */ + requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls); + + state->tls_context = + TLS_SERVER_START(&props, + ctx = smtpd_tls_ctx, + stream = state->client, + fd = -1, + timeout = var_smtpd_starttls_tmout, + requirecert = requirecert, + serverid = state->service, + namaddr = state->namaddr, + cipher_grade = cipher_grade, + cipher_exclusions = STR(cipher_exclusions), + mdalg = var_smtpd_tls_fpt_dgst); + +#endif /* USE_TLSPROXY */ + + /* + * For new (i.e. not re-used) TLS sessions, increment the client's new + * TLS session rate counter. We enforce the limit here only for human + * factors reasons (reduce the WTF factor), even though it is too late to + * save the CPU that was already burnt on PKI ops. The real safety + * mechanism applies with future STARTTLS commands (or wrappermode + * connections), prior to the SSL handshake. + * + * XXX The client event count/rate control must be consistent in its use of + * client address information in connect and disconnect events. For now + * we exclude xclient authorized hosts from event count/rate control. + */ + if (var_smtpd_cntls_limit > 0 + && (state->tls_context == 0 || state->tls_context->session_reused == 0) + && SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("New TLS session rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + if (state->tls_context) + smtpd_chat_reply(state, + "421 4.7.0 %s Error: too many new TLS sessions from %s", + var_myhostname, state->namaddr); + /* XXX Use regular return to signal end of session. */ + vstream_longjmp(state->client, SMTP_ERR_QUIET); + } + + /* + * When the TLS handshake fails, the conversation is in an unknown state. + * There is nothing we can do except to disconnect from the client. + */ + if (state->tls_context == 0) + vstream_longjmp(state->client, SMTP_ERR_EOF); + + /* + * If we are requiring verified client certs, enforce the constraint + * here. We have a usable TLS session with the client, so no need to + * disable I/O, ... we can even be polite and send "421 ...". + */ + if (requirecert && TLS_CERT_IS_TRUSTED(state->tls_context) == 0) { + + /* + * Fetch and reject the next command (should be EHLO), then + * disconnect (side-effect of returning "421 ...". + */ + cert_present = TLS_CERT_IS_PRESENT(state->tls_context); + msg_info("NOQUEUE: abort: TLS from %s: %s", + state->namaddr, cert_present ? + "Client certificate not trusted" : + "No client certificate presented"); + smtpd_chat_query(state); + smtpd_chat_reply(state, "421 4.7.1 %s Error: %s", + var_myhostname, cert_present ? + "Client certificate not trusted" : + "No client certificate presented"); + state->error_mask |= MAIL_ERROR_POLICY; + return; + } + + /* + * When TLS is turned on, we may offer AUTH methods that would not be + * offered within a plain-text session. + * + * XXX Always refresh SASL the mechanism list after STARTTLS. Dovecot + * responses may depend on whether the SMTP connection is encrypted. + */ +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + /* Non-wrappermode, presumably. */ + if (smtpd_sasl_is_active(state)) { + smtpd_sasl_auth_reset(state); + smtpd_sasl_deactivate(state); + } + /* Wrappermode and non-wrappermode. */ + if (smtpd_sasl_is_active(state) == 0) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + } +#endif +} + +/* starttls_cmd - respond to STARTTLS */ + +static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + const char *err; + int rate; + + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: STARTTLS"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) { + if (err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + /* Sendmail compatibility: map 4xx into 454. */ + else if (err[0] == '4') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "454 4.3.0 Try again later"); + return (-1); + } + } + if (state->tls_context != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "554 5.5.1 Error: TLS already active"); + return (-1); + } + if (var_smtpd_use_tls == 0 + || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented"); + return (-1); + } +#ifdef USE_TLSPROXY + + /* + * Note: state->tlsproxy is left open when smtp_flush() calls longjmp(), + * so we garbage-collect the VSTREAM in smtpd_state_reset(). + */ +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_SERVER | TLS_PROXY_FLAG_SEND_CONTEXT) + + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); + if (state->tlsproxy == 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + /* RFC 3207 Section 4. */ + smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem"); + return (-1); + } +#else /* USE_TLSPROXY */ + if (smtpd_tls_ctx == 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + /* RFC 3207 Section 4. */ + smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem"); + return (-1); + } +#endif /* USE_TLSPROXY */ + + /* + * Enforce TLS handshake rate limit when this client negotiated too many + * new TLS sessions in the recent past. + * + * XXX The client event count/rate control must be consistent in its use of + * client address information in connect and disconnect events. For now + * we exclude xclient authorized hosts from event count/rate control. + */ + if (var_smtpd_cntls_limit > 0 + && SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls_stat(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Refusing STARTTLS request from %s for service %s", + state->namaddr, state->service); + smtpd_chat_reply(state, + "454 4.7.0 Error: too many new TLS sessions from %s", + state->namaddr); +#ifdef USE_TLSPROXY + (void) vstream_fclose(state->tlsproxy); + state->tlsproxy = 0; +#endif + return (-1); + } + smtpd_chat_reply(state, "220 2.0.0 Ready to start TLS"); + /* Flush before we switch read/write routines or file descriptors. */ + smtp_flush(state->client); + /* At this point there must not be any pending plaintext. */ + vstream_fpurge(state->client, VSTREAM_PURGE_BOTH); + + /* + * Reset all inputs to the initial state. + * + * XXX RFC 2487 does not forbid the use of STARTTLS while mail transfer is + * in progress, so we have to allow it even when it makes no sense. + */ + helo_reset(state); + mail_reset(state); + rcpt_reset(state); + + /* + * Turn on TLS, using code that is shared with TLS wrapper mode. This + * code does not return when the handshake fails. + */ + smtpd_start_tls(state); + return (0); +} + +/* tls_reset - undo STARTTLS */ + +static void tls_reset(SMTPD_STATE *state) +{ + int failure = 0; + + /* + * Don't waste time when we lost contact. + */ + if (state->tls_context) { + if (vstream_feof(state->client) || vstream_ferror(state->client)) + failure = 1; + vstream_fflush(state->client); /* NOT: smtp_flush() */ +#ifdef USE_TLSPROXY + tls_proxy_context_free(state->tls_context); +#else + tls_server_stop(smtpd_tls_ctx, state->client, var_smtpd_starttls_tmout, + failure, state->tls_context); +#endif + state->tls_context = 0; + } +} + +#endif + +#if !defined(USE_TLS) || !defined(USE_SASL_AUTH) + +/* unimpl_cmd - dummy for functionality that is not compiled in */ + +static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * When a connection is closed we want to log the request counts for + * unimplemented STARTTLS or AUTH commands separately, instead of logging + * those commands as "unknown". By handling unimplemented commands with + * this dummy function, we avoid messing up the command processing loop. + */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented"); + return (-1); +} + +#endif + + /* + * The table of all SMTP commands that we know. Set the junk limit flag on + * any command that can be repeated an arbitrary number of times without + * triggering a tarpit delay of some sort. + */ +typedef struct SMTPD_CMD { + char *name; + int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *); + int flags; + int success_count; + int total_count; +} SMTPD_CMD; + + /* + * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM, + * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined + * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands + * can only appear as the last command in a group". RFC 3030 allows BDAT + * commands to be pipelined as well. + */ +#define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */ +#define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */ +#define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */ + +static SMTPD_CMD smtpd_cmd_table[] = { + {SMTPD_CMD_HELO, helo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_EHLO, ehlo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_XCLIENT, xclient_cmd, SMTPD_CMD_FLAG_PRE_TLS}, + {SMTPD_CMD_XFORWARD, xforward_cmd,}, +#ifdef USE_TLS + {SMTPD_CMD_STARTTLS, starttls_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, +#else + {SMTPD_CMD_STARTTLS, unimpl_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, +#endif +#ifdef USE_SASL_AUTH + {SMTPD_CMD_AUTH, smtpd_sasl_auth_cmd_wrapper,}, +#else + {SMTPD_CMD_AUTH, unimpl_cmd,}, +#endif + {SMTPD_CMD_MAIL, mail_cmd,}, + {SMTPD_CMD_RCPT, rcpt_cmd,}, + {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_BDAT, bdat_cmd,}, + {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,}, + {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_ETRN, etrn_cmd, SMTPD_CMD_FLAG_LIMIT,}, + {SMTPD_CMD_QUIT, quit_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, + {0,}, +}; + +static STRING_LIST *smtpd_noop_cmds; +static STRING_LIST *smtpd_forbid_cmds; + +/* smtpd_proto - talk the SMTP protocol */ + +static void smtpd_proto(SMTPD_STATE *state) +{ + int argc; + SMTPD_TOKEN *argv; + SMTPD_CMD *cmdp; + const char *ehlo_words; + const char *err; + int status; + const char *cp; + +#ifdef USE_TLS + int tls_rate; + +#endif + + /* + * Print a greeting banner and run the state machine. Read SMTP commands + * one line at a time. According to the standard, a sender or recipient + * address could contain an escaped newline. I think this is perverse, + * and anyone depending on this is really asking for trouble. + * + * In case of mail protocol trouble, the program jumps back to this place, + * so that it can perform the necessary cleanup before talking to the + * next client. The setjmp/longjmp primitives are like a sharp tool: use + * with care. I would certainly recommend against the use of + * setjmp/longjmp in programs that change privilege levels. + * + * In case of file system trouble the program terminates after logging the + * error and after informing the client. In all other cases (out of + * memory, panic) the error is logged, and the msg_cleanup() exit handler + * cleans up, but no attempt is made to inform the client of the nature + * of the problem. + */ + smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_rec_deadline); + + while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE) + /* void */ ; + switch (status) { + + default: + msg_panic("smtpd_proto: unknown error reading from %s", + state->namaddr); + break; + + case SMTP_ERR_TIME: + state->reason = REASON_TIMEOUT; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.4.2 %s Error: timeout exceeded", + var_myhostname); + break; + + case SMTP_ERR_EOF: + state->reason = REASON_LOST_CONNECTION; + break; + + case SMTP_ERR_QUIET: + break; + + case SMTP_ERR_DATA: + msg_info("%s: reject: %s from %s: " + "421 4.3.0 %s Server local data error", + (state->queue_id ? state->queue_id : "NOQUEUE"), + state->where, state->namaddr, var_myhostname); + state->error_mask |= MAIL_ERROR_DATA; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.3.0 %s Server local data error", + var_myhostname); + break; + + case 0: + + /* + * In TLS wrapper mode, turn on TLS using code that is shared with + * the STARTTLS command. This code does not return when the handshake + * fails. + * + * Enforce TLS handshake rate limit when this client negotiated too many + * new TLS sessions in the recent past. + * + * XXX This means we don't complete a TLS handshake just to tell the + * client that we don't provide service. TLS wrapper mode is + * obsolete, so we don't have to provide perfect support. + */ +#ifdef USE_TLS + if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode + && state->tls_context == 0) { +#ifdef USE_TLSPROXY + /* We garbage-collect the VSTREAM in smtpd_state_reset() */ + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, + PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); + if (state->tlsproxy == 0) { + msg_warn("Wrapper-mode request dropped from %s for service %s." + " TLS context initialization failed. For details see" + " earlier warnings in your logs.", + state->namaddr, state->service); + break; + } +#else /* USE_TLSPROXY */ + if (smtpd_tls_ctx == 0) { + msg_warn("Wrapper-mode request dropped from %s for service %s." + " TLS context initialization failed. For details see" + " earlier warnings in your logs.", + state->namaddr, state->service); + break; + } +#endif /* USE_TLSPROXY */ + if (var_smtpd_cntls_limit > 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls_stat(anvil_clnt, state->service, + state->addr, &tls_rate) == ANVIL_STAT_OK + && tls_rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Refusing TLS service request from %s for service %s", + state->namaddr, state->service); + break; + } + smtpd_start_tls(state); + } +#endif + + /* + * XXX The client connection count/rate control must be consistent in + * its use of client address information in connect and disconnect + * events. For now we exclude xclient authorized hosts from + * connection count/rate control. + * + * XXX Must send connect/disconnect events to the anvil server even when + * this service is not connection count or rate limited, otherwise it + * will discard client message or recipient rate information too + * early or too late. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_connect(anvil_clnt, state->service, state->addr, + &state->conn_count, &state->conn_rate) + == ANVIL_STAT_OK) { + if (var_smtpd_cconn_limit > 0 + && state->conn_count > var_smtpd_cconn_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Connection concurrency limit exceeded: %d from %s for service %s", + state->conn_count, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + if (var_smtpd_crate_limit > 0 + && state->conn_rate > var_smtpd_crate_limit) { + msg_warn("Connection rate limit exceeded: %d from %s for service %s", + state->conn_rate, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + } + + /* + * Determine what server ESMTP features to suppress, typically to + * avoid inter-operability problems. Moved up so we don't send 421 + * immediately after sending the initial server response. + */ + if (ehlo_discard_maps == 0 + || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0) + ehlo_words = var_smtpd_ehlo_dis_words; + state->ehlo_discard_mask = ehlo_mask(ehlo_words); + + /* XXX We use the real client for connect access control. */ + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_client(state)) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + state->access_denied = mystrdup(err); + smtpd_chat_reply(state, "%s", state->access_denied); + state->error_count++; + } + + /* + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses + * other than the initial greeting and any response to HELO or EHLO + * are prefaced with a status code as defined in RFC 3463. + */ + + /* + * XXX If a Milter rejects CONNECT, reply with 220 except in case of + * hard reject or 421 (disconnect). The reply persists so it will + * apply to MAIL FROM and to other commands such as AUTH, STARTTLS, + * and VRFY. Note: after a Milter CONNECT reject, we must not reject + * HELO or EHLO, but we do change the feature list that is announced + * in the EHLO response. + */ + else { + err = 0; + if (state->milters != 0) { + milter_macro_callback(state->milters, smtpd_milter_eval, + (void *) state); + if ((err = milter_conn_event(state->milters, state->name, + state->addr, + strcmp(state->port, CLIENT_PORT_UNKNOWN) ? + state->port : "0", + state->addr_family)) != 0) + err = check_milter_reply(state, err); + } + if (err && err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "554 %s ESMTP not accepting connections", + var_myhostname); + state->error_count++; + } else if (err && strncmp(err, "421", 3) == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "421 %s Service unavailable - try again later", + var_myhostname); + /* Not: state->error_count++; */ + } else { + smtpd_chat_reply(state, "220 %s", var_smtpd_banner); + } + } + + /* + * SASL initialization for plaintext mode. + * + * XXX Backwards compatibility: allow AUTH commands when the AUTH + * announcement is suppressed via smtpd_sasl_exceptions_networks. + * + * XXX Safety: don't enable SASL with "smtpd_tls_auth_only = yes" and + * non-TLS build. + */ +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable && smtpd_sasl_is_active(state) == 0 +#ifdef USE_TLS + && state->tls_context == 0 && !var_smtpd_tls_auth_only +#else + && var_smtpd_tls_auth_only == 0 +#endif + ) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); +#endif + + /* + * The command read/execute loop. + */ + for (;;) { + if (state->flags & SMTPD_FLAG_HANGUP) + break; + if (state->error_count >= var_smtpd_hard_erlim) { + state->reason = REASON_ERROR_LIMIT; + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many errors", + var_myhostname); + break; + } + watchdog_pat(); + smtpd_chat_query(state); + /* Safety: protect internal interfaces against malformed UTF-8. */ + if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer), + LEN(state->buffer)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax"); + state->error_count++; + continue; + } + /* Move into smtpd_chat_query() and update session transcript. */ + if (smtpd_cmd_filter != 0) { + for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++) + /* void */ ; + if ((cp = dict_get(smtpd_cmd_filter, cp)) != 0) { + msg_info("%s: replacing command \"%.100s\" with \"%.100s\"", + state->namaddr, STR(state->buffer), cp); + vstring_strcpy(state->buffer, cp); + } else if (smtpd_cmd_filter->error != 0) { + msg_warn("%s:%s lookup error for \"%.100s\"", + smtpd_cmd_filter->type, smtpd_cmd_filter->name, + printable(STR(state->buffer), '?')); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + } + if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad syntax"); + state->error_count++; + continue; + } + /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */ + if (*var_smtpd_noop_cmds + && string_list_match(smtpd_noop_cmds, argv[0].strval)) { + smtpd_chat_reply(state, "250 2.0.0 Ok"); + if (state->junk_cmds++ > var_smtpd_junk_cmd_limit) + state->error_count++; + continue; + } + for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++) + if (strcasecmp(argv[0].strval, cmdp->name) == 0) + break; + cmdp->total_count += 1; + /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ + if (cmdp->name == 0) { + state->where = SMTPD_CMD_UNKNOWN; + if (is_header(argv[0].strval) + || (*var_smtpd_forbid_cmds + && string_list_match(smtpd_forbid_cmds, argv[0].strval))) { + msg_warn("non-SMTP command from %s: %.100s", + state->namaddr, vstring_str(state->buffer)); + smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye."); + break; + } + } + /* XXX We use the real client for connect access control. */ + if (state->access_denied && cmdp->action != quit_cmd) { + /* XXX Exception for Milter override. */ + if (strncmp(state->access_denied + 1, "21", 2) == 0) { + smtpd_chat_reply(state, "%s", state->access_denied); + continue; + } + smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s", + state->namaddr); /* RFC 2821 Sec 3.1 */ + state->error_count++; + continue; + } + /* state->access_denied == 0 || cmdp->action == quit_cmd */ + if (cmdp->name == 0) { + if (state->milters != 0 + && (err = milter_unknown_event(state->milters, + argv[0].strval)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + smtpd_chat_reply(state, "%s", err); + } else + smtpd_chat_reply(state, "502 5.5.2 Error: command not recognized"); + state->error_mask |= MAIL_ERROR_PROTOCOL; + state->error_count++; + continue; + } +#ifdef USE_TLS + if (var_smtpd_enforce_tls && + !state->tls_context && + (cmdp->flags & SMTPD_CMD_FLAG_PRE_TLS) == 0) { + smtpd_chat_reply(state, + "530 5.7.0 Must issue a STARTTLS command first"); + state->error_count++; + continue; + } +#endif + state->where = cmdp->name; + if (SMTPD_STAND_ALONE(state) == 0 + && (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0 + || (cmdp->flags & SMTPD_CMD_FLAG_LAST)) + && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0 + && (vstream_peek(state->client) > 0 + || peekfd(vstream_fileno(state->client)) > 0)) { + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + escape(state->expand_buf, vstream_peek_data(state->client), + vstream_peek(state->client) < 100 ? + vstream_peek(state->client) : 100); + msg_info("improper command pipelining after %s from %s: %s", + cmdp->name, state->namaddr, STR(state->expand_buf)); + state->flags |= SMTPD_FLAG_ILL_PIPELINING; + } + if (cmdp->action(state, argc, argv) != 0) + state->error_count++; + else + cmdp->success_count += 1; + if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT) + && state->junk_cmds++ > var_smtpd_junk_cmd_limit) + state->error_count++; + if (cmdp->action == quit_cmd) + break; + } + break; + } + + /* + * XXX The client connection count/rate control must be consistent in its + * use of client address information in connect and disconnect events. + * For now we exclude xclient authorized hosts from connection count/rate + * control. + * + * XXX Must send connect/disconnect events to the anvil server even when + * this service is not connection count or rate limited, otherwise it + * will discard client message or recipient rate information too early or + * too late. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr)) + anvil_clnt_disconnect(anvil_clnt, state->service, state->addr); + + /* + * Log abnormal session termination, in case postmaster notification has + * been turned off. In the log, indicate the last recognized state before + * things went wrong. Don't complain about clients that go away without + * sending QUIT. Log the byte count after DATA to help diagnose MTU + * troubles. + */ + if (state->reason && state->where) { + if (strcmp(state->where, SMTPD_AFTER_DATA) == 0) { + msg_info("%s after %s (%lu bytes) from %s", /* 2.5 compat */ + state->reason, SMTPD_CMD_DATA, /* 2.5 compat */ + (long) (state->act_size + vstream_peek(state->client)), + state->namaddr); + } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) { + msg_info("%s after %s (%lu bytes) from %s", + state->reason, SMTPD_CMD_BDAT, + (long) (state->act_size + VSTRING_LEN(state->buffer) + + VSTRING_LEN(state->bdat_get_buffer)), + state->namaddr); + } else if (strcmp(state->where, SMTPD_AFTER_EOM) + || strcmp(state->reason, REASON_LOST_CONNECTION)) { + msg_info("%s after %s from %s", + state->reason, state->where, state->namaddr); + } + } + + /* + * Cleanup whatever information the client gave us during the SMTP + * dialog. + * + * XXX Duplicated in xclient_cmd(). + */ +#ifdef USE_TLS + tls_reset(state); +#endif + helo_reset(state); +#ifdef USE_SASL_AUTH + smtpd_sasl_auth_reset(state); + if (smtpd_sasl_is_active(state)) { + smtpd_sasl_deactivate(state); + } +#endif + chat_reset(state, 0); + mail_reset(state); + rcpt_reset(state); + if (state->milters) + milter_disc_event(state->milters); +} + +/* smtpd_format_cmd_stats - format per-command statistics */ + +static char *smtpd_format_cmd_stats(VSTRING *buf) +{ + SMTPD_CMD *cmdp; + int all_success = 0; + int all_total = 0; + + /* + * Log the statistics. Note that this loop produces no output when no + * command was received. We address that after the loop. + */ + VSTRING_RESET(buf); + for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) { + if (cmdp->total_count > 0) { + vstring_sprintf_append(buf, " %s=%d", + cmdp->name ? cmdp->name : "unknown", + cmdp->success_count); + if (cmdp->success_count != cmdp->total_count) + vstring_sprintf_append(buf, "/%d", cmdp->total_count); + all_success += cmdp->success_count; + all_total += cmdp->total_count; + } + if (cmdp->name == 0) + break; + } + + /* + * Reset the per-command counters. + */ + for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) { + cmdp->success_count = cmdp->total_count = 0; + if (cmdp->name == 0) + break; + } + + /* + * Log total numbers, so that logfile analyzers will see something even + * if the above loop produced no output. When no commands were received + * log "0/0" to simplify the identification of abnormal sessions: any + * statistics with [0-9]/ indicate that there was a problem. + */ + vstring_sprintf_append(buf, " commands=%d", all_success); + if (all_success != all_total || all_total == 0) + vstring_sprintf_append(buf, "/%d", all_total); + return (lowercase(STR(buf))); +} + +/* setup_milters - set up Milters after a connection is established */ + +static void setup_milters(SMTPD_STATE *state) +{ + const char *milter_string; + + /* + * Postcondition: either state->milters is set, or the + * INPUT_TRANSP_MILTER flag is passed down-stream. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && (smtpd_input_transp_mask & INPUT_TRANSP_MILTER) == 0 + && ((smtpd_milter_maps + && (milter_string = + maps_find(smtpd_milter_maps, state->addr, 0)) != 0) + || *(milter_string = var_smtpd_milters) != 0) + && strcasecmp(milter_string, SMTPD_MILTERS_DISABLE) != 0) { + state->milters = milter_create(milter_string, + var_milt_conn_time, + var_milt_cmd_time, + var_milt_msg_time, + var_milt_protocol, + var_milt_def_action, + var_milt_conn_macros, + var_milt_helo_macros, + var_milt_mail_macros, + var_milt_rcpt_macros, + var_milt_data_macros, + var_milt_eoh_macros, + var_milt_eod_macros, + var_milt_unk_macros, + var_milt_macro_deflts); + } + + /* + * Safety: disable non_smtpd_milters when not sending our own mail filter + * list. Otherwise the next stage could handle this message as a local + * submission. + */ + if (state->milters == 0) + smtpd_input_transp_mask |= INPUT_TRANSP_MILTER; +} + +/* teardown_milters - release resources */ + +static void teardown_milters(SMTPD_STATE *state) +{ + if (state->milters) { + milter_free(state->milters); + state->milters = 0; + } + smtpd_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); +} + + +/* smtpd_service - service one client */ + +static void smtpd_service(VSTREAM *stream, char *service, char **argv) +{ + SMTPD_STATE state; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (SMTPD_STAND_ALONE_STREAM(stream) == 0 + && inet_proto_info()->ai_family_list[0] == 0) + msg_fatal("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * This routine runs when a client has connected to our network port, or + * when the smtp server is run in stand-alone mode (input from pipe). + * + * Look up and sanitize the peer name, then initialize some connection- + * specific state. When the name service is hosed, hostname lookup will + * take a while. This is why I always run a local name server on critical + * machines. + */ + smtpd_state_init(&state, stream, service); + msg_info("connect from %s", state.namaddr); + + /* + * Disable TLS when running in stand-alone mode via "sendmail -bs". + */ + if (SMTPD_STAND_ALONE((&state))) { + var_smtpd_use_tls = 0; + var_smtpd_enforce_tls = 0; + var_smtpd_tls_auth_only = 0; + } + + /* + * XCLIENT must not override its own access control. + */ + xclient_allowed = SMTPD_STAND_ALONE((&state)) == 0 && + namadr_list_match(xclient_hosts, state.name, state.addr); + + /* + * Overriding XFORWARD access control makes no sense, either. + */ + xforward_allowed = SMTPD_STAND_ALONE((&state)) == 0 && + namadr_list_match(xforward_hosts, state.name, state.addr); + + /* + * See if we need to turn on verbose logging for this client. + */ + debug_peer_check(state.name, state.addr); + + /* + * Set up Milters, or disable Milters down-stream. + */ + setup_milters(&state); /* duplicates xclient_cmd */ + + /* + * Provide the SMTP service. + */ + if ((state.flags & SMTPD_FLAG_HANGUP) == 0) + smtpd_proto(&state); + + /* + * After the client has gone away, clean up whatever we have set up at + * connection time. + */ + msg_info("disconnect from %s%s", state.namaddr, + smtpd_format_cmd_stats(state.buffer)); + teardown_milters(&state); /* duplicates xclient_cmd */ + smtpd_state_reset(&state); + debug_peer_restore(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize blacklist/etc. patterns before entering the chroot jail, in + * case they specify a filename pattern. + */ + smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN, + var_smtpd_noop_cmds); + smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS, + MATCH_FLAG_RETURN, + var_smtpd_forbid_cmds); + verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN, + var_verp_clients); + xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN, + var_xclient_hosts); + xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN, + var_xforward_hosts); + hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN + | match_parent_style(VAR_SMTPD_HOGGERS), + var_smtpd_hoggers); + + /* + * Open maps before dropping privileges so we can read passwords etc. + * + * XXX We should not do this in stand-alone (sendmail -bs) mode, but we + * can't use SMTPD_STAND_ALONE(state) here. This means "sendmail -bs" + * will try to connect to proxymap when invoked by root for mail + * submission. To fix, we would have to pass stand-alone mode information + * via different means. For now we have to tell people not to run mail + * clients as root. + */ + if (getuid() == 0 || getuid() == var_owner_uid) + smtpd_check_init(); + smtpd_expand_init(); + debug_peer_init(); + + if (var_smtpd_sasl_enable) +#ifdef USE_SASL_AUTH + smtpd_sasl_initialize(); + + if (*var_smtpd_sasl_exceptions_networks) + sasl_exceptions_networks = + namadr_list_init(VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, + MATCH_FLAG_RETURN, + var_smtpd_sasl_exceptions_networks); +#else + msg_warn("%s is true, but SASL support is not compiled in", + VAR_SMTPD_SASL_ENABLE); +#endif + + if (*var_smtpd_cmd_filter) + smtpd_cmd_filter = dict_open(var_smtpd_cmd_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * XXX Temporary fix to pretend that we consistently implement TLS + * security levels. We implement only a subset for now. If we implement + * more levels, wrappermode should override only weaker TLS security + * levels. + * + * Note: tls_level_lookup() logs no warning. + */ + if (!var_smtpd_tls_wrappermode && *var_smtpd_tls_level) { + switch (tls_level_lookup(var_smtpd_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_smtpd_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_SMTPD_TLS_LEVEL, var_smtpd_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_smtpd_enforce_tls = var_smtpd_use_tls = 1; + break; + case TLS_LEV_MAY: + var_smtpd_enforce_tls = 0; + var_smtpd_use_tls = 1; + break; + case TLS_LEV_NONE: + var_smtpd_enforce_tls = var_smtpd_use_tls = 0; + break; + } + } + + /* + * With TLS wrapper mode, we run on a dedicated port and turn on TLS + * before actually speaking the SMTP protocol. This implies TLS enforce + * mode. + * + * With non-wrapper mode, TLS enforce mode implies that we don't advertise + * AUTH before the client issues STARTTLS. + */ + var_smtpd_enforce_tls = var_smtpd_tls_wrappermode || var_smtpd_enforce_tls; + var_smtpd_tls_auth_only = var_smtpd_tls_auth_only || var_smtpd_enforce_tls; + var_smtpd_use_tls = var_smtpd_use_tls || var_smtpd_enforce_tls; + + /* + * Keys can only be loaded when running with suitable permissions. When + * called from "sendmail -bs" this is not the case, so we must not + * announce STARTTLS support. + */ + if (getuid() == 0 || getuid() == var_owner_uid) { + if (var_smtpd_use_tls) { +#ifdef USE_TLS +#ifndef USE_TLSPROXY + TLS_SERVER_INIT_PROPS props; + const char *cert_file; + int have_server_cert; + int no_server_cert_ok; + int require_server_cert; + + /* + * Can't use anonymous ciphers if we want client certificates. + * Must use anonymous ciphers if we have no certificates. + * + * XXX: Ugh! Too many booleans! + */ + ask_client_cert = require_server_cert = + (var_smtpd_tls_ask_ccert + || (var_smtpd_enforce_tls && var_smtpd_tls_req_ccert)); + if (strcasecmp(var_smtpd_tls_cert_file, "none") == 0) { + no_server_cert_ok = 1; + cert_file = ""; + } else { + no_server_cert_ok = 0; + cert_file = var_smtpd_tls_cert_file; + } + + have_server_cert = *cert_file != 0; + have_server_cert |= *var_smtpd_tls_eccert_file != 0; + have_server_cert |= *var_smtpd_tls_dcert_file != 0; + + if (*var_smtpd_tls_chain_files != 0) { + if (!have_server_cert) + have_server_cert = 1; + else + msg_warn("Both %s and one or more of the legacy " + " %s, %s or %s are non-empty; the legacy " + " parameters will be ignored", + VAR_SMTPD_TLS_CHAIN_FILES, + VAR_SMTPD_TLS_CERT_FILE, + VAR_SMTPD_TLS_ECCERT_FILE, + VAR_SMTPD_TLS_DCERT_FILE); + } + /* Some TLS configuration errors are not show stoppers. */ + if (!have_server_cert && require_server_cert) + msg_warn("Need a server cert to request client certs"); + if (!var_smtpd_enforce_tls && var_smtpd_tls_req_ccert) + msg_warn("Can't require client certs unless TLS is required"); + /* After a show-stopper error, reply with 454 to STARTTLS. */ + if (have_server_cert + || (no_server_cert_ok && !require_server_cert)) { + + tls_pre_jail_init(TLS_ROLE_SERVER); + + /* + * Large parameter lists are error-prone, so we emulate a + * language feature that C does not have natively: named + * parameter lists. + */ + smtpd_tls_ctx = + TLS_SERVER_INIT(&props, + log_param = VAR_SMTPD_TLS_LOGLEVEL, + log_level = var_smtpd_tls_loglevel, + verifydepth = var_smtpd_tls_ccert_vd, + cache_type = TLS_MGR_SCACHE_SMTPD, + set_sessid = var_smtpd_tls_set_sessid, + chain_files = var_smtpd_tls_chain_files, + cert_file = cert_file, + key_file = var_smtpd_tls_key_file, + dcert_file = var_smtpd_tls_dcert_file, + dkey_file = var_smtpd_tls_dkey_file, + eccert_file = var_smtpd_tls_eccert_file, + eckey_file = var_smtpd_tls_eckey_file, + CAfile = var_smtpd_tls_CAfile, + CApath = var_smtpd_tls_CApath, + dh1024_param_file + = var_smtpd_tls_dh1024_param_file, + dh512_param_file + = var_smtpd_tls_dh512_param_file, + eecdh_grade = var_smtpd_tls_eecdh, + protocols = var_smtpd_enforce_tls ? + var_smtpd_tls_mand_proto : + var_smtpd_tls_proto, + ask_ccert = ask_client_cert, + mdalg = var_smtpd_tls_fpt_dgst); + } else { + msg_warn("No server certs available. TLS won't be enabled"); + } +#endif /* USE_TLSPROXY */ +#else + msg_warn("TLS has been selected, but TLS support is not compiled in"); +#endif + } + } + + /* + * flush client. + */ + flush_init(); + + /* + * EHLO keyword filter. + */ + if (*var_smtpd_ehlo_dis_maps) + ehlo_discard_maps = maps_create(VAR_SMTPD_EHLO_DIS_MAPS, + var_smtpd_ehlo_dis_maps, + DICT_FLAG_LOCK); + + /* + * Per-client Milter support. + */ + if (*var_smtpd_milter_maps) + smtpd_milter_maps = maps_create(VAR_SMTPD_MILTER_MAPS, + var_smtpd_milter_maps, + DICT_FLAG_LOCK); + + /* + * DNS reply filter. + */ + if (*var_smtpd_dns_re_filter) + dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, + var_smtpd_dns_re_filter); + + /* + * Reject footer. + */ + if (*var_smtpd_rej_ftr_maps) + smtpd_chat_pre_jail_init(); +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize the receive transparency options: do we want unknown + * recipient checks, address mapping, header_body_checks?. + */ + smtpd_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); + + /* + * Initialize before-queue filter options: do we want speed-matching + * support so that the entire message is received before we contact a + * before-queue content filter? + */ + if (*var_smtpd_proxy_filt) + smtpd_proxy_opts = + smtpd_proxy_parse_opts(VAR_SMTPD_PROXY_OPTS, var_smtpd_proxy_opts); + + /* + * Sanity checks. The queue_minfree value should be at least as large as + * (process_limit * message_size_limit) but that is unpractical, so we + * arbitrarily pick a small multiple of the per-message size limit. This + * helps to avoid many unneeded (re)transmissions. + */ + if (var_queue_minfree > 0 + && var_message_limit > 0 + && var_queue_minfree / 1.5 < var_message_limit) + msg_warn("%s(%lu) should be at least 1.5*%s(%lu)", + VAR_QUEUE_MINFREE, (unsigned long) var_queue_minfree, + VAR_MESSAGE_LIMIT, (unsigned long) var_message_limit); + + /* + * Connection rate management. + */ + if (var_smtpd_crate_limit || var_smtpd_cconn_limit + || var_smtpd_cmail_limit || var_smtpd_crcpt_limit + || var_smtpd_cntls_limit || var_smtpd_cauth_limit) + anvil_clnt = anvil_clnt_create(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0, + VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0, + VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0, + VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0, + VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0, + VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0, + VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0, + VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0, + VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, 0, 0, + VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, 0, 0, + VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, 0, 0, + VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code, 0, 0, + VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code, 0, 0, + VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, 0, 0, + VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, 0, 0, + VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0, + VAR_SMTPD_RCPT_OVERLIM, DEF_SMTPD_RCPT_OVERLIM, &var_smtpd_rcpt_overlim, 1, 0, + VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0, + VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode, 200, 599, + VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode, 200, 599, + VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode, 200, 499, + VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode, 200, 499, + VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code, 0, 0, + VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code, 0, 0, + VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, 0, 0, + VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0, + VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0, + VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, 0, 0, + VAR_SMTPD_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0, + VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0, + VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0, + VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0, + VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0, + VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0, +#ifdef USE_TLS + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, +#endif + VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0, + VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0, + VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0, + VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0, + VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0, + VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0, + VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, &var_smtpd_policy_tmout, 1, 0, + VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, &var_smtpd_policy_idle, 1, 0, + VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, &var_smtpd_policy_ttl, 1, 0, +#ifdef USE_TLS + VAR_SMTPD_STARTTLS_TMOUT, DEF_SMTPD_STARTTLS_TMOUT, &var_smtpd_starttls_tmout, 1, 0, +#endif + VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, &var_milt_conn_time, 1, 0, + VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, &var_milt_cmd_time, 1, 0, + VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, &var_milt_msg_time, 1, 0, + VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0, + VAR_SMTPD_UPROXY_TMOUT, DEF_SMTPD_UPROXY_TMOUT, &var_smtpd_uproxy_tmout, 1, 0, + VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, &var_smtpd_policy_try_delay, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required, + VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject, + VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env, + VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd, + VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route, + VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable, + VAR_SMTPD_SASL_AUTH_HDR, DEF_SMTPD_SASL_AUTH_HDR, &var_smtpd_sasl_auth_hdr, + VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients, + VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, + VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from, + VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt, + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode, + VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only, +#ifdef USE_TLS + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header, + VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid, +#endif + VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup, + VAR_SMTPD_DELAY_OPEN, DEF_SMTPD_DELAY_OPEN, &var_smtpd_delay_open, + VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_SMTPD_REC_DEADLINE, DEF_SMTPD_REC_DEADLINE, &var_smtpd_rec_deadline, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0, + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0, + VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0, + VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0, + VAR_RELAY_CHECKS, DEF_RELAY_CHECKS, &var_relay_checks, 0, 0, + VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0, + VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0, + VAR_DATA_CHECKS, DEF_DATA_CHECKS, &var_data_checks, 0, 0, + VAR_EOD_CHECKS, DEF_EOD_CHECKS, &var_eod_checks, 0, 0, + VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0, + VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, 0, 0, + VAR_BOUNCE_RCPT, DEF_ERROR_RCPT, &var_bounce_rcpt, 1, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, + VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0, + VAR_SMTPD_SASL_PATH, DEF_SMTPD_SASL_PATH, &var_smtpd_sasl_path, 1, 0, + VAR_SMTPD_SASL_SERVICE, DEF_SMTPD_SASL_SERVICE, &var_smtpd_sasl_service, 1, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0, + VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0, + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0, + VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0, + VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, + VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0, + VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0, + VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0, + VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0, + VAR_SMTPD_PROXY_OPTS, DEF_SMTPD_PROXY_OPTS, &var_smtpd_proxy_opts, 0, 0, + VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0, + VAR_XCLIENT_HOSTS, DEF_XCLIENT_HOSTS, &var_xclient_hosts, 0, 0, + VAR_XFORWARD_HOSTS, DEF_XFORWARD_HOSTS, &var_xforward_hosts, 0, 0, + VAR_SMTPD_HOGGERS, DEF_SMTPD_HOGGERS, &var_smtpd_hoggers, 0, 0, + VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, 0, 0, + VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0, + VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0, +#ifdef USE_TLS + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0, + VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0, + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, +#endif + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTPD_SASL_TYPE, DEF_SMTPD_SASL_TYPE, &var_smtpd_sasl_type, 1, 0, + VAR_SMTPD_MILTERS, DEF_SMTPD_MILTERS, &var_smtpd_milters, 0, 0, + VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0, + VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0, + VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0, + VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0, + VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0, + VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0, + VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0, + VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0, + VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0, + VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0, + VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0, + VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0, + VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0, + VAR_SMTPD_MILTER_MAPS, DEF_SMTPD_MILTER_MAPS, &var_smtpd_milter_maps, 0, 0, + VAR_STRESS, DEF_STRESS, &var_stress, 0, 0, + VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0, + VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0, + VAR_REJECT_TMPF_ACT, DEF_REJECT_TMPF_ACT, &var_reject_tmpf_act, 1, 0, + VAR_UNK_NAME_TF_ACT, DEF_UNK_NAME_TF_ACT, &var_unk_name_tf_act, 1, 0, + VAR_UNK_ADDR_TF_ACT, DEF_UNK_ADDR_TF_ACT, &var_unk_addr_tf_act, 1, 0, + VAR_UNV_RCPT_TF_ACT, DEF_UNV_RCPT_TF_ACT, &var_unv_rcpt_tf_act, 1, 0, + VAR_UNV_FROM_TF_ACT, DEF_UNV_FROM_TF_ACT, &var_unv_from_tf_act, 1, 0, + VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0, +#ifdef USE_TLSPROXY + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, +#endif + VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, 0, 0, + VAR_SMTPD_UPROXY_PROTO, DEF_SMTPD_UPROXY_PROTO, &var_smtpd_uproxy_proto, 0, 0, + VAR_SMTPD_POLICY_DEF_ACTION, DEF_SMTPD_POLICY_DEF_ACTION, &var_smtpd_policy_def_action, 1, 0, + VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0, + VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0, + VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0, + 0, + }; + static const CONFIG_RAW_TABLE raw_table[] = { + VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0, + VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply, 1, 0, + VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, smtpd_service, + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_RAW_TABLE(raw_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + 0); +} |