[PATCH] Add proxy_protocol option to mail listener

Kees Bos cornelis.bos at gmail.com
Mon Aug 7 13:23:12 UTC 2017


# HG changeset patch
# User Kees Bos <cornelis.bos at gmail.com>
# Date 1500565189 0
#      Thu Jul 20 15:39:49 2017 +0000
# Node ID 327f18e079b175b14277a23e75715f5feee34d69
# Parent  863b862534d7ac0dbf8babf68b824de6fb0d6ef4
Add proxy_protocol option to mail listener

Add support for the mail handlers. This enables the use of an upstream
loadbalancer/proxy that connects with the proxy protocol. Examples of
this are haproxy or a nginx stream handler that uses then proxy protocol
in client conections.

The proxy protocol source ip address will we exposed to the auth
handler as 'Proxy-Protocol-IP'.

If the sender ip address matches one or more "set_real_ip_from" directives,
the source ip address as specified in the in the proxy protocol will be
used as 'Client-IP' in the authentication call and as address in the
XCLIENT call.

Example config:
mail {
    server_name mail.example.com;
    auth_http   localhost:9000/;

    server {
        listen 143 proxy_protocol;
        protocol imap;
    }

    server {
        listen 25 proxy_protocol;
        protocol smtp;
        set_real_ip_from 127.0.0.0/8;
        set_real_ip_from ::/128;
    }
}

In the imap config, the source address given in the proxy protocol will
never be used as Client-IP.

In the smtp config, the source address given in the proxy protocol will
only be used as XCLIENT address when the sender address matches the
"set_real_ip_from" settings (in this case only loopback address).

diff -r 863b862534d7 -r 327f18e079b1 auto/modules
--- a/auto/modules	Wed Jul 19 21:39:40 2017 +0800
+++ b/auto/modules	Thu Jul 20 15:39:49 2017 +0000
@@ -954,6 +954,12 @@
     ngx_module_srcs=src/mail/ngx_mail_proxy_module.c
 
     . auto/module
+
+    ngx_module_name=ngx_mail_realip_module
+    ngx_module_deps=
+    ngx_module_srcs=src/mail/ngx_mail_realip_module.c
+
+    . auto/module
 fi
 
 
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail.c
--- a/src/mail/ngx_mail.c	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail.c	Thu Jul 20 15:39:49 2017 +0000
@@ -408,6 +408,7 @@
 #if (NGX_MAIL_SSL)
         addrs[i].conf.ssl = addr[i].opt.ssl;
 #endif
+        addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 
         len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
                             buf, NGX_SOCKADDR_STRLEN, 1);
@@ -457,6 +458,7 @@
 #if (NGX_MAIL_SSL)
         addrs6[i].conf.ssl = addr[i].opt.ssl;
 #endif
+        addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol;
 
         len = ngx_sock_ntop(&addr[i].opt.sockaddr.sockaddr, addr[i].opt.socklen,
                             buf, NGX_SOCKADDR_STRLEN, 1);
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail.h	Thu Jul 20 15:39:49 2017 +0000
@@ -40,6 +40,7 @@
     unsigned                ipv6only:1;
 #endif
     unsigned                so_keepalive:2;
+    unsigned                proxy_protocol:1;
 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
     int                     tcp_keepidle;
     int                     tcp_keepintvl;
