[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