From cee5f132d1b81d3b8738944036eb02af418b54be Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 5 Dec 2015 20:21:51 +0000 Subject: [PATCH] PROXY: Move Proxy Protocol support from Experimental to mainline. No testsuite coverage yet. --- doc/doc-docbook/spec.xfpt | 103 ++++++++++++++++++++- doc/doc-txt/ChangeLog | 4 + doc/doc-txt/experimental-spec.txt | 145 ------------------------------ src/src/EDITME | 15 ++-- src/src/config.h.defaults | 2 +- src/src/exim.c | 6 +- src/src/expand.c | 2 +- src/src/globals.c | 6 +- src/src/globals.h | 4 +- src/src/macros.h | 2 +- src/src/readconf.c | 6 +- src/src/receive.c | 2 +- src/src/smtp_in.c | 16 ++-- 13 files changed, 139 insertions(+), 174 deletions(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 73887d4e7..6096e1df2 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -12032,6 +12032,16 @@ a single-component name, Exim calls &[gethostbyname()]& (or 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 &<>&. +.wen + .new .vitem &$prdr_requested$& .cindex "PRDR" "variable for" @@ -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 &<>&. +.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 &<>& 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 diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index d57c3db00..1fa19daad 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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 ----------------- diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index f0f1035ea..45e7d1ba1 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -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 diff --git a/src/src/EDITME b/src/src/EDITME index c6d693017..a67343b3a 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -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, diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index 54b5598c9..6e2c22063 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -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 diff --git a/src/src/exim.c b/src/src/exim.c index e6777a3db..4e90ca8fd 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -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 diff --git a/src/src/expand.c b/src/src/expand.c index bd16f4956..f3baee9af 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -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 }, diff --git a/src/src/globals.c b/src/src/globals.c index eea84d3e3..fbfb9b8a2 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -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""; diff --git a/src/src/globals.h b/src/src/globals.h index fed049531..4263e104d 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -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 */ diff --git a/src/src/macros.h b/src/src/macros.h index 4df7e5150..1cec4abd5 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -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 diff --git a/src/src/readconf.c b/src/src/readconf.c index f127d5202..cba33ac35 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -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 }, diff --git a/src/src/receive.c b/src/src/receive.c index b430ee261..01f461650 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -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 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index d940c69f0..d99f02e69 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -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) -- 2.25.1