@@ -54,7 +55,8 @@
 typedef struct {
     ngx_mail_conf_ctx_t    *ctx;
     ngx_str_t               addr_text;
-    ngx_uint_t              ssl;    /* unsigned   ssl:1; */
+    unsigned                ssl:1;
+    unsigned                proxy_protocol:1;
 } ngx_mail_addr_conf_t;
 
 typedef struct {
@@ -204,6 +206,8 @@
     unsigned                esmtp:1;
     unsigned                auth_method:3;
     unsigned                auth_wait:1;
+    unsigned                ssl:1;
+    unsigned                proxy_protocol:1;
 
     ngx_str_t               login;
     ngx_str_t               passwd;
@@ -371,6 +375,8 @@
 #endif
 
 
+ngx_int_t ngx_mail_realip_handler(ngx_mail_session_t *s);
+
 void ngx_mail_init_connection(ngx_connection_t *c);
 
 ngx_int_t ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail_auth_http_module.c	Thu Jul 20 15:39:49 2017 +0000
@@ -1134,7 +1134,7 @@
 {
     size_t                     len;
     ngx_buf_t                 *b;
-    ngx_str_t                  login, passwd;
+    ngx_str_t                 *client_addr, login, passwd;
 #if (NGX_MAIL_SSL)
     ngx_str_t                  verify, subject, issuer, serial, fingerprint,
                                raw_cert, cert;
@@ -1209,6 +1209,12 @@
 
     cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
+    if (ngx_mail_realip_handler(s) == NGX_OK) {
+        client_addr = &s->connection->proxy_protocol_addr;
+    } else {
+        client_addr = &s->connection->addr_text;
+    }
+
     len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
           + sizeof("Host: ") - 1 + ahcf->host_header.len + sizeof(CRLF) - 1
           + sizeof("Auth-Method: ") - 1
@@ -1221,8 +1227,7 @@
                 + sizeof(CRLF) - 1
           + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
                 + sizeof(CRLF) - 1
-          + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len
-                + sizeof(CRLF) - 1
+          + sizeof("Client-IP: ") - 1 + client_addr->len + sizeof(CRLF) - 1
           + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1
           + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len + sizeof(CRLF) - 1
           + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len + sizeof(CRLF) - 1
@@ -1240,6 +1245,11 @@
           + ahcf->header.len
           + sizeof(CRLF) - 1;
 
+    if (s->connection->proxy_protocol_addr.len) {
+        len += sizeof("Proxy-Protocol-IP: ") - 1 +
+               s->connection->proxy_protocol_addr.len + sizeof(CRLF) - 1;
+    }
+
     b = ngx_create_temp_buf(pool, len);
     if (b == NULL) {
         return NULL;
@@ -1287,8 +1297,7 @@
                           s->login_attempt);
 
     b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1);
-    b->last = ngx_copy(b->last, s->connection->addr_text.data,
-                       s->connection->addr_text.len);
+    b->last = ngx_copy(b->last, client_addr->data, client_addr->len);
     *b->last++ = CR; *b->last++ = LF;
 
     if (s->host.len) {
@@ -1298,6 +1307,14 @@
         *b->last++ = CR; *b->last++ = LF;
     }
 
+    if (s->connection->proxy_protocol_addr.len) {
+        b->last = ngx_cpymem(b->last, "Proxy-Protocol-IP: ",
+                             sizeof("Proxy-Protocol-IP: ") - 1);
+        b->last = ngx_copy(b->last, s->connection->proxy_protocol_addr.data,
+                           s->connection->proxy_protocol_addr.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
     if (s->auth_method == NGX_MAIL_AUTH_NONE) {
 
         /* HELO, MAIL FROM, and RCPT TO can't contain CRLF, no need to escape */
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail_core_module.c
--- a/src/mail/ngx_mail_core_module.c	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail_core_module.c	Thu Jul 20 15:39:49 2017 +0000
@@ -574,6 +574,11 @@
 #endif
         }
 
+        if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) {
+            ls->proxy_protocol = 1;
+            continue;
+        }
+
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                            "the invalid \"%V\" parameter", &value[i]);
         return NGX_CONF_ERROR;
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail_handler.c	Thu Jul 20 15:39:49 2017 +0000
@@ -12,6 +12,7 @@
 
 
 static void ngx_mail_init_session(ngx_connection_t *c);
+static void ngx_mail_proxy_protocol_handler(ngx_event_t *rev);
 
 #if (NGX_MAIL_SSL)
 static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
@@ -143,6 +144,8 @@
     ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA client %*s connected to %V",
                   c->number, len, text, s->addr_text);
 
+    s->proxy_protocol = addr_conf->proxy_protocol;
+
     ctx = ngx_palloc(c->pool, sizeof(ngx_mail_log_ctx_t));
     if (ctx == NULL) {
         ngx_mail_close_connection(c);
@@ -165,16 +168,11 @@
 
     sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
 
+    s->ssl = 0;
     if (sslcf->enable) {
-        c->log->action = "SSL handshaking";
-
-        ngx_mail_ssl_init_connection(&sslcf->ssl, c);
-        return;
-    }
-
-    if (addr_conf->ssl) {
-
-        c->log->action = "SSL handshaking";
+        s->ssl = 1;
+    } else if (addr_conf->ssl) {
+        s->ssl = 1;
 
         if (sslcf->ssl.ctx == NULL) {
             ngx_log_error(NGX_LOG_ERR, c->log, 0,
@@ -183,11 +181,97 @@
             ngx_mail_close_connection(c);
             return;
         }
+    }
 
+    }
+#endif
+
+    if (s->proxy_protocol) {
+        c->log->action = "reading PROXY protocol";
+
+        ngx_add_timer(c->read, cscf->timeout);
+        c->read->handler = ngx_mail_proxy_protocol_handler;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+        }
+        return;
+    }
+
+
+#if (NGX_MAIL_SSL)
+    if (s->ssl) {
+        ngx_mail_ssl_conf_t  *sslcf;
+        c->log->action = "SSL handshaking";
+
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
         ngx_mail_ssl_init_connection(&sslcf->ssl, c);
         return;
     }
