[PATCH] Add proxy_protocol option to mail listener

Kees Bos cornelis.bos at gmail.com
Tue Jul 18 10:06:09 UTC 2017


# HG changeset patch
# User Kees Bos <cornelis.bos at gmail.com>
# Date 1500371531 0
#      Tue Jul 18 09:52:11 2017 +0000
# Node ID 8dd6050ca6858d9bea139067611ca5c69cfe8f18
# Parent  e3723f2a11b7ec1c196d59c331739bc21d9d9afd
Add proxy_protocol option to mail listener

Add support for the mail handlers. This enables the use of an upstream
loadbalancer/proxy (like haproxy) that connects with the proxy protocol.

The original ip (as exposed with the proxy protocol) will be used as
parameter for the 'Client-IP' in the authentication call and as address
in the XCLIENT call.

Optionally (if set), the real ips from the client that are using the
proxy protocol can be restricted with "set_real_ip_from".

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;
    }
}

diff -r e3723f2a11b7 -r 8dd6050ca685 auto/modules
--- a/auto/modules      Mon Jul 17 17:23:51 2017 +0300
+++ b/auto/modules      Tue Jul 18 09:52:11 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 e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail.c
--- a/src/mail/ngx_mail.c       Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail.c       Tue Jul 18 09:52:11 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 e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h       Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail.h       Tue Jul 18 09:52:11 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;
diff -r e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c      Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail_auth_http_module.c      Tue Jul 18 09:52:11 2017 +0000
@@ -1142,6 +1142,7 @@
     ngx_mail_ssl_conf_t       *sslcf;
 #endif
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_str_t                 *client_addr;
 
     if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
         return NULL;
@@ -1208,6 +1209,11 @@
 #endif
 
     cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+    if (s->connection->proxy_protocol_addr.len) {
+        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
@@ -1221,7 +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("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
@@ -1287,8 +1293,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) {
diff -r e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail_core_module.c
--- a/src/mail/ngx_mail_core_module.c   Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail_core_module.c   Tue Jul 18 09:52:11 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 e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c       Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail_handler.c       Tue Jul 18 09:52:11 2017 +0000
@@ -12,13 +12,14 @@
 
 
 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);
 static void ngx_mail_ssl_handshake_handler(ngx_connection_t *c);
 static ngx_int_t ngx_mail_verify_cert(ngx_mail_session_t *s,
     ngx_connection_t *c);
 #endif
+ngx_int_t ngx_mail_realip_handler(ngx_mail_session_t *s);
 
 
 void
@@ -138,11 +139,7 @@
 
     ngx_set_connection_log(c, cscf->error_log);
 
-    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 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 +162,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 +175,99 @@
             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;
+    }
+
+    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 connected to %V",
+                  c->number, len, text, s->addr_text);
+
+#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];
+    size_t                     size, len;
+    ssize_t                    n;
+    ngx_err_t                  err;
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    u_char                     text[NGX_SOCKADDR_STRLEN];
+    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 (real %*s:%d) connected to %V",
+        c->number, len, text,
+        c->proxy_protocol_addr.len, c->proxy_protocol_addr.data,
+        c->proxy_protocol_port, s->addr_text);
+
+    if (ngx_mail_realip_handler(s) != NGX_OK) {
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+#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 +302,19 @@
     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 +322,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 e3723f2a11b7 -r 8dd6050ca685 src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c  Mon Jul 17 17:23:51 2017 +0300
+++ b/src/mail/ngx_mail_proxy_module.c  Tue Jul 18 09:52:11 2017 +0000
@@ -456,6 +456,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");
@@ -523,11 +524,17 @@
         ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
                        "mail proxy send xclient");
 
+        if (s->connection->proxy_protocol_addr.len) {
+            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 +556,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 e3723f2a11b7 -r 8dd6050ca685 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 Tue Jul 18 09:52:11 2017 +0000
@@ -0,0 +1,239 @@
+
+/*
+ * 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;
+
+
+ngx_int_t ngx_mail_realip_handler(ngx_mail_session_t *s);
+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_int_t ngx_mail_realip_init(ngx_conf_t *cf);
+
+
+static ngx_command_t  ngx_mail_realip_commands[] = {
+
+    { ngx_string("set_real_ip_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;
+    u_char                         text[NGX_SOCKADDR_STRLEN];
+
+    c = s->connection;
+
+    rscf = ngx_mail_get_module_srv_conf(s, ngx_mail_realip_module);
+
+    if (rscf->from == NULL) {
+        // No set_real_ip_from defined. Allow all.
+    } else if (ngx_cidr_match(c->sockaddr, rscf->from) != NGX_OK) {
+        size_t len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1);
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "*%uA Unauthorized client %*s", c->number, len, text);
+        return NGX_DECLINED;
+    }
+
+    if (c->proxy_protocol_addr.len == 0) {
+        size_t len = ngx_sock_ntop(c->sockaddr, c->socklen, text, NGX_SOCKADDR_STRLEN, 1);
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "*%uA No proxy_protocol %*s", c->number, len, text);
+        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