[PATCH] Add support for Proxy Protocol to mail SMTP

Maxim Vladimirsky maxim at mailgunhq.com
Thu Nov 3 16:41:03 UTC 2016


Hi Folks,

I was not sure if you would be interested in this, but decided to run it by
you anyway.

We need to run Nginx as an SMTP proxy sitting behind ELB in AWS, but we
also want the upstream SMTP server to get the real client ip, so Nginx is
configured to provide it via an XCLIENT command. However the stock version
of Nginx provides ELB's ip instead, because it does not recognize the Proxy
Protocol header (http://docs.aws.amazon.com/elasticloadbalancing/latest/
classic/enable-proxy-protocol.html#proxy-protocol) sent to it by ELB. The
following patch updates the mail module so that it can be configured to
expect Proxy Protocol header by setting `proxy_protocol on`. In that case
Proxy Protocol header is parsed, a client IP is retrieved and passed to an
SMTP upstream in an XCLIENT command.

Here it is:

# HG changeset patch
# User Maxim Vladimirskiy <maxim at mailgunhq.com>
# Date 1478116426 25200
#      Wed Nov 02 12:53:46 2016 -0700
# Node ID fcb527c8ade6db113acbee677f321fa0249348a2
# Parent  0fba3ed4e7eba474e3f518763d9e02851c9ff024
Add support for Proxy Protocol to mail SMTP

When nginx is not the first proxy in a proxy chain it needs to be able to
understand the Proxy Protocol Header to retrieve client IP and pass it
downstream in an XCLIENT command. With these changes one can do
that by setting `proxy_protocol on;` in the mail module config.

diff -r 0fba3ed4e7eb -r fcb527c8ade6 src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h  Wed Nov 02 20:05:21 2016 +0300
+++ b/src/mail/ngx_mail.h  Wed Nov 02 12:53:46 2016 -0700
@@ -185,6 +185,7 @@
     void                  **ctx;
     void                  **main_conf;
     void                  **srv_conf;
+    ngx_mail_addr_conf_t   *addr_conf;

     ngx_resolver_ctx_t     *resolver_ctx;

diff -r 0fba3ed4e7eb -r fcb527c8ade6 src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c  Wed Nov 02 20:05:21 2016 +0300
+++ b/src/mail/ngx_mail_handler.c  Wed Nov 02 12:53:46 2016 -0700
@@ -9,9 +9,12 @@
 #include <ngx_core.h>
 #include <ngx_event.h>
 #include <ngx_mail.h>
+#include <ngx_mail_proxy_module.h>


 static void ngx_mail_init_session(ngx_connection_t *c);
+static void ngx_mail_proxy_protocol_handler(ngx_event_t *rev);
+static void ngx_mail_init_session_handler(ngx_event_t *rev);

 #if (NGX_MAIL_SSL)
 static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t
*c);
@@ -34,6 +37,8 @@
     ngx_mail_session_t        *s;
     ngx_mail_addr_conf_t      *addr_conf;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_proxy_conf_t     *pcf;
+    ngx_event_t               *rev;
     u_char                     text[NGX_SOCKADDR_STRLEN];
 #if (NGX_HAVE_INET6)
     struct sockaddr_in6       *sin6;
@@ -128,6 +133,7 @@

     s->main_conf = addr_conf->ctx->main_conf;
     s->srv_conf = addr_conf->ctx->srv_conf;
+    s->addr_conf = addr_conf;

     s->addr_text = &addr_conf->addr_text;

@@ -159,10 +165,31 @@

     c->log_error = NGX_ERROR_INFO;

+    rev = c->read;
+
+    pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+    if (pcf->proxy_protocol) {
+        rev->handler = ngx_mail_proxy_protocol_handler;
+        ngx_mail_proxy_protocol_handler(rev);
+        return;
+    }
+
+    ngx_mail_init_session_handler(rev);
+}
+
+
+static void
+ngx_mail_init_session_handler(ngx_event_t *rev)
+{
+    ngx_connection_t  *c;
+
+    c = rev->data;
+
 #if (NGX_MAIL_SSL)
-    {
     ngx_mail_ssl_conf_t  *sslcf;
+    ngx_mail_session_t   *s;

+    s = c->data;
     sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);

     if (sslcf->enable) {
@@ -172,7 +199,7 @@
         return;
     }

-    if (addr_conf->ssl) {
+    if (s->addr_conf->ssl) {

         c->log->action = "SSL handshaking";

@@ -187,8 +214,6 @@
         ngx_mail_ssl_init_connection(&sslcf->ssl, c);
         return;
     }
-
-    }
 #endif

     ngx_mail_init_session(c);
@@ -898,3 +923,73 @@

     return p;
 }
