PROXY: Move Proxy Protocol support from Experimental to mainline.
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 5 Dec 2015 20:21:51 +0000 (20:21 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 8 Dec 2015 17:07:11 +0000 (17:07 +0000)
No testsuite coverage yet.

13 files changed:
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/experimental-spec.txt
src/src/EDITME
src/src/config.h.defaults
src/src/exim.c
src/src/expand.c
src/src/globals.c
src/src/globals.h
src/src/macros.h
src/src/readconf.c
src/src/receive.c
src/src/smtp_in.c

index 73887d4..6096e1d 100644 (file)
@@ -12033,6 +12033,16 @@ qualified host name. See also &$smtp_active_hostname$&.
 
 
 .new
+.vitem &$proxy_host_address$& &&&
+       &$proxy_host_port$& &&&
+       &$proxy_target_address$& &&&
+       &$proxy_target_port$& &&&
+       &$proxy_session$&
+These variables are only available when built with Proxy Protocol support
+For details see chapter &<<SECTproxyInbound>>&.
+.wen
+
+.new
 .vitem &$prdr_requested$&
 .cindex "PRDR" "variable for"
 This variable is set to &"yes"& if PRDR was requested by the client for the
@@ -13469,6 +13479,7 @@ listed in more than one group.
 .row &%helo_verify_hosts%&           "HELO hard-checked for these hosts"
 .row &%host_lookup%&                 "host name looked up for these hosts"
 .row &%host_lookup_order%&           "order of DNS and local name lookups"
+.row &%hosts_proxy%&                 "use proxy protocol for these hosts"
 .row &%host_reject_connection%&      "reject connection from these hosts"
 .row &%hosts_treat_as_local%&        "useful in some cluster configurations"
 .row &%local_scan_timeout%&          "timeout for &[local_scan()]&"
@@ -14824,6 +14835,14 @@ If the &%smtp_connection%& log selector is not set, this option has no effect.
 
 
 
+.new
+.option hosts_proxy main "host list&!!" unset
+.cindex proxy "proxy protocol"
+This option enables use of Proxy Protocol proxies for incoming
+connections.  For details see &<<SECTproxyInbound>>&.
+.wen
+
+
 .option hosts_treat_as_local main "domain list&!!" unset
 .cindex "local host" "domains treated as"
 .cindex "host" "treated as local"
@@ -35461,6 +35480,9 @@ selection marked by asterisks:
 &` queue_time                 `&  time on queue for one recipient
 &` queue_time_overall         `&  time on queue for whole message
 &` pid                        `&  Exim process id
+.new
+&` proxy                      `&  proxy address on <= lines
+.wen
 &` received_recipients        `&  recipients on <= lines
 &` received_sender            `&  sender on <= lines
 &`*rejected_header            `&  header contents on reject log
@@ -35587,6 +35609,16 @@ rejection lines, and (despite the name) to outgoing &"=>"& and &"->"& lines.
 The latter can be disabled by turning off the &%outgoing_interface%& option.
 .wen
 .next
+.new
+.cindex log "incoming proxy address"
+.cindex proxy "logging proxy address"
+.cindex "TCP/IP" "logging proxy address"
+&%proxy%&: The internal (closest to the system running Exim) IP address
+of the proxy, tagged by PRX=, on the &"<="& line for a message accepted
+on a proxied connection.
+See &<<SECTproxyInbound>>& for more information.
+.wen
+.next
 .cindex "log" "incoming remote port"
 .cindex "port" "logging remote"
 .cindex "TCP/IP" "logging incoming remote port"
@@ -38025,6 +38057,74 @@ for more information of what they mean.
 A proxy is an intermediate system through which communication is passed.
 Proxies may provide a security, availability or load-distribution function.
 
+
+.section "Inbound proxies" SECTproxyInbound
+.cindex proxy inbound
+.cindex proxy "server side"
+.cindex proxy "Proxy protocol"
+.cindex "Proxy protocol" proxy
+
+Exim has support for receiving inbound SMTP connections via a proxy
+that uses &"Proxy Protocol"& to speak to it.
+To include this support, include &"SUPPORT_PROXY=yes"&
+in Local/Makefile.
+
+It was built on specifications from:
+http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
+That URL was revised in May 2014 to version 2 spec:
+http://git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e
+
+The purpose of this facility is so that an application load balancer,
+such as HAProxy, can sit in front of several Exim servers
+to distribute load.
+Exim uses the local protocol communication with the proxy to obtain
+the remote SMTP system IP address and port information.
+There is no logging if a host passes or
+fails Proxy Protocol negotiation, but it can easily be determined and
+recorded in an ACL (example is below).
+
+Use of a proxy is enabled by setting the &%hosts_proxy%&
+main configuration option to a hostlist; connections from these
+hosts will use Proxy Protocol.
+
+To log the IP of the proxy in the incoming logline, add &"+proxy"&
+to the &%log_selector%& option.
+This will add a component tagged with &"PRX="& to the line.
+
+The following expansion variables are usable
+(&"internal"& and &"external"& here refer to the interfaces
+of the proxy):
+.display
+&'proxy_host_address   '& internal IP address of the proxy
+&'proxy_host_port      '& internal TCP port of the proxy
+&'proxy_target_address '& external IP address of the proxy
+&'proxy_target_port    '& external TCP port of the proxy
+&'proxy_session        '& boolean: SMTP connection via proxy
+.endd
+If &$proxy_session$& is set but &$proxy_host_address$& is empty
+there was a protocol error.
+
+Since the real connections are all coming from the proxy, and the
+per host connection tracking is done before Proxy Protocol is
+evaluated, &%smtp_accept_max_per_host%& must be set high enough to
+handle all of the parallel volume you expect per inbound proxy.
+With the option set so high, you lose the ability
+to protect your server from many connections from one IP.
+In order to prevent your server from overload, you
+need to add a per connection ratelimit to your connect ACL.
+A possible solution is:
+.display
+  # Set max number of connections per host
+  LIMIT   = 5
+  # Or do some kind of IP lookup in a flat file or database
+  # LIMIT = ${lookup{$sender_host_address}iplsearch{/etc/exim/proxy_limits}}
+
+  defer   message        = Too many connections from this IP right now
+          ratelimit      = LIMIT / 5s / per_conn / strict
+.endd
+
+
+
 .section "Outbound proxies" SECTproxySOCKS
 .cindex proxy outbound
 .cindex proxy "client side"
@@ -38035,7 +38135,8 @@ using a protocol called SOCKS5 (defined by RFC1928).
 The support can be optionally included by defining SUPPORT_SOCKS=yes in
 Local/Makefile.
 
-Use of a proxy is enabled by setting the &%socks_proxy%& on an smtp transport.
+Use of a proxy is enabled by setting the &%socks_proxy%& option
+on an smtp transport.
 The option value is expanded and should then be a list
 (colon-separated by default) of proxy specifiers.
 Each proxy specifier is a list
index d57c3db..1fa19da 100644 (file)
@@ -115,6 +115,10 @@ JH/22 Bugs 963, 1721: Fix some corner cases in message body canonicalisation
 JH/23 Move SOCKS5 support from Experimental to mainline, enabled for a build
       by defining SUPPORT_SOCKS.
 
+JH/26 Move PROXY support from Experimental to mainline, enabled for a build
+      by defining SUPPORT_PROXY.  Note that the proxy_required_hosts option
+      is renamed to hosts_proxy.
+
 
 Exim version 4.86
 -----------------
index f0f1035..45e7d1b 100644 (file)
@@ -973,151 +973,6 @@ Where SPAMMER_SET is a macro and it is defined as
 set acl_c_spam_host = ${lookup redis{GET...}}
 
 
-Proxy Protocol Support
---------------------------------------------------------------
-
-Exim now has Experimental "Proxy Protocol" support.  It was built on
-specifications from:
-http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
-Above URL revised May 2014 to change version 2 spec:
-http://git.1wt.eu/web?p=haproxy.git;a=commitdiff;h=afb768340c9d7e50d8e
-
-The purpose of this function is so that an application load balancer,
-such as HAProxy, can sit in front of several Exim servers and Exim
-will log the IP that is connecting to the proxy server instead of
-the IP of the proxy server when it connects to Exim.  It resets the
-$sender_address_host and $sender_address_port to the IP:port of the
-connection to the proxy.  It also re-queries the DNS information for
-this new IP address so that the original sender's hostname and IP
-get logged in the Exim logfile.  There is no logging if a host passes or
-fails Proxy Protocol negotiation, but it can easily be determined and
-recorded in an ACL (example is below).
-
-1. To compile Exim with Proxy Protocol support, put this in
-Local/Makefile:
-
-EXPERIMENTAL_PROXY=yes
-
-2. Global configuration settings:
-
-proxy_required_hosts = HOSTLIST
-
-The proxy_required_hosts option will require any IP in that hostlist
-to use Proxy Protocol. The specification of Proxy Protocol is very
-strict, and if proxy negotiation fails, Exim will not allow any SMTP
-command other than QUIT. (See end of this section for an example.)
-The option is expanded when used, so it can be a hostlist as well as
-string of IP addresses.  Since it is expanded, specifying an alternate
-separator is supported for ease of use with IPv6 addresses.
-
-To log the IP of the proxy in the incoming logline, add:
-  log_selector = +proxy
-
-A default incoming logline (wrapped for appearance) will look like this:
-
-  2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net
-  H=mail.example.net [1.2.3.4] P=esmtp S=433
-
-With the log selector enabled, an email that was proxied through a
-Proxy Protocol server at 192.168.1.2 will look like this:
-
-  2013-11-04 09:25:06 1VdNti-0001OY-1V <= me@example.net
-  H=mail.example.net [1.2.3.4] P=esmtp PRX=192.168.1.2 S=433
-
-3. In the ACL's the following expansion variables are available.
-
-proxy_host_address   The (internal) src IP of the proxy server
-                     making the connection to the Exim server.
-proxy_host_port      The (internal) src port the proxy server is
-                     using to connect to the Exim server.
-proxy_target_address The dest (public) IP of the remote host to
-                     the proxy server.
-proxy_target_port    The dest port the remote host is using to
-                     connect to the proxy server.
-proxy_session        Boolean, yes/no, the connected host is required
-                     to use Proxy Protocol.
-
-There is no expansion for a failed proxy session, however you can detect
-it by checking if $proxy_session is true but $proxy_host is empty.  As
-an example, in my connect ACL, I have:
-
-  warn    condition      = ${if and{ {bool{$proxy_session}} \
-                                     {eq{$proxy_host_address}{}} } }
-          log_message    = Failed required proxy protocol negotiation \
-                           from $sender_host_name [$sender_host_address]
-
-  warn    condition      = ${if and{ {bool{$proxy_session}} \
-                                     {!eq{$proxy_host_address}{}} } }
-          # But don't log health probes from the proxy itself
-          condition      = ${if eq{$proxy_host_address}{$sender_host_address} \
-                                {false}{true}}
-          log_message    = Successfully proxied from $sender_host_name \
-                           [$sender_host_address] through proxy protocol \
-                           host $proxy_host_address
-
-  # Possibly more clear
-  warn logwrite = Remote Source Address: $sender_host_address:$sender_host_port
-       logwrite = Proxy Target Address: $proxy_target_address:$proxy_target_port
-       logwrite = Proxy Internal Address: $proxy_host_address:$proxy_host_port
-       logwrite = Internal Server Address: $received_ip_address:$received_port
-
-
-4. Recommended ACL additions:
-   - Since the real connections are all coming from your proxy, and the
-     per host connection tracking is done before Proxy Protocol is
-     evaluated, smtp_accept_max_per_host must be set high enough to
-     handle all of the parallel volume you expect per inbound proxy.
-   - With the smtp_accept_max_per_host set so high, you lose the ability
-     to protect your server from massive numbers of inbound connections
-     from one IP.  In order to prevent your server from being DOS'd, you
-     need to add a per connection ratelimit to your connect ACL.  I
-     suggest something like this:
-
-  # Set max number of connections per host
-  LIMIT   = 5
-  # Or do some kind of IP lookup in a flat file or database
-  # LIMIT = ${lookup{$sender_host_address}iplsearch{/etc/exim/proxy_limits}}
-
-  defer   message        = Too many connections from this IP right now
-          ratelimit      = LIMIT / 5s / per_conn / strict
-
-
-5. Runtime issues to be aware of:
-   - The proxy has 3 seconds (hard-coded in the source code) to send the
-     required Proxy Protocol header after it connects.  If it does not,
-     the response to any commands will be:
-     "503 Command refused, required Proxy negotiation failed"
-   - If the incoming connection is configured in Exim to be a Proxy
-     Protocol host, but the proxy is not sending the header, the banner
-     does not get sent until the timeout occurs.  If the sending host
-     sent any input (before the banner), this causes a standard Exim
-     synchronization error (i.e. trying to pipeline before PIPELINING
-     was advertised).
-   - This is not advised, but is mentioned for completeness if you have
-     a specific internal configuration that you want this:  If the Exim
-     server only has an internal IP address and no other machines in your
-     organization will connect to it to try to send email, you may
-     simply set the hostlist to "*", however, this will prevent local
-     mail programs from working because that would require mail from
-     localhost to use Proxy Protocol.  Again, not advised!
-
-6. Example of a refused connection because the Proxy Protocol header was
-not sent from a host configured to use Proxy Protocol.  In the example,
-the 3 second timeout occurred (when a Proxy Protocol banner should have
-been sent), the banner was displayed to the user, but all commands are
-rejected except for QUIT:
-
-# nc mail.example.net 25
-220-mail.example.net, ESMTP Exim 4.82+proxy, Mon, 04 Nov 2013 10:45:59
-220 -0800 RFC's enforced
-EHLO localhost
-503 Command refused, required Proxy negotiation failed
-QUIT
-221 mail.example.net closing connection
-
-
-
-
 DANE
 ------------------------------------------------------------
 DNS-based Authentication of Named Entities, as applied
index c6d6930..a67343b 100644 (file)
@@ -402,6 +402,7 @@ EXIM_MONITOR=eximon.bin
 #
 # WITH_OLD_CLAMAV_STREAM=yes
 
+
 #------------------------------------------------------------------------------
 # By default Exim includes code to support DKIM (DomainKeys Identified
 # Mail, RFC4871) signing and verification.  Verification of signatures is
@@ -487,9 +488,6 @@ EXIM_MONITOR=eximon.bin
 # CFLAGS += -I/usr/local/include
 # LDFLAGS += -lhiredis
 
-# Uncomment the following line to enable Experimental Proxy Protocol
-# EXPERIMENTAL_PROXY=yes
-
 # Uncomment the following line to enable support for checking certificate
 # ownership
 # EXPERIMENTAL_CERTNAMES=yes
@@ -921,11 +919,18 @@ ZCAT_COMMAND=/usr/bin/zcat
 
 #------------------------------------------------------------------------------
 # Proxying.
-# If you may want to use outbound (client-side) proxying, uncomment the SOCKS
-# line below.
+#
+# If you may want to use outbound (client-side) proxying, using Socks5,
+# uncomment the line below.
 
 # SUPPORT_SOCKS=yes
 
+# If you may want to use inbound (server-side) proxying, using Proxy Protocol,
+# uncomment the line below.
+
+# SUPPORT_PROXY=yes
+
+
 
 #------------------------------------------------------------------------------
 # Support for authentication via Radius is also available. The Exim support,
index 54b5598..6e2c220 100644 (file)
@@ -138,6 +138,7 @@ it's a default value. */
 #define SUPPORT_MBX
 #define SUPPORT_MOVE_FROZEN_MESSAGES
 #define SUPPORT_PAM
+#define SUPPORT_PROXY
 #define SUPPORT_SOCKS
 #define SUPPORT_TLS
 #define SUPPORT_TRANSLATE_IP_ADDRESS
@@ -177,7 +178,6 @@ it's a default value. */
 #define EXPERIMENTAL_DMARC
 #define EXPERIMENTAL_EVENT
 #define EXPERIMENTAL_INTERNATIONAL
-#define EXPERIMENTAL_PROXY
 #define EXPERIMENTAL_REDIS
 #define EXPERIMENTAL_SPF
 #define EXPERIMENTAL_SRS
index e6777a3..4e90ca8 100644 (file)
@@ -829,6 +829,9 @@ fprintf(f, "Support for:");
 #ifndef DISABLE_PRDR
   fprintf(f, " PRDR");
 #endif
+#ifdef SUPPORT_PROXY
+  fprintf(f, " PROXY");
+#endif
 #ifdef SUPPORT_SOCKS
   fprintf(f, " SOCKS");
 #endif
@@ -856,9 +859,6 @@ fprintf(f, "Support for:");
 #ifdef EXPERIMENTAL_INTERNATIONAL
   fprintf(f, " Experimental_International");
 #endif
-#ifdef EXPERIMENTAL_PROXY
-  fprintf(f, " Experimental_Proxy");
-#endif
 #ifdef EXPERIMENTAL_EVENT
   fprintf(f, " Experimental_Event");
 #endif
index bd16f49..f3baee9 100644 (file)
@@ -615,7 +615,7 @@ static var_entry var_table[] = {
   { "prdr_requested",      vtype_bool,        &prdr_requested },
 #endif
   { "primary_hostname",    vtype_stringptr,   &primary_hostname },
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   { "proxy_host_address",  vtype_stringptr,   &proxy_host_address },
   { "proxy_host_port",     vtype_int,         &proxy_host_port },
   { "proxy_session",       vtype_bool,        &proxy_session },
index eea84d3..fbfb9b8 100644 (file)
@@ -875,7 +875,7 @@ bit_table log_options[]        = { /* must be in alphabetical order */
   BIT_TABLE(L, outgoing_interface),
   BIT_TABLE(L, outgoing_port),
   BIT_TABLE(L, pid),
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   BIT_TABLE(L, proxy),
 #endif
   BIT_TABLE(L, queue_run),
@@ -1001,10 +1001,10 @@ int     process_info_len       = 0;
 uschar *process_log_path       = NULL;
 BOOL    prod_requires_admin    = TRUE;
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
+uschar *hosts_proxy            = US"";
 uschar *proxy_host_address     = US"";
 int     proxy_host_port        = 0;
-uschar *proxy_required_hosts   = US"";
 BOOL    proxy_session          = FALSE;
 BOOL    proxy_session_failed   = FALSE;
 uschar *proxy_target_address   = US"";
index fed0495..4263e10 100644 (file)
@@ -645,10 +645,10 @@ extern int     process_info_len;
 extern uschar *process_log_path;       /* Alternate path */
 extern BOOL    prod_requires_admin;    /* TRUE if prodding requires admin */
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
+extern uschar *hosts_proxy;            /* Hostlist which (require) use proxy protocol */
 extern uschar *proxy_host_address;     /* IP of host being proxied */
 extern int     proxy_host_port;        /* Port of host being proxied */
-extern uschar *proxy_required_hosts;   /* Hostlist which (require) use proxy protocol */
 extern BOOL    proxy_session;          /* TRUE if receiving mail from valid proxy  */
 extern BOOL    proxy_session_failed;   /* TRUE if required proxy negotiation failed */
 extern uschar *proxy_target_address;   /* IP of proxy server inbound */
index 4df7e51..1cec4ab 100644 (file)
@@ -194,7 +194,7 @@ record. */
 /* Wait this long before determining that a Proxy Protocol configured
 host isn't speaking the protocol, and so is disallowed. Can be moved to
 runtime configuration if per site settings become needed. */
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 #define PROXY_NEGOTIATION_TIMEOUT_SEC 3
 #define PROXY_NEGOTIATION_TIMEOUT_USEC 0
 #endif
index f127d52..cba33ac 100644 (file)
@@ -285,6 +285,9 @@ static optionlist optionlist_config[] = {
   { "host_lookup_order",        opt_stringptr,   &host_lookup_order },
   { "host_reject_connection",   opt_stringptr,   &host_reject_connection },
   { "hosts_connection_nolog",   opt_stringptr,   &hosts_connection_nolog },
+#ifdef SUPPORT_PROXY
+  { "hosts_proxy",              opt_stringptr,   &proxy_required_hosts },
+#endif
   { "hosts_treat_as_local",     opt_stringptr,   &hosts_treat_as_local },
 #ifdef LOOKUP_IBASE
   { "ibase_servers",            opt_stringptr,   &ibase_servers },
@@ -354,9 +357,6 @@ static optionlist optionlist_config[] = {
   { "print_topbitchars",        opt_bool,        &print_topbitchars },
   { "process_log_path",         opt_stringptr,   &process_log_path },
   { "prod_requires_admin",      opt_bool,        &prod_requires_admin },
-#ifdef EXPERIMENTAL_PROXY
-  { "proxy_required_hosts",     opt_stringptr,   &proxy_required_hosts },
-#endif
   { "qualify_domain",           opt_stringptr,   &qualify_domain_sender },
   { "qualify_recipient",        opt_stringptr,   &qualify_domain_recipient },
   { "queue_domains",            opt_stringptr,   &queue_domains },
index b430ee2..01f4616 100644 (file)
@@ -3775,7 +3775,7 @@ if (prdr_requested)
   s = string_append(s, &size, &sptr, 1, US" PRDR");
 #endif
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 if (proxy_session && LOGGING(proxy))
   s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host_address);
 #endif
index d940c69..d99f02e 100644 (file)
@@ -96,7 +96,7 @@ enum {
 
   QUIT_CMD, HELP_CMD,
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   PROXY_FAIL_IGNORE_CMD,
 #endif
 
@@ -583,7 +583,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /*************************************************
 *     Restore socket timeout to previous value   *
 *************************************************/
@@ -620,7 +620,7 @@ int rc;
 /* Cannot configure local connection as a proxy inbound */
 if (sender_host_address == NULL) return proxy_session;
 
-rc = verify_check_this_host(&proxy_required_hosts, NULL, NULL,
+rc = verify_check_this_host(&hosts_proxy, NULL, NULL,
                            sender_host_address, NULL);
 if (rc == OK)
   {
@@ -1025,7 +1025,7 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  #ifdef EXPERIMENTAL_PROXY
+  #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
   if (proxy_session && proxy_session_failed)
     {
@@ -1082,7 +1082,7 @@ for (p = cmd_list; p < cmd_list_end; p++)
     }
   }
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* Only allow QUIT command if Proxy Protocol parsing failed */
 if (proxy_session && proxy_session_failed)
   return PROXY_FAIL_IGNORE_CMD;
@@ -2311,7 +2311,7 @@ if (!sender_host_unknown)
 
 if (smtp_batched_input) return TRUE;
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* If valid Proxy Protocol source is connecting, set up session.
  * Failure will not allow any SMTP function other than QUIT. */
 proxy_session = FALSE;
@@ -5103,11 +5103,11 @@ while (done <= 0)
     done = 1;   /* Pretend eof - drops connection */
     break;
 
-    #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
     smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
     break;
-    #endif
+#endif
 
     default:
     if (unknown_command_count++ >= smtp_max_unknown_commands)