+# endif
 
+    ngx_mail_init_session(c);
+}
+
+
+void
+ngx_mail_proxy_protocol_handler(ngx_event_t *rev)
+{
+    u_char                    *p, buf[NGX_PROXY_PROTOCOL_MAX_HEADER + 1],
+                               text[NGX_SOCKADDR_STRLEN];
+    size_t                     size, len;
+    ssize_t                    n;
+    ngx_err_t                  err;
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_log_ctx_t        *ctx;
+
+    size = sizeof(buf);
+    c = rev->data;
+
+    n = recv(c->fd, (char *) buf, size, MSG_PEEK);
+    err = ngx_socket_errno;
+
+    if (n == -1) {
+        ngx_connection_error(c, err, "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;
+    }
+
+    size = p - buf;
+    if (c->recv(c, buf, size) != (ssize_t) size) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s = c->data;
+    s->proxy_protocol = 0;
+
+    ctx = c->log->data;
+    ctx->client = &c->proxy_protocol_addr;
+
+    len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1);
+    ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                  "*%uA client %*s proxy-protocol %*s:%d",
+                  c->number, len, text,
+                  c->proxy_protocol_addr.len, c->proxy_protocol_addr.data,
+                  c->proxy_protocol_port);
+
+#if (NGX_MAIL_SSL)
+    {
+    ngx_mail_ssl_conf_t  *sslcf;
+    if (s->ssl) {
+        c->log->action = "SSL handshaking";
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+        return;
+    }
     }
 #endif
 
@@ -222,6 +306,21 @@
     ngx_mail_session_t        *s;
     ngx_mail_core_srv_conf_t  *cscf;
 
