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.
This commit is contained in:
Maxim Dounin 2021-05-19 03:13:28 +03:00
parent 4617dd64b8
commit 5015209054
4 changed files with 65 additions and 18 deletions

View File

@ -236,6 +236,7 @@ typedef struct {
/* used to parse POP3/IMAP/SMTP command */ /* used to parse POP3/IMAP/SMTP command */
ngx_uint_t state; ngx_uint_t state;
u_char *tag_start;
u_char *cmd_start; u_char *cmd_start;
u_char *arg_start; u_char *arg_start;
ngx_uint_t literal_len; ngx_uint_t literal_len;

View File

@ -226,6 +226,10 @@ ngx_mail_imap_auth_state(ngx_event_t *rev)
ngx_str_set(&s->out, imap_next); ngx_str_set(&s->out, imap_next);
} }
if (s->buffer->pos < s->buffer->last) {
s->blocked = 1;
}
switch (rc) { switch (rc) {
case NGX_DONE: case NGX_DONE:
@ -275,13 +279,14 @@ ngx_mail_imap_auth_state(ngx_event_t *rev)
if (s->state) { if (s->state) {
/* preserve tag */ /* preserve tag */
s->arg_start = s->buffer->start + s->tag.len; s->arg_start = s->buffer->pos;
s->buffer->pos = s->arg_start;
s->buffer->last = s->arg_start;
} else { } else {
s->buffer->pos = s->buffer->start; if (s->buffer->pos == s->buffer->last) {
s->buffer->last = s->buffer->start; s->buffer->pos = s->buffer->start;
s->buffer->last = s->buffer->start;
}
s->tag.len = 0; s->tag.len = 0;
} }
} }
@ -459,6 +464,8 @@ ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
if (c->ssl == NULL) { if (c->ssl == NULL) {
sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
if (sslcf->starttls) { if (sslcf->starttls) {
s->buffer->pos = s->buffer->start;
s->buffer->last = s->buffer->start;
c->read->handler = ngx_mail_starttls_handler; c->read->handler = ngx_mail_starttls_handler;
return NGX_OK; return NGX_OK;
} }

View File

@ -231,6 +231,8 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s)
ngx_str_t *arg; ngx_str_t *arg;
enum { enum {
sw_start = 0, sw_start = 0,
sw_tag,
sw_invalid,
sw_spaces_before_command, sw_spaces_before_command,
sw_command, sw_command,
sw_spaces_before_argument, sw_spaces_before_argument,
@ -253,18 +255,21 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s)
/* IMAP tag */ /* IMAP tag */
case sw_start: case sw_start:
s->tag_start = p;
state = sw_tag;
/* fall through */
case sw_tag:
switch (ch) { switch (ch) {
case ' ': case ' ':
s->tag.len = p - s->buffer->start + 1; s->tag.len = p - s->tag_start + 1;
s->tag.data = s->buffer->start; s->tag.data = s->tag_start;
state = sw_spaces_before_command; state = sw_spaces_before_command;
break; break;
case CR: case CR:
s->state = sw_start;
return NGX_MAIL_PARSE_INVALID_COMMAND;
case LF: case LF:
s->state = sw_start; goto invalid;
return NGX_MAIL_PARSE_INVALID_COMMAND;
default: default:
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')
&& (ch < '0' || ch > '9') && ch != '-' && ch != '.' && (ch < '0' || ch > '9') && ch != '-' && ch != '.'
@ -272,23 +277,23 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s)
{ {
goto invalid; goto invalid;
} }
if (p - s->buffer->start > 31) { if (p - s->tag_start > 31) {
goto invalid; goto invalid;
} }
break; break;
} }
break; break;
case sw_invalid:
goto invalid;
case sw_spaces_before_command: case sw_spaces_before_command:
switch (ch) { switch (ch) {
case ' ': case ' ':
break; break;
case CR: case CR:
s->state = sw_start;
return NGX_MAIL_PARSE_INVALID_COMMAND;
case LF: case LF:
s->state = sw_start; goto invalid;
return NGX_MAIL_PARSE_INVALID_COMMAND;
default: default:
s->cmd_start = p; s->cmd_start = p;
state = sw_command; state = sw_command;
@ -408,6 +413,9 @@ ngx_mail_imap_parse_command(ngx_mail_session_t *s)
goto invalid; goto invalid;
} }
s->cmd.data = s->cmd_start;
s->cmd.len = p - s->cmd_start;
switch (ch) { switch (ch) {
case ' ': case ' ':
state = sw_spaces_before_argument; state = sw_spaces_before_argument;
@ -631,13 +639,40 @@ done:
invalid: invalid:
s->state = sw_start; s->state = sw_invalid;
s->quoted = 0; s->quoted = 0;
s->backslash = 0; s->backslash = 0;
s->no_sync_literal = 0; s->no_sync_literal = 0;
s->literal_len = 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;
} }

View File

@ -486,6 +486,10 @@ ngx_mail_proxy_imap_handler(ngx_event_t *rev)
c->log->action = NULL; c->log->action = NULL;
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); 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); ngx_mail_proxy_handler(s->connection->write);
return; return;