[exim-cvs] Proxy Protocol - Server support

Top Page
Delete this message
Reply to this message
Author: Exim Git Commits Mailing List
Date:  
To: exim-cvs
Subject: [exim-cvs] Proxy Protocol - Server support
Gitweb: http://git.exim.org/exim.git/commitdiff/a3c8643131ef2a3f8100de7027be6bdf4e2ef3af
Commit:     a3c8643131ef2a3f8100de7027be6bdf4e2ef3af
Parent:     5c7706b0c39a7cbb031f1d86594b41e0c96008fa
Author:     Todd Lyons <tlyons@???>
AuthorDate: Sat Nov 30 11:31:21 2013 -0800
Committer:  Todd Lyons <tlyons@???>
CommitDate: Sat Nov 30 11:31:21 2013 -0800


    Proxy Protocol - Server support


    Initial conf setting and expansions
    Logging setting whether to record proxy host, off by default
    Put PROXY processing before connect ACL
    Fix incoming address logging
    Add Proxy Protocol to ChangeLog
    Set window for Proxy Protocol header to be sent
    Update docs and EDITME.
---
 doc/doc-txt/ChangeLog             |    4 +
 doc/doc-txt/NewStuff              |    5 +
 doc/doc-txt/experimental-spec.txt |  112 +++++++++++++
 src/src/EDITME                    |    3 +
 src/src/config.h.defaults         |    1 +
 src/src/expand.c                  |    5 +
 src/src/globals.c                 |   12 ++
 src/src/globals.h                 |    9 +
 src/src/macros.h                  |   10 +
 src/src/readconf.c                |    3 +
 src/src/receive.c                 |    7 +
 src/src/smtp_in.c                 |  334 ++++++++++++++++++++++++++++++++++++-
 12 files changed, 504 insertions(+), 1 deletions(-)


diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 989ec52..12369e4 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -23,6 +23,10 @@ JH/01 Fix memory-handling in use of acl as a conditional; avoid free of
 TL/01 LDAP support uses per connection or global context settings, depending
       upon the detected version of the libraries at build time.


+TL/02 Experimental Proxy Protocol support: allows a proxied SMTP connection
+      to extract and use the src ip:port in logging and expansions as if it
+      were a direct connection from the outside internet.
+


Exim version 4.82
-----------------
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index c901097..11cfcff 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -9,6 +9,11 @@ the documentation is updated, this file is reduced to a short list.
Version 4.83
------------

+ 1. If built with the EXPERIMENTAL_PROXY feature enabled, Exim can be
+    configured to expect an initial header from a proxy that will make the
+    actual external source IP:host be used in exim instead of the IP of the
+    proxy that is connecting to it.
+


Version 4.82
------------
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt
index b33612f..92790ae 100644
--- a/doc/doc-txt/experimental-spec.txt
+++ b/doc/doc-txt/experimental-spec.txt
@@ -1015,6 +1015,118 @@ 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
+
+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@???
+  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@???
+  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     The src IP of the proxy server making the connection
+proxy_port     The src port the proxy server is using
+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}{}} } }
+          log_message    = Failed required proxy protocol negotiation \
+                           from $sender_host_name [$sender_host_address]
+
+  warn    condition      = ${if and{ {bool{$proxy_session}} \
+                                     {!eq{$proxy_host}{}} } }
+          # But don't log health probes from the proxy itself
+          condition      = ${if eq{$proxy_host}{$sender_host_address} \
+                                {false}{true}}
+          log_message    = Successfully proxied from $sender_host_name \
+                           [$sender_host_address] through proxy protocol \
+                           host $proxy_host
+
+4. Runtime issues to be aware of:
+   - 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.
+   - 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!
+
+5. 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
+
+


--------------------------------------------------------------
End of file
diff --git a/src/src/EDITME b/src/src/EDITME
index 3f818f3..7377af8 100644
--- a/src/src/EDITME
+++ b/src/src/EDITME
@@ -480,6 +480,9 @@ EXIM_MONITOR=eximon.bin
# CFLAGS += -I/usr/local/include
# LDFLAGS += -lhiredis

+# Uncomment the following line to enable Experimental Proxy Protocol
+# EXPERIMENTAL_PROXY=yes
+

 ###############################################################################
 #                 THESE ARE THINGS YOU MIGHT WANT TO SPECIFY                  #
diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults
index 8c1e799..f68537a 100644
--- a/src/src/config.h.defaults
+++ b/src/src/config.h.defaults
@@ -169,6 +169,7 @@ it's a default value. */
 #define EXPERIMENTAL_DMARC
 #define EXPERIMENTAL_OCSP
 #define EXPERIMENTAL_PRDR
+#define EXPERIMENTAL_PROXY
 #define EXPERIMENTAL_REDIS
 #define EXPERIMENTAL_SPF
 #define EXPERIMENTAL_SRS
diff --git a/src/src/expand.c b/src/src/expand.c
index de9f7b5..bf1f82c 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -556,6 +556,11 @@ static var_entry var_table[] = {
   { "parent_local_part",   vtype_stringptr,   &deliver_localpart_parent },
   { "pid",                 vtype_pid,         NULL },
   { "primary_hostname",    vtype_stringptr,   &primary_hostname },
+#ifdef EXPERIMENTAL_PROXY
+  { "proxy_host",          vtype_stringptr,   &proxy_host },
+  { "proxy_port",          vtype_int,         &proxy_port },
+  { "proxy_session",       vtype_bool,        &proxy_session },
+#endif
   { "prvscheck_address",   vtype_stringptr,   &prvscheck_address },
   { "prvscheck_keynum",    vtype_stringptr,   &prvscheck_keynum },
   { "prvscheck_result",    vtype_stringptr,   &prvscheck_result },
diff --git a/src/src/globals.c b/src/src/globals.c
index 133a7bf..ec6700d 100644
--- a/src/src/globals.c
+++ b/src/src/globals.c
@@ -797,6 +797,9 @@ bit_table log_options[]        = {
   { US"lost_incoming_connection",     L_lost_incoming_connection },
   { US"outgoing_port",                LX_outgoing_port },
   { US"pid",                          LX_pid },
+#ifdef EXPERIMENTAL_PROXY
+  { US"proxy",                        LX_proxy },
+#endif
   { US"queue_run",                    L_queue_run },
   { US"queue_time",                   LX_queue_time },
   { US"queue_time_overall",           LX_queue_time_overall },
@@ -914,6 +917,15 @@ uschar  process_info[PROCESS_INFO_SIZE];
 int     process_info_len       = 0;
 uschar *process_log_path       = NULL;
 BOOL    prod_requires_admin    = TRUE;
+
+#ifdef EXPERIMENTAL_PROXY
+uschar *proxy_host             = US"";
+int     proxy_port             = 0;
+uschar *proxy_required_hosts   = US"";
+BOOL    proxy_session          = FALSE;
+BOOL    proxy_session_failed   = FALSE;
+#endif
+
 uschar *prvscheck_address      = NULL;
 uschar *prvscheck_keynum       = NULL;
 uschar *prvscheck_result       = NULL;
diff --git a/src/src/globals.h b/src/src/globals.h
index 265f94e..5661489 100644
--- a/src/src/globals.h
+++ b/src/src/globals.h
@@ -592,6 +592,15 @@ extern uschar  process_info[];         /* For SIGUSR1 output */
 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
+extern uschar *proxy_host;             /* IP of proxy server */
+extern int     proxy_port;             /* Port of proxy server */
+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 */
+#endif
+
 extern uschar *prvscheck_address;      /* Set during prvscheck expansion item */
 extern uschar *prvscheck_keynum;       /* Set during prvscheck expansion item */
 extern uschar *prvscheck_result;       /* Set during prvscheck expansion item */
diff --git a/src/src/macros.h b/src/src/macros.h
index a73bb0b..9f59f7a 100644
--- a/src/src/macros.h
+++ b/src/src/macros.h
@@ -177,6 +177,14 @@ record. */


#define WAIT_NAME_MAX 50

+/* 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
+#define PROXY_NEGOTIATION_TIMEOUT_SEC 3
+#define PROXY_NEGOTIATION_TIMEOUT_USEC 0
+#endif
+
/* Fixed option values for all PCRE functions */

 #define PCRE_COPT 0   /* compile */
@@ -414,6 +422,7 @@ set all the bits in a multi-word selector. */
 #define LX_unknown_in_list             0x81000000
 #define LX_8bitmime                    0x82000000
 #define LX_smtp_mailauth               0x84000000
+#define LX_proxy                       0x88000000


 #define L_default     (L_connection_reject        | \
                        L_delay_delivery           | \
@@ -481,6 +490,7 @@ to conflict with system errno values. */
 #define ERRNO_RCPT4XX        (-44)   /* RCPT gave 4xx error */
 #define ERRNO_MAIL4XX        (-45)   /* MAIL gave 4xx error */
 #define ERRNO_DATA4XX        (-46)   /* DATA gave 4xx error */
+#define ERRNO_PROXYFAIL      (-47)   /* Negotiation failed for proxy configured host */


/* These must be last, so all retry deferments can easily be identified */

diff --git a/src/src/readconf.c b/src/src/readconf.c
index 77c7984..c5200de 100644
--- a/src/src/readconf.c
+++ b/src/src/readconf.c
@@ -332,6 +332,9 @@ 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 072fee9..9205436 100644
--- a/src/src/receive.c
+++ b/src/src/receive.c
@@ -3755,6 +3755,13 @@ if (sender_host_authenticated != NULL)
 if (prdr_requested)
   s = string_append(s, &size, &sptr, 1, US" PRDR");
 #endif
+#ifdef EXPERIMENTAL_PROXY
+if (proxy_session &&
+    (log_extra_selector & LX_proxy) != 0)
+  {
+  s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host);
+  }
+#endif


sprintf(CS big_buffer, "%d", msg_size);
s = string_append(s, &size, &sptr, 2, US" S=", big_buffer);
diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c
index 4740aa5..144ad28 100644
--- a/src/src/smtp_in.c
+++ b/src/src/smtp_in.c
@@ -94,6 +94,10 @@ enum {

QUIT_CMD, HELP_CMD,

+#ifdef EXPERIMENTAL_PROXY
+ PROXY_FAIL_IGNORE_CMD,
+#endif
+
/* These are specials that don't correspond to actual commands */

EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD,
@@ -549,6 +553,294 @@ exim_exit(EXIT_FAILURE);



+#ifdef EXPERIMENTAL_PROXY
+/*************************************************
+*       Check if host is required proxy host     *
+*************************************************/
+/* The function determines if inbound host will be a regular smtp host
+or if it is configured that it must use Proxy Protocol.
+
+Arguments: none
+Returns:   bool
+*/
+
+static BOOL
+check_proxy_protocol_host()
+{
+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,
+                           sender_host_address, NULL);
+if (rc == OK)
+  {
+  DEBUG(D_receive)
+    debug_printf("Detected proxy protocol configured host\n");
+  proxy_session = TRUE;
+  }
+return proxy_session;
+}
+
+
+/*************************************************
+*           Flush waiting input string           *
+*************************************************/
+static void
+flush_input()
+{
+int rc;
+
+rc = smtp_getc();
+while (rc != '\n')             /* End of input string */
+  {
+  rc = smtp_getc();
+  }
+}
+
+
+/*************************************************
+*         Setup host for proxy protocol          *
+*************************************************/
+/* The function configures the connection based on a header from the
+inbound host to use Proxy Protocol. The specification is very exact
+so exit with an error if do not find the exact required pieces. This
+includes an incorrect number of spaces separating args.
+
+Arguments: none
+Returns:   int
+*/
+
+static BOOL
+setup_proxy_protocol_host()
+{
+union {
+  struct {
+    uschar line[108];
+  } v1;
+  struct {
+    uschar sig[12];
+    uschar ver;
+    uschar cmd;
+    uschar fam;
+    uschar len;
+    union {
+      struct { /* TCP/UDP over IPv4, len = 12 */
+        uint32_t src_addr;
+        uint32_t dst_addr;
+        uint16_t src_port;
+        uint16_t dst_port;
+      } ip4;
+      struct { /* TCP/UDP over IPv6, len = 36 */
+        uint8_t  src_addr[16];
+        uint8_t  dst_addr[16];
+        uint16_t src_port;
+        uint16_t dst_port;
+      } ip6;
+      struct { /* AF_UNIX sockets, len = 216 */
+        uschar   src_addr[108];
+        uschar   dst_addr[108];
+      } unx;
+    } addr;
+  } v2;
+} hdr;
+
+int size, ret, fd;
+uschar *tmpip;
+const char v2sig[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02";
+uschar *iptype;  /* To display debug info */
+struct timeval tv;
+
+fd = fileno(smtp_in);
+
+/* Proxy Protocol host must send header within a short time
+(default 3 seconds) or it's considered invalid */
+tv.tv_sec  = PROXY_NEGOTIATION_TIMEOUT_SEC;
+tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
+setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,
+           sizeof(struct timeval));
+do
+  {
+  ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK);
+  }
+  while (ret == -1 && errno == EINTR);
+
+if (ret == -1)
+  return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL;
+
+if (ret >= 16 &&
+    memcmp(&hdr.v2, v2sig, 13) == 0)
+  {
+  DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
+  size = 16 + hdr.v2.len;
+  if (ret < size)
+    {
+    DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header\n");
+    goto proxyfail;
+    }
+  switch (hdr.v2.cmd)
+    {
+    case 0x01: /* PROXY command */
+      switch (hdr.v2.fam)
+        {
+        case 0x11:  /* TCPv4 */
+          tmpip = string_sprintf("%s", hdr.v2.addr.ip4.src_addr);
+          if (!string_is_ip_address(tmpip,NULL))
+            return ERRNO_PROXYFAIL;
+          sender_host_address = tmpip;
+          sender_host_port    = hdr.v2.addr.ip4.src_port;
+          goto done;
+        case 0x21:  /* TCPv6 */
+          tmpip = string_sprintf("%s", hdr.v2.addr.ip6.src_addr);
+          if (!string_is_ip_address(tmpip,NULL))
+            return ERRNO_PROXYFAIL;
+          sender_host_address = tmpip;
+          sender_host_port    = hdr.v2.addr.ip6.src_port;
+          goto done;
+        }
+      /* Unsupported protocol, keep local connection address */
+      break;
+    case 0x00: /* LOCAL command */
+      /* Keep local connection address for LOCAL */
+      break;
+    default:
+      DEBUG(D_receive) debug_printf("Unsupported PROXYv2 command\n");
+      goto proxyfail;
+    }
+  }
+else if (ret >= 8 &&
+         memcmp(hdr.v1.line, "PROXY", 5) == 0)
+  {
+  uschar *p = string_copy(hdr.v1.line);
+  uschar *end = memchr(p, '\r', ret - 1);
+  uschar *sp;     /* Utility variables follow */
+  int     tmp_port;
+  char   *endc;
+
+  if (!end || end[1] != '\n')
+    {
+    DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
+    goto proxyfail;
+    }
+  *end = '\0'; /* Terminate the string */
+  size = end + 2 - hdr.v1.line; /* Skip header + CRLF */
+  DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
+  /* Step through the string looking for the required fields. Ensure
+     strict adherance to required formatting, exit for any error. */
+  p += 5;
+  if (!isspace(*(p++)))
+    {
+    DEBUG(D_receive) debug_printf("Missing space after PROXY command\n");
+    goto proxyfail;
+    }
+  if (!Ustrncmp(p, CCS"TCP4", 4))
+    iptype = US"IPv4";
+  else if (!Ustrncmp(p,CCS"TCP6", 4))
+    iptype = US"IPv6";
+  else if (!Ustrncmp(p,CCS"UNKNOWN", 7))
+    {
+    iptype = US"Unknown";
+    goto done;
+    }
+  else
+    {
+    DEBUG(D_receive) debug_printf("Invalid TCP type\n");
+    goto proxyfail;
+    }
+
+  p += Ustrlen(iptype);
+  if (!isspace(*(p++)))
+    {
+    DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n");
+    goto proxyfail;
+    }
+  /* Find the end of the arg */
+  if ((sp = Ustrchr(p, ' ')) == NULL)
+    {
+    DEBUG(D_receive)
+      debug_printf("Did not find proxied src %s\n", iptype);
+    goto proxyfail;
+    }
+  *sp = '\0';
+  if(!string_is_ip_address(p,NULL))
+    {
+    DEBUG(D_receive)
+      debug_printf("Proxied src arg is not an %s address\n", iptype);
+    goto proxyfail;
+    }
+  proxy_host = sender_host_address;
+  sender_host_address = p;
+  p = sp + 1;
+  if ((sp = Ustrchr(p, ' ')) == NULL)
+    {
+    DEBUG(D_receive)
+      debug_printf("Did not find proxy dest %s\n", iptype);
+    goto proxyfail;
+    }
+  *sp = '\0';
+  if(!string_is_ip_address(p,NULL))
+    {
+    DEBUG(D_receive)
+      debug_printf("Proxy dest arg is not an %s address\n", iptype);
+    goto proxyfail;
+    }
+  /* Should save dest ip somewhere? */
+  p = sp + 1;
+  if ((sp = Ustrchr(p, ' ')) == NULL)
+    {
+    DEBUG(D_receive) debug_printf("Did not find proxied src port\n");
+    goto proxyfail;
+    }
+  *sp = '\0';
+  tmp_port = strtol(CCS p,&endc,10);
+  if (*endc || tmp_port == 0)
+    {
+    DEBUG(D_receive)
+      debug_printf("Proxied src port '%s' not an integer\n", p);
+    goto proxyfail;
+    }
+  proxy_port = sender_host_port;
+  sender_host_port = tmp_port;
+  p = sp + 1;
+  if ((sp = Ustrchr(p, '\0')) == NULL)
+    {
+    DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
+    goto proxyfail;
+    }
+  tmp_port = strtol(CCS p,&endc,10);
+  if (*endc || tmp_port == 0)
+    {
+    DEBUG(D_receive)
+      debug_printf("Proxy dest port '%s' not an integer\n", p);
+    goto proxyfail;
+    }
+  /* Should save dest port somewhere? */
+  /* Already checked for /r /n above. Good V1 header received. */
+  goto done;
+  }
+else
+  {
+  /* Wrong protocol */
+  DEBUG(D_receive) debug_printf("Wrong proxy protocol specified\n");
+  goto proxyfail;
+  }
+
+proxyfail:
+/* Don't flush any potential buffer contents. Any input should cause a
+synchronization failure or we just don't want to speak SMTP to them */
+return FALSE;
+
+done:
+flush_input();
+DEBUG(D_receive)
+  debug_printf("Valid %s sender from Proxy Protocol header\n",
+               iptype);
+return proxy_session;
+}
+#endif
+
+
+
 /*************************************************
 *           Read one command line                *
 *************************************************/
@@ -622,6 +914,14 @@ if required. */


 for (p = cmd_list; p < cmd_list_end; p++)
   {
+  #ifdef EXPERIMENTAL_PROXY
+  /* Only allow QUIT command if Proxy Protocol parsing failed */
+  if (proxy_session && proxy_session_failed)
+    {
+    if (p->cmd != QUIT_CMD)
+      continue;
+    }
+  #endif
   if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 &&
        (smtp_cmd_buffer[p->len-1] == ':' ||   /* "mail from:" or "rcpt to:" */
         smtp_cmd_buffer[p->len] == 0 ||
@@ -669,6 +969,12 @@ for (p = cmd_list; p < cmd_list_end; p++)
     }
   }


+#ifdef EXPERIMENTAL_PROXY
+/* Only allow QUIT command if Proxy Protocol parsing failed */
+if (proxy_session && proxy_session_failed)
+ return PROXY_FAIL_IGNORE_CMD;
+#endif
+
/* Enforce synchronization for unknown commands */

 if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
@@ -1832,6 +2138,28 @@ if (!sender_host_unknown)


if (smtp_batched_input) return TRUE;

+#ifdef EXPERIMENTAL_PROXY
+/* If valid Proxy Protocol source is connecting, set up session.
+ * Failure will not allow any SMTP function other than QUIT. */
+proxy_session = FALSE;
+proxy_session_failed = FALSE;
+if (check_proxy_protocol_host())
+  {
+  if (setup_proxy_protocol_host() == FALSE)
+    {
+    proxy_session_failed = TRUE;
+    DEBUG(D_receive)
+      debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+    }
+  else
+    {
+    sender_host_name = NULL;
+    (void)host_name_lookup();
+    host_build_sender_fullhost();
+    }
+  }
+#endif
+
 /* Run the ACL if it exists */


user_msg = NULL;
@@ -2594,7 +2922,6 @@ smtp_respond(code, len, TRUE, user_msg);



-
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -4379,6 +4706,11 @@ while (done <= 0)
     done = 1;   /* Pretend eof - drops connection */
     break;


+    #ifdef EXPERIMENTAL_PROXY
+    case PROXY_FAIL_IGNORE_CMD:
+    smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
+    break;
+    #endif


     default:
     if (unknown_command_count++ >= smtp_max_unknown_commands)