+    s = c->data;
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    if (s->proxy_protocol) {
+        c->log->action = "reading PROXY protocol";
+
+        ngx_add_timer(c->read, cscf->timeout);
+        c->read->handler = ngx_mail_proxy_protocol_handler;
+
+        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+            ngx_mail_close_connection(c);
+        }
+        return;
+    }
+
     if (ngx_ssl_create_connection(ssl, c, 0) != NGX_OK) {
         ngx_mail_close_connection(c);
         return;
@@ -229,10 +328,6 @@
 
     if (ngx_ssl_handshake(c) == NGX_AGAIN) {
 
-        s = c->data;
-
-        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
         ngx_add_timer(c->read, cscf->timeout);
 
         c->ssl->handler = ngx_mail_ssl_handshake_handler;
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c	Wed Jul 19 21:39:40 2017 +0800
+++ b/src/mail/ngx_mail_proxy_module.c	Thu Jul 20 15:39:49 2017 +0000
@@ -450,7 +450,7 @@
 {
     u_char                    *p;
     ngx_int_t                  rc;
-    ngx_str_t                  line;
+    ngx_str_t                 *client_addr, line;
     ngx_buf_t                 *b;
     ngx_connection_t          *c;
     ngx_mail_session_t        *s;
@@ -523,11 +523,17 @@
         ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
                        "mail proxy send xclient");
 
+        if (ngx_mail_realip_handler(s) == NGX_OK) {
+            client_addr = &s->connection->proxy_protocol_addr;
+        } else {
+            client_addr = &s->connection->addr_text;
+        }
+
         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->len + s->login.len + s->host.len;
 
 #if (NGX_HAVE_INET6)
         if (s->connection->sockaddr->sa_family == AF_INET6) {
@@ -549,8 +555,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);
diff -r 863b862534d7 -r 327f18e079b1 src/mail/ngx_mail_realip_module.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mail/ngx_mail_realip_module.c	Thu Jul 20 15:39:49 2017 +0000
@@ -0,0 +1,232 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    ngx_array_t       *from;     /* array of ngx_cidr_t */
+} ngx_mail_realip_srv_conf_t;
+
+
+typedef struct {
+    struct sockaddr   *sockaddr;
+    socklen_t          socklen;
+    ngx_str_t          addr_text;
+} ngx_mail_realip_ctx_t;
+
+
+static char *ngx_mail_realip_from(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
+static void *ngx_mail_realip_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_realip_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_command_t  ngx_mail_realip_commands[] = {
+
+    { ngx_string("set_realip_from"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_mail_realip_from,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_realip_module_ctx = {
+    NULL,                                  /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_realip_create_srv_conf,       /* create server configuration */
+    ngx_mail_realip_merge_srv_conf         /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_realip_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_realip_module_ctx,           /* module context */
+    ngx_mail_realip_commands,              /* module directives */
+    NGX_MAIL_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+ngx_int_t
+ngx_mail_realip_handler(ngx_mail_session_t *s)
+{
+    ngx_connection_t              *c;
+    ngx_mail_realip_srv_conf_t    *rscf;
+
+    rscf = ngx_mail_get_module_srv_conf(s, ngx_mail_realip_module);
+
+    if (rscf->from == NULL) {
+        return NGX_DECLINED;
+    }
+
+    c = s->connection;
+
+    if (c->proxy_protocol_addr.len == 0) {
+        return NGX_DECLINED;
+    }
+
+    if (ngx_cidr_match(c->sockaddr, rscf->from) != NGX_OK) {
+        return NGX_DECLINED;
+    }
+
+    return NGX_OK;
+}
+
+
+static char *
+ngx_mail_realip_from(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_realip_srv_conf_t *rscf = conf;
+
+    ngx_int_t             rc;
+    ngx_str_t            *value;
+    ngx_url_t             u;
+    ngx_cidr_t            c, *cidr;
+    ngx_uint_t            i;
+    struct sockaddr_in   *sin;
+#if (NGX_HAVE_INET6)
+    struct sockaddr_in6  *sin6;
+#endif
+
+    value = cf->args->elts;
+
+    if (rscf->from == NULL) {
+        rscf->from = ngx_array_create(cf->pool, 2,
+                                      sizeof(ngx_cidr_t));
+        if (rscf->from == NULL) {
+            return NGX_CONF_ERROR;
+        }
+    }
+
+#if (NGX_HAVE_UNIX_DOMAIN)
+
+    if (ngx_strcmp(value[1].data, "unix:") == 0) {
+        cidr = ngx_array_push(rscf->from);
+        if (cidr == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        cidr->family = AF_UNIX;
+        return NGX_CONF_OK;
+    }
+
+#endif
+
+    rc = ngx_ptocidr(&value[1], &c);
+
+    if (rc != NGX_ERROR) {
+        if (rc == NGX_DONE) {
+            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+                               "low address bits of %V are meaningless",
+                               &value[1]);
+        }
+
+        cidr = ngx_array_push(rscf->from);
+        if (cidr == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *cidr = c;
+
+        return NGX_CONF_OK;
+    }
+
+    ngx_memzero(&u, sizeof(ngx_url_t));
+    u.host = value[1];
+
+    if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "%s in set_real_ip_from \"%V\"",
+                               u.err, &u.host);
+        }
+
+        return NGX_CONF_ERROR;
+    }
+
+    cidr = ngx_array_push_n(rscf->from, u.naddrs);
+    if (cidr == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    ngx_memzero(cidr, u.naddrs * sizeof(ngx_cidr_t));
+
+    for (i = 0; i < u.naddrs; i++) {
+        cidr[i].family = u.addrs[i].sockaddr->sa_family;
+
+        switch (cidr[i].family) {
+
+#if (NGX_HAVE_INET6)
+        case AF_INET6:
+            sin6 = (struct sockaddr_in6 *) u.addrs[i].sockaddr;
+            cidr[i].u.in6.addr = sin6->sin6_addr;
+            ngx_memset(cidr[i].u.in6.mask.s6_addr, 0xff, 16);
+            break;
+#endif
+
+        default: /* AF_INET */
+            sin = (struct sockaddr_in *) u.addrs[i].sockaddr;
+            cidr[i].u.in.addr = sin->sin_addr.s_addr;
+            cidr[i].u.in.mask = 0xffffffff;
+            break;
+        }
+    }
+
+    return NGX_CONF_OK;
+}
+
+
+static void *
+ngx_mail_realip_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_realip_srv_conf_t  *conf;
+
+    conf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_realip_srv_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->from = NULL;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_mail_realip_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_realip_srv_conf_t *prev = parent;
+    ngx_mail_realip_srv_conf_t *conf = child;
+
+    if (conf->from == NULL) {
+        conf->from = prev->from;
+    }
+
+    return NGX_CONF_OK;
+}


More information about the nginx-devel mailing list