changeset 7843:b38728495e1a

Mail: IMAP pipelining support. The change is mostly the same as the SMTP one (04e43d03e153 and 3f5d0af4e40a), and ensures that nginx is able to properly handle or reject multiple IMAP commands. The s->cmd field is not really used and set for consistency. Non-synchronizing literals handling in invalid/unknown commands is limited, so when a non-synchronizing literal is detected at the end of a discarded line, the connection is closed.
author Maxim Dounin <mdounin@mdounin.ru>
date Wed, 19 May 2021 03:13:28 +0300
parents 4b15f1b92100
children ec1071830799
files src/mail/ngx_mail.h src/mail/ngx_mail_imap_handler.c src/mail/ngx_mail_parse.c src/mail/ngx_mail_proxy_module.c
diffstat 4 files changed, 65 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -236,6 +236,7 @@ typedef struct {
     /* used to parse POP3/IMAP/SMTP command */
 
     ngx_uint_t              state;
+    u_char                 *tag_start;
     u_char                 *cmd_start;
     u_char                 *arg_start;
     ngx_uint_t              literal_len;
--- a/src/mail/ngx_mail_imap_handler.c
+++ b/src/mail/ngx_mail_imap_handler.c
@@ -226,6 +226,10 @@ ngx_mail_imap_auth_state(ngx_event_t *re
         ngx_str_set(&s->out, imap_next);
     }
 
+    if (s->buffer->pos < s->buffer->last) {
+        s->blocked = 1;
+    }
+
     switch (rc) {
 
     case NGX_DONE:
@@ -275,13 +279,14 @@ ngx_mail_imap_auth_state(ngx_event_t *re
 
         if (s->state) {
             /* preserve tag */
-            s->arg_start = s->buffer->start + s->tag.len;
-            s->buffer->pos = s->arg_start;
-            s->buffer->last = s->arg_start;
+            s->arg_start = s->buffer->pos;
 
         } else {
-            s->buffer->pos = s->buffer->start;
-            s->buffer->last = s->buffer->start;
+            if (s->buffer->pos == s->buffer->last) {
+                s->buffer->pos = s->buffer->start;
+                s->buffer->last = s->buffer->start;
+            }
+
             s->tag.len = 0;
         }
     }
@@ -459,6 +464,8 @@ ngx_mail_imap_starttls(ngx_mail_session_
     if (c->ssl == NULL) {
         sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
         if (sslcf->starttls) {
+            s->buffer->pos = s->buffer->start;
+            s->buffer->last = s->buffer->start;
             c->read->handler = ngx_mail_starttls_handler;
             return NGX_OK;
         }
--- a/src/mail/ngx_mail_parse.c
+++ b/src/mail/ngx_mail_parse.c
@@ -231,6 +231,8 @@ ngx_mail_imap_parse_command(ngx_mail_ses
     ngx_str_t  *arg;
     enum {
         sw_start = 0,
+        sw_tag,
+        sw_invalid,
         sw_spaces_before_command,
         sw_command,
         sw_spaces_before_argument,
@@ -253,18 +255,21 @@ ngx_mail_imap_parse_command(ngx_mail_ses
 
         /* IMAP tag */
         case sw_start:
+            s->tag_start = p;
+            state = sw_tag;
+
+            /* fall through */
+
+        case sw_tag:
             switch (ch) {
             case ' ':
-                s->tag.len = p - s->buffer->start + 1;
-                s->tag.data = s->buffer->start;
+                s->tag.len = p - s->tag_start + 1;
+                s->tag.data = s->tag_start;
                 state = sw_spaces_before_command;
                 break;
             case CR:
-                s->state = sw_start;
-                return NGX_MAIL_PARSE_INVALID_COMMAND;
             case LF:
-                s->state = sw_start;
-                return NGX_MAIL_PARSE_INVALID_COMMAND;
+                goto invalid;
             default:
                 if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')
                     && (ch < '0' || ch > '9') && ch != '-' && ch != '.'
@@ -272,23 +277,23 @@ ngx_mail_imap_parse_command(ngx_mail_ses
                 {
                     goto invalid;
                 }
-                if (p - s->buffer->start > 31) {
+                if (p - s->tag_start > 31) {
                     goto invalid;
                 }
                 break;
             }
             break;
 
+        case sw_invalid:
+            goto invalid;
+
         case sw_spaces_before_command:
             switch (ch) {
             case ' ':
                 break;
             case CR:
-                s->state = sw_start;
-                return NGX_MAIL_PARSE_INVALID_COMMAND;
             case LF:
-                s->state = sw_start;
-                return NGX_MAIL_PARSE_INVALID_COMMAND;
+                goto invalid;
             default:
                 s->cmd_start = p;
                 state = sw_command;
@@ -408,6 +413,9 @@ ngx_mail_imap_parse_command(ngx_mail_ses
                     goto invalid;
                 }
 
+                s->cmd.data = s->cmd_start;
+                s->cmd.len = p - s->cmd_start;
+
                 switch (ch) {
                 case ' ':
                     state = sw_spaces_before_argument;
@@ -631,13 +639,40 @@ done:
 
 invalid:
 
-    s->state = sw_start;
+    s->state = sw_invalid;
     s->quoted = 0;
     s->backslash = 0;
     s->no_sync_literal = 0;
     s->literal_len = 0;
 
-    return NGX_MAIL_PARSE_INVALID_COMMAND;
+    /* skip invalid command till LF */
+
+    for ( /* void */ ; p < s->buffer->last; p++) {
+        if (*p == LF) {
+            s->state = sw_start;
+            s->buffer->pos = p + 1;
+
+            /* detect non-synchronizing literals */
+
+            if ((size_t) (p - s->buffer->start) > sizeof("{1+}") - 1) {
+                p--;
+
+                if (*p == CR) {
+                    p--;
+                }
+
+                if (*p == '}' && *(p - 1) == '+') {
+                    s->quit = 1;
+                }
+            }
+
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+    }
+
+    s->buffer->pos = p;
+
+    return NGX_AGAIN;
 }
 
 
--- a/src/mail/ngx_mail_proxy_module.c
+++ b/src/mail/ngx_mail_proxy_module.c
@@ -486,6 +486,10 @@ ngx_mail_proxy_imap_handler(ngx_event_t 
         c->log->action = NULL;
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
 
+        if (s->buffer->pos < s->buffer->last) {
+            ngx_post_event(c->write, &ngx_posted_events);
+        }
+
         ngx_mail_proxy_handler(s->connection->write);
 
         return;