[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