# HG changeset patch # User Maxim Dounin # Date 1185148512 0 # Node ID f1e2fab7a46c0bb4d94eb6d477c0ca132ce7368e # Parent 137505db424671199d08953d778601a337e1ba3e Mail: smtp proxy without authentication. Activated by auth method "unauth" in smtp_auth directive. Waits for MAIL FROM and first RCPT TO from client, asks auth_http for backend with additional headers Auth-SMTP-Helo, Auth-SMTP-From, Auth-SMTP-To, and establishes connection to backend. Auth-SMTP-From/To currently contain full command (e.g. "mail from: <>"), this may change in future. The functionality was designed to take off load from real smtp servers. Additionally it may be used to implement pop-before-smtp authentication (but dont do it unless you really need it - use real auth instead). Current bug-features: - If only "unauth" method activated in config, other methods (e.g. plain, login) not advertised but accepted. Make sure your auth server handles this gracefully. - If backend server returns error on MAIL FROM / RCPT TO command while proxy tunnel setup, nginx will close connection to client with 4xx error. One may use proxy_pass_error_message directive to pass original error message to client. - Syntax of MAIL FROM / RCPT TO commands from client isn't checked. diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h --- a/src/mail/ngx_mail.h +++ b/src/mail/ngx_mail.h @@ -181,8 +181,12 @@ typedef enum { ngx_smtp_auth_plain, ngx_smtp_auth_cram_md5, ngx_smtp_helo, - ngx_smtp_noxclient, - ngx_smtp_xclient + ngx_smtp_helo_xclient, + ngx_smtp_helo_from, + ngx_smtp_xclient, + ngx_smtp_xclient_from, + ngx_smtp_from, + ngx_smtp_to } ngx_smtp_state_e; @@ -216,7 +220,7 @@ typedef struct { unsigned no_sync_literal:1; unsigned starttls:1; unsigned esmtp:1; - unsigned auth_method:2; + unsigned auth_method:3; unsigned auth_wait:1; ngx_str_t login; @@ -228,6 +232,8 @@ typedef struct { ngx_str_t *addr_text; ngx_str_t smtp_helo; + ngx_str_t smtp_from; + ngx_str_t smtp_to; ngx_uint_t command; ngx_array_t args; @@ -297,12 +303,14 @@ typedef struct { #define NGX_MAIL_AUTH_LOGIN 1 #define NGX_MAIL_AUTH_APOP 2 #define NGX_MAIL_AUTH_CRAM_MD5 3 +#define NGX_MAIL_AUTH_UNAUTH 4 #define NGX_MAIL_AUTH_PLAIN_ENABLED 0x0002 #define NGX_MAIL_AUTH_LOGIN_ENABLED 0x0004 #define NGX_MAIL_AUTH_APOP_ENABLED 0x0008 #define NGX_MAIL_AUTH_CRAM_MD5_ENABLED 0x0010 +#define NGX_MAIL_AUTH_UNAUTH_ENABLED 0x0020 #define NGX_MAIL_PARSE_INVALID_COMMAND 20 diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c --- a/src/mail/ngx_mail_auth_http_module.c +++ b/src/mail/ngx_mail_auth_http_module.c @@ -137,7 +137,8 @@ static ngx_str_t ngx_mail_auth_http_me ngx_string("plain"), ngx_string("plain"), ngx_string("apop"), - ngx_string("cram-md5") + ngx_string("cram-md5"), + ngx_string("unauth") }; static ngx_str_t ngx_mail_smtp_errcode = ngx_string("535 5.7.0"); @@ -1173,6 +1174,9 @@ ngx_mail_auth_http_create_request(ngx_ma + sizeof(CRLF) - 1 + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len + sizeof(CRLF) - 1 + + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len + + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len + + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len + ahcf->header.len + sizeof(CRLF) - 1; @@ -1227,6 +1231,27 @@ ngx_mail_auth_http_create_request(ngx_ma s->connection->addr_text.len); *b->last++ = CR; *b->last++ = LF; + if (s->auth_method == NGX_MAIL_AUTH_UNAUTH) { + + /* HELO / MAIL FROM / RCPT TO can't contain CRLF, no need to escape */ + + b->last = ngx_cpymem(b->last, "Auth-SMTP-Helo: ", + sizeof("Auth-SMTP-Helo: ") - 1); + b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len); + *b->last++ = CR; *b->last++ = LF; + + b->last = ngx_cpymem(b->last, "Auth-SMTP-From: ", + sizeof("Auth-SMTP-From: ") - 1); + b->last = ngx_copy(b->last, s->smtp_from.data, s->smtp_from.len); + *b->last++ = CR; *b->last++ = LF; + + b->last = ngx_cpymem(b->last, "Auth-SMTP-To: ", + sizeof("Auth-SMTP-To: ") - 1); + b->last = ngx_copy(b->last, s->smtp_to.data, s->smtp_to.len); + *b->last++ = CR; *b->last++ = LF; + + } + if (ahcf->header.len) { b->last = ngx_copy(b->last, ahcf->header.data, ahcf->header.len); } diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c --- a/src/mail/ngx_mail_core_module.c +++ b/src/mail/ngx_mail_core_module.c @@ -66,6 +66,7 @@ static ngx_conf_bitmask_t ngx_smtp_auth { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED }, { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED }, { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED }, + { ngx_string("unauth"), NGX_MAIL_AUTH_UNAUTH_ENABLED }, { ngx_null_string, 0 } }; @@ -74,7 +75,8 @@ static ngx_str_t ngx_imap_auth_methods_ ngx_string("AUTH=PLAIN"), ngx_string("AUTH=LOGIN"), ngx_null_string, /* APOP */ - ngx_string("AUTH=CRAM-MD5") + ngx_string("AUTH=CRAM-MD5"), + ngx_null_string /* UNAUTH */ }; @@ -82,7 +84,8 @@ static ngx_str_t ngx_smtp_auth_methods_ ngx_string("PLAIN"), ngx_string("LOGIN"), ngx_null_string, /* APOP */ - ngx_string("CRAM-MD5") + ngx_string("CRAM-MD5"), + ngx_null_string /* UNAUTH */ }; @@ -301,10 +304,10 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t ngx_mail_core_srv_conf_t *prev = parent; ngx_mail_core_srv_conf_t *conf = child; - u_char *p, *auth_p; + u_char *p, *auth_p, *last_p; size_t size, stls_only_size; ngx_str_t *c, *d; - ngx_uint_t i, m; + ngx_uint_t i, m, smtp_auth_enabled; ngx_conf_merge_size_value(conf->imap_client_buffer_size, prev->imap_client_buffer_size, @@ -599,23 +602,28 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t conf->smtp_capabilities = prev->smtp_capabilities; } - size = sizeof("250-") - 1 + conf->server_name.len + sizeof(CRLF) - 1 - + sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1; + size = sizeof("250-") - 1 + conf->server_name.len + sizeof(CRLF) - 1; c = conf->smtp_capabilities.elts; for (i = 0; i < conf->smtp_capabilities.nelts; i++) { size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1; } + smtp_auth_enabled = 0; for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0; m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED; m <<= 1, i++) { if (m & conf->smtp_auth_methods) { size += 1 + ngx_smtp_auth_methods_names[i].len; + smtp_auth_enabled = 1; } } + if (smtp_auth_enabled) { + size += sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1; + } + p = ngx_palloc(cf->pool, size); if (p == NULL) { return NGX_CONF_ERROR; @@ -624,11 +632,13 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t conf->smtp_capability.len = size; conf->smtp_capability.data = p; + last_p = p; *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-'; p = ngx_cpymem(p, conf->server_name.data, conf->server_name.len); *p++ = CR; *p++ = LF; for (i = 0; i < conf->smtp_capabilities.nelts; i++) { + last_p = p; *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-'; p = ngx_cpymem(p, c[i].data, c[i].len); *p++ = CR; *p++ = LF; @@ -636,21 +646,28 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t auth_p = p; - *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' '; - *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H'; + if (smtp_auth_enabled) { + last_p = p; + + *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' '; + *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H'; - for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0; - m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED; - m <<= 1, i++) - { - if (m & conf->smtp_auth_methods) { - *p++ = ' '; - p = ngx_cpymem(p, ngx_smtp_auth_methods_names[i].data, - ngx_smtp_auth_methods_names[i].len); + for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0; + m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED; + m <<= 1, i++) + { + if (m & conf->smtp_auth_methods) { + *p++ = ' '; + p = ngx_cpymem(p, ngx_smtp_auth_methods_names[i].data, + ngx_smtp_auth_methods_names[i].len); + } } - } + + *p++ = CR; *p = LF; - *p++ = CR; *p = LF; + } else { + last_p[3] = ' '; + } size += sizeof("250 STARTTLS" CRLF) - 1; @@ -669,7 +686,7 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t *p++ = CR; *p = LF; p = conf->smtp_starttls_capability.data - + (auth_p - conf->smtp_capability.data) + 3; + + (last_p - conf->smtp_capability.data) + 3; *p = '-'; size = (auth_p - conf->smtp_capability.data) @@ -688,6 +705,12 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t p = ngx_cpymem(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1); + if (last_p < auth_p) { + p = conf->smtp_starttls_only_capability.data + + (last_p - conf->smtp_capability.data) + 3; + *p = '-'; + } + return NGX_CONF_OK; } diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c --- a/src/mail/ngx_mail_handler.c +++ b/src/mail/ngx_mail_handler.c @@ -55,6 +55,7 @@ static u_char smtp_password[] = "334 UG static u_char smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF; static u_char smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF; static u_char smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF; +static u_char smtp_bad_sequence[] = "503 5.5.1 Bad sequence of commands" CRLF; void @@ -1548,6 +1549,11 @@ ngx_smtp_auth_state(ngx_event_t *rev) ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len); + s->smtp_from.len = 0; + s->smtp_from.data = NULL; + s->smtp_to.len = 0; + s->smtp_to.data = NULL; + if (s->command == NGX_SMTP_HELO) { size = cscf->smtp_server_name.len; text = cscf->smtp_server_name.data; @@ -1580,6 +1586,17 @@ ngx_smtp_auth_state(ngx_event_t *rev) break; + case NGX_SMTP_RSET: + + s->smtp_from.len = 0; + s->smtp_from.data = NULL; + s->smtp_to.len = 0; + s->smtp_to.data = NULL; + + text = smtp_ok; + size = sizeof(smtp_ok) - 1; + break; + case NGX_SMTP_AUTH: #if (NGX_MAIL_SSL) @@ -1706,8 +1723,12 @@ ngx_smtp_auth_state(ngx_event_t *rev) break; case NGX_SMTP_MAIL: - - if (s->connection->log->log_level >= NGX_LOG_INFO) { + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + + if (s->connection->log->log_level >= NGX_LOG_INFO + || (cscf->smtp_auth_methods + & NGX_MAIL_AUTH_UNAUTH_ENABLED)) + { l.len = s->buffer->last - s->buffer->start; l.data = s->buffer->start; @@ -1731,16 +1752,91 @@ ngx_smtp_auth_state(ngx_event_t *rev) l.len = i; - ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, - "client was rejected: \"%V\"", &l); + if (!(cscf->smtp_auth_methods + & NGX_MAIL_AUTH_UNAUTH_ENABLED)) + { + ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, + "client was rejected: \"%V\"", &l); + } + + } + + if (!(cscf->smtp_auth_methods & NGX_MAIL_AUTH_UNAUTH_ENABLED)) + { + text = smtp_auth_required; + size = sizeof(smtp_auth_required) - 1; + break; + } + + /* allow unauth */ + + if (s->smtp_from.len) { + text = smtp_bad_sequence; + size = sizeof(smtp_bad_sequence) - 1; + break; + } + + s->smtp_from.len = l.len; + + s->smtp_from.data = ngx_palloc(c->pool, l.len); + if (s->smtp_from.data == NULL) { + ngx_mail_session_internal_server_error(s); + return; } - text = smtp_auth_required; - size = sizeof(smtp_auth_required) - 1; + ngx_memcpy(s->smtp_from.data, l.data, l.len); + + text = smtp_ok; + size = sizeof(smtp_ok) - 1; break; + case NGX_SMTP_RCPT: + + if (s->smtp_from.len == 0) { + text = smtp_bad_sequence; + size = sizeof(smtp_bad_sequence) - 1; + break; + } + + l.len = s->buffer->last - s->buffer->start; + l.data = s->buffer->start; + + for (i = 0; i < l.len; i++) { + ch = l.data[i]; + + if (ch != CR && ch != LF) { + continue; + } + + l.data[i] = ' '; + } + + while (i) { + if (l.data[i - 1] != ' ') { + break; + } + + i--; + } + + l.len = i; + + s->smtp_to.len = l.len; + + s->smtp_to.data = ngx_palloc(c->pool, l.len); + if (s->smtp_to.data == NULL) { + ngx_mail_session_internal_server_error(s); + return; + } + + ngx_memcpy(s->smtp_to.data, l.data, l.len); + + s->auth_method = NGX_MAIL_AUTH_UNAUTH; + + ngx_mail_do_auth(s); + return; + case NGX_SMTP_NOOP: - case NGX_SMTP_RSET: text = smtp_ok; size = sizeof(smtp_ok) - 1; break; @@ -1761,6 +1857,10 @@ ngx_smtp_auth_state(ngx_event_t *rev) s->smtp_helo.len = 0; s->smtp_helo.data = NULL; + s->smtp_from.len = 0; + s->smtp_from.data = NULL; + s->smtp_to.len = 0; + s->smtp_to.data = NULL; text = smtp_ok; size = sizeof(smtp_ok) - 1; diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -102,7 +102,8 @@ ngx_module_t ngx_mail_proxy_module = { }; -static u_char smtp_ok[] = "235 2.0.0 OK" CRLF; +static u_char smtp_auth_ok[] = "235 2.0.0 OK" CRLF; +static u_char smtp_ok[] = "250 2.0.0 OK" CRLF; void @@ -516,11 +517,13 @@ ngx_mail_proxy_smtp_handler(ngx_event_t p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len); *p++ = CR; *p = LF; - s->mail_state = pcf->xclient ? ngx_smtp_helo: ngx_smtp_noxclient; + s->mail_state = pcf->xclient ? ngx_smtp_helo_xclient : + s->auth_method == NGX_MAIL_AUTH_UNAUTH ? + ngx_smtp_helo_from : ngx_smtp_helo; break; - case ngx_smtp_helo: + case ngx_smtp_helo_xclient: ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send xclient"); @@ -537,31 +540,77 @@ ngx_mail_proxy_smtp_handler(ngx_event_t return; } - if (s->smtp_helo.len) { - line.len = ngx_sprintf(line.data, - "XCLIENT PROTO=%sSMTP HELO=%V ADDR=%V LOGIN=%V " - "NAME=[UNAVAILABLE]" CRLF, - (s->esmtp ? "E" : ""), &s->smtp_helo, - &s->connection->addr_text, &s->login) - - line.data; - } else { - line.len = ngx_sprintf(line.data, - "XCLIENT PROTO=SMTP ADDR=%V LOGIN=%V " - "NAME=[UNAVAILABLE]" CRLF, - &s->connection->addr_text, &s->login) - - line.data; + line.len = ngx_sprintf(line.data, + "XCLIENT PROTO=%sSMTP%s%V ADDR=%V%s%V " + "NAME=[UNAVAILABLE]" CRLF, + (s->esmtp ? "E" : ""), + (s->smtp_helo.len ? " HELO=" : ""), &s->smtp_helo, + &s->connection->addr_text, + (s->login.len ? " LOGIN=" : ""), &s->login) + - line.data; + + s->mail_state = s->auth_method == NGX_MAIL_AUTH_UNAUTH ? + ngx_smtp_xclient_from : ngx_smtp_xclient; + + break; + + case ngx_smtp_helo_from: + case ngx_smtp_xclient_from: + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, + "mail proxy send mail from"); + + s->connection->log->action = "sending MAIL FROM to upstream"; + + line.len = s->smtp_from.len + sizeof(CRLF) - 1; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_mail_proxy_internal_server_error(s); + return; } - s->mail_state = ngx_smtp_xclient; + p = ngx_cpymem(line.data, s->smtp_from.data, s->smtp_from.len); + *p++ = CR; *p = LF; + + s->mail_state = ngx_smtp_from; + break; - case ngx_smtp_noxclient: + case ngx_smtp_from: + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, + "mail proxy send rcpt to"); + + s->connection->log->action = "sending RCPT TO to upstream"; + + line.len = s->smtp_to.len + sizeof(CRLF) - 1; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_mail_proxy_internal_server_error(s); + return; + } + + p = ngx_cpymem(line.data, s->smtp_to.data, s->smtp_to.len); + *p++ = CR; *p = LF; + + s->mail_state = ngx_smtp_to; + + break; + + case ngx_smtp_helo: case ngx_smtp_xclient: + case ngx_smtp_to: - ngx_memcpy(s->proxy->buffer->start, smtp_ok, sizeof(smtp_ok) - 1); + if (s->auth_method == NGX_MAIL_AUTH_UNAUTH) { + ngx_memcpy(s->proxy->buffer->start, smtp_ok, sizeof(smtp_ok) - 1); + s->proxy->buffer->last = s->proxy->buffer->start + + sizeof(smtp_ok) - 1; + } else { + ngx_memcpy(s->proxy->buffer->start, smtp_auth_ok, + sizeof(smtp_auth_ok) - 1); + s->proxy->buffer->last = s->proxy->buffer->start + + sizeof(smtp_auth_ok) - 1; + } s->proxy->buffer->pos = s->proxy->buffer->start; - s->proxy->buffer->last = s->proxy->buffer->start + sizeof(smtp_ok) - 1; s->connection->read->handler = ngx_mail_proxy_handler; s->connection->write->handler = ngx_mail_proxy_handler; @@ -701,15 +750,24 @@ ngx_mail_proxy_read_response(ngx_mail_se switch (state) { case ngx_smtp_helo: - case ngx_smtp_noxclient: + case ngx_smtp_helo_from: + case ngx_smtp_helo_xclient: + case ngx_smtp_from: + case ngx_smtp_to: if (p[0] == '2' && p[1] == '5' && p[2] == '0') { return NGX_OK; } break; case ngx_smtp_start: + if (p[0] == '2' && p[1] == '2' && p[2] == '0') { + return NGX_OK; + } + break; + case ngx_smtp_xclient: - if (p[0] == '2' && p[1] == '2' && p[2] == '0') { + case ngx_smtp_xclient_from: + if (p[0] == '2' && (p[1] == '2' || p[1] == '5') && p[2] == '0') { return NGX_OK; } break;