+
+
+static void
+ngx_mail_proxy_protocol_handler(ngx_event_t *rev)
+{
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+    u_char                    *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER + 1];
+    size_t                     size;
+    ssize_t                    n;
+    ngx_connection_t          *c;
+
+    c = rev->data;
+    s = c->data;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
+                   "read proxy protocol header");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed
out");
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (c->close) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http recv(): %z", n);
+
+    if (n == -1) {
+        if (ngx_socket_errno == NGX_EAGAIN) {
+            rev->ready = 0;
+
+            if (!rev->timer_set) {
+                ngx_add_timer(rev, cscf->timeout);
+                ngx_reusable_connection(c, 1);
+            }
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_mail_close_connection(c);
+            }
+            return;
+        }
+
+        ngx_connection_error(c, ngx_socket_errno, "recv() failed");
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    p = ngx_proxy_protocol_read(c, buf, buf + n);
+    if (p == NULL) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    // Purge the Proxy Protocol header from the network buffer.
+    size = p - buf;
+    if (c->recv(c, buf, size) != (ssize_t) size) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    ngx_mail_init_session_handler(rev);
+}
\ No newline at end of file
diff -r 0fba3ed4e7eb -r fcb527c8ade6 src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c Wed Nov 02 20:05:21 2016 +0300
+++ b/src/mail/ngx_mail_proxy_module.c Wed Nov 02 12:53:46 2016 -0700
@@ -10,15 +10,7 @@
 #include <ngx_event.h>
 #include <ngx_event_connect.h>
 #include <ngx_mail.h>
-
-
-typedef struct {
-    ngx_flag_t  enable;
-    ngx_flag_t  pass_error_message;
-    ngx_flag_t  xclient;
-    size_t      buffer_size;
-    ngx_msec_t  timeout;
-} ngx_mail_proxy_conf_t;
+#include <ngx_mail_proxy_module.h>


 static void ngx_mail_proxy_block_read(ngx_event_t *rev);
@@ -74,6 +66,13 @@
       offsetof(ngx_mail_proxy_conf_t, xclient),
       NULL },

+    { ngx_string("proxy_protocol"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_proxy_conf_t, proxy_protocol),
+      NULL },
+
       ngx_null_command
 };

@@ -456,6 +455,7 @@
     ngx_mail_session_t        *s;
     ngx_mail_proxy_conf_t     *pcf;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_str_t                  client_addr;

     ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
                    "mail proxy smtp auth handler");
@@ -525,9 +525,12 @@

         s->connection->log->action = "sending XCLIENT to upstream";

-        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="
-                          CRLF) - 1
-                   + s->connection->addr_text.len + s->login.len +
s->host.len;
+        client_addr = s->connection->addr_text;
+        if (s->connection->proxy_protocol_addr.data != NULL) {
+            client_addr = s->connection->proxy_protocol_addr;
+        }
+        line.len = sizeof("XCLIENT ADDR= LOGIN= NAME=" CRLF) - 1
+                   + client_addr.len + s->login.len + s->host.len;

 #if (NGX_HAVE_INET6)
         if (s->connection->sockaddr->sa_family == AF_INET6) {
@@ -549,9 +552,7 @@
         }
 #endif

-        p = ngx_copy(p, s->connection->addr_text.data,
-                     s->connection->addr_text.len);
-
+        p = ngx_copy(p, client_addr.data, client_addr.len);
         if (s->login.len) {
             p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1);
             p = ngx_copy(p, s->login.data, s->login.len);
@@ -1099,6 +1100,7 @@
     pcf->enable = NGX_CONF_UNSET;
     pcf->pass_error_message = NGX_CONF_UNSET;
     pcf->xclient = NGX_CONF_UNSET;
+    pcf->proxy_protocol = NGX_CONF_UNSET;
     pcf->buffer_size = NGX_CONF_UNSET_SIZE;
     pcf->timeout = NGX_CONF_UNSET_MSEC;

@@ -1115,6 +1117,7 @@
     ngx_conf_merge_value(conf->enable, prev->enable, 0);
     ngx_conf_merge_value(conf->pass_error_message,
prev->pass_error_message, 0);
     ngx_conf_merge_value(conf->xclient, prev->xclient, 1);
+    ngx_conf_merge_value(conf->proxy_protocol, prev->proxy_protocol, 0);
     ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
                               (size_t) ngx_pagesize);
     ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 24 * 60 *
60000);
diff -r 0fba3ed4e7eb -r fcb527c8ade6 src/mail/ngx_mail_proxy_module.h
--- /dev/null  Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mail/ngx_mail_proxy_module.h Wed Nov 02 12:53:46 2016 -0700
@@ -0,0 +1,29 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_PROXY_MODULE_H_INCLUDED_
+#define _NGX_MAIL_PROXY_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+
+typedef struct {
+    ngx_flag_t  enable;
+    ngx_flag_t  pass_error_message;
+    ngx_flag_t  xclient;
+    ngx_flag_t  proxy_protocol;
+    size_t      buffer_size;
+    ngx_msec_t  timeout;
+} ngx_mail_proxy_conf_t;
+
+
+extern ngx_module_t  ngx_mail_proxy_module;
+
+
+#endif /* _NGX_MAIL_PROXY_MODULE_H_INCLUDED_ */


Cheers
Maxim Vladimirskiy
GitHub Profile: https://github.com/horkhe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx-devel/attachments/20161103/24231baa/attachment.html>


More information about the nginx-devel mailing list