Opened 7 months ago

Closed 6 months ago

#2159 closed defect (fixed)

Massmail problems with mail module

Reported by: mmgit@… Owned by:
Priority: minor Milestone:
Component: nginx-module Version: 1.18.x
Keywords: smtp, mail Cc:
uname -a: Linux mailtest01 4.19.0-16-amd64 #1 SMP Debian 4.19.181-1 (2021-03-19) x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.18.0
built by gcc 8.3.0 (Debian 8.3.0-6)
built with OpenSSL 1.1.1d 10 Sep 2019
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fdebug-prefix-map=/data/builder/debuild/nginx-1.18.0/debian/debuild-base/nginx-1.18.0=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie'

Description

We encountered a problem when sending mass-mails thru NGINX. When sending a mail to more than (in our case) ~130 recipients, NGINX closes the connection during the SMTP connection without an error.

What we found is, that this error only occurs when TLS and pipelining is involved. If either TLS or pipelining is deactivated, sending the mail works.
Another workaround is to increase the smtp_client_buffer. On our test system the mempage size is 4k.

What we saw in a capture is that when we send a mail with 130 recipients and pipelining is activated, NGINX receives the recipient commands and accepts them with 250 2.0.0 OK but then closes the TCP connection with a FIN.
I think it would be better that the sender gets an error if there is a to small buffer size.

Change History (5)

comment:1 by Maxim Dounin, 7 months ago

Status: newaccepted

Thanks for the report, I think I see what is going on here.

When an SMTP command from the client cannot be read into the smtp_client_buffer buffer, nginx responds with "500 5.5.1 Invalid command" error and logs the "client sent too long command" error to the error log. But if the buffer is filled with pipelined SMTP commands and there is no free space in it, nginx might call recv() with zero length while trying to parse an additional command from the buffer. If this happens, recv() returns 0 and nginx thinks that the client closed the connection, so it closes the connection as well.

Please try the following patch, it should resolve this regardless of the smtp_client_buffer size (since nginx is only interested in one RCPT TO command before it passes the connection to the backend server, default buffer size of 4k is expected to be enough).

# HG changeset patch
# User Maxim Dounin <mdounin@mdounin.ru>
# Date 1617811333 -10800
#      Wed Apr 07 19:02:13 2021 +0300
# Node ID 3d0102933b51fa7b9ca930442777a0e613a2ca8c
# Parent  ad5aa609ca30bb89f8b53f5a1d08bde5b964a3ac
Mail: fixed reading with fully filled buffer (ticket #2159).

With SMTP pipelining, ngx_mail_read_command() can be called with s->buffer
without any space available, to parse additional commands received to the
buffer on previous calls.  Previously, this resulted in recv() being
called with zero len, resulting in zero being returned, which was interpreted
as a connection close by the client, so nginx silently closed connection.

Fix is to avoid calling c->recv() if there is no free space in the buffer,
but continue parsing of the already received commands.

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
@@ -833,20 +833,23 @@ ngx_mail_read_command(ngx_mail_session_t
     ngx_str_t                  l;
     ngx_mail_core_srv_conf_t  *cscf;
 
-    n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+    if (s->buffer->last < s->buffer->end) {
+
+        n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
 
-    if (n == NGX_ERROR || n == 0) {
-        ngx_mail_close_connection(c);
-        return NGX_ERROR;
-    }
+        if (n == NGX_ERROR || n == 0) {
+            ngx_mail_close_connection(c);
+            return NGX_ERROR;
+        }
 
-    if (n > 0) {
-        s->buffer->last += n;
-    }
+        if (n > 0) {
+            s->buffer->last += n;
+        }
 
-    if (n == NGX_AGAIN) {
-        if (s->buffer->pos == s->buffer->last) {
-            return NGX_AGAIN;
+        if (n == NGX_AGAIN) {
+            if (s->buffer->pos == s->buffer->last) {
+                return NGX_AGAIN;
+            }
         }
     }
 

Note that the patch is actually quite simple, though changes indentation of some code. Whitespace-agnostic version of the same patch is as follows:

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
@@ -833,6 +833,8 @@ ngx_mail_read_command(ngx_mail_session_t
     ngx_str_t                  l;
     ngx_mail_core_srv_conf_t  *cscf;
 
+    if (s->buffer->last < s->buffer->end) {
+
     n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
 
     if (n == NGX_ERROR || n == 0) {
@@ -849,6 +851,7 @@ ngx_mail_read_command(ngx_mail_session_t
             return NGX_AGAIN;
         }
     }
+    }
 
     cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 

comment:2 by mmgit@…, 7 months ago

We will try the patch next week.
Thank you very much for the fast response.

comment:3 by mmgit@…, 7 months ago

Tested the fix with 1.19.9 and it works.

comment:4 by Maxim Dounin <mdounin@…>, 6 months ago

In 7829:2851e4c7de03/nginx:

Mail: fixed reading with fully filled buffer (ticket #2159).

With SMTP pipelining, ngx_mail_read_command() can be called with s->buffer
without any space available, to parse additional commands received to the
buffer on previous calls. Previously, this resulted in recv() being called
with zero length, resulting in zero being returned, which was interpreted
as a connection close by the client, so nginx silently closed connection.

Fix is to avoid calling c->recv() if there is no free space in the buffer,
but continue parsing of the already received commands.

comment:5 by Maxim Dounin, 6 months ago

Resolution: fixed
Status: acceptedclosed

Thanks for testing, fix committed.

Note: See TracTickets for help on using tickets.