Proxy Protocol - Server support
authorTodd Lyons <tlyons@exim.org>
Sat, 30 Nov 2013 19:31:21 +0000 (11:31 -0800)
committerTodd Lyons <tlyons@exim.org>
Sat, 30 Nov 2013 19:31:21 +0000 (11:31 -0800)
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.

12 files changed:
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
doc/doc-txt/experimental-spec.txt
src/src/EDITME
src/src/config.h.defaults
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 989ec52c5cb4e6907c7b0208def0faa8205242d5..12369e4346d7f7e4ccaca8316c1a478ec4cf7e7d 100644 (file)
@@ -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
 -----------------
index c901097246e9670c33a7819758acbe9650d6f610..11cfcffa07e5f0932fac089fda8ec0fa2f83c079 100644 (file)
@@ -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
 ------------
index b33612f43d319c7ffe2b760c7377e626a8c8bce7..92790ae33a158bfd05fc748c0aafc0b32f16bb55 100644 (file)
@@ -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@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     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
index 3f818f35581a6af7445f6e0560fe9478168e333e..7377af844d0694a8a7571e3e4b24e31195326e10 100644 (file)
@@ -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                  #
index 8c1e799da313a7023abf721ef3656b107b61b29c..f68537a2afb9fe43e37904ab44cef6d7e0555133 100644 (file)
@@ -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
index de9f7b5ad994861c13e445c21c55e67ce33b12af..bf1f82c6facacd332de02338667a7bcf2662a7d9 100644 (file)
@@ -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 },
index 133a7bf740d9a13349af77d00240f383108e0c47..ec6700df5fa5432a2b3bd41bc51bfd7fe4d1715d 100644 (file)
@@ -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;
index 265f94e60617bd505925a17209a468883e21ba28..5661489a7b66d1ff0b9a14e8fcbc0a6256823809 100644 (file)
@@ -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 */
index a73bb0ba67feab43c9b274a107a8f99629f52d32..9f59f7acde369442bf3f9a3c043fb81969c66599 100644 (file)
@@ -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 */
 
index 77c798412fd36ad145d000e2ef16bbd22ad7ce6c..c5200de67519b9e2221b0d72726c964e40cc1333 100644 (file)
@@ -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 },
index 072fee9f1f27aaf4fe50541e1ee17e94d3f5ba69..9205436d335283f2447e861aae0d4afb5aca4efa 100644 (file)
@@ -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);
index 4740aa5ff519e86e7ecbca2ff8815f7aa5523aa7..144ad28a22e82fef88638a6fd5326eca8b130404 100644 (file)
@@ -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)