/*
* Copyright (C) Igor Sysoev
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>
#include <ngx_mail.h>
#include <ngx_mail_pop3_module.h>
#include <ngx_mail_imap_module.h>
#include <ngx_mail_smtp_module.h>
ngx_int_t
ngx_mail_pop3_parse_command(ngx_mail_session_t *s)
{
u_char ch, *p, *c, c0, c1, c2, c3;
ngx_str_t *arg;
enum {
sw_start = 0,
sw_command,
sw_invalid,
sw_spaces_before_argument,
sw_argument,
sw_almost_done
} state;
state = s->state;
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
switch (state) {
/* POP3 command */
case sw_start:
s->cmd_start = p;
state = sw_command;
/* fall through */
case sw_command:
if (ch == ' ' || ch == CR || ch == LF) {
c = s->cmd_start;
if (p - c == 4) {
c0 = ngx_toupper(c[0]);
c1 = ngx_toupper(c[1]);
c2 = ngx_toupper(c[2]);
c3 = ngx_toupper(c[3]);
if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R')
{
s->command = NGX_POP3_USER;
} else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S')
{
s->command = NGX_POP3_PASS;
} else if (c0 == 'A' && c1 == 'P' && c2 == 'O' && c3 == 'P')
{
s->command = NGX_POP3_APOP;
} else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
{
s->command = NGX_POP3_QUIT;
} else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A')
{
s->command = NGX_POP3_CAPA;
} else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
{
s->command = NGX_POP3_AUTH;
} else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
{
s->command = NGX_POP3_NOOP;
#if (NGX_MAIL_SSL)
} else if (c0 == 'S' && c1 == 'T' && c2 == 'L' && c3 == 'S')
{
s->command = NGX_POP3_STLS;
#endif
} else {
goto invalid;
}
} else {
goto invalid;
}
s->cmd.data = s->cmd_start;
s->cmd.len = p - s->cmd_start;
switch (ch) {
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
}
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
goto invalid;
}
break;
case sw_invalid:
goto invalid;
case sw_spaces_before_argument:
switch (ch) {
case ' ':
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
default:
if (s->args.nelts <= 2) {
state = sw_argument;
s->arg_start = p;
break;
}
goto invalid;
}
break;
case sw_argument:
switch (ch) {
case ' ':
/*
* the space should be considered as part of the at username
* or password, but not of argument in other commands
*/
if (s->command == NGX_POP3_USER
|| s->command == NGX_POP3_PASS)
{
break;
}
/* fall through */
case CR:
case LF:
arg = ngx_array_push(&s->args);
if (arg == NULL) {
return NGX_ERROR;
}
arg->len = p - s->arg_start;
arg->data = s->arg_start;
s->arg_start = NULL;
switch (ch) {
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
default:
break;
}
break;
case sw_almost_done:
switch (ch) {
case LF:
goto done;
default:
goto invalid;
}
}
}
s->buffer->pos = p;
s->state = state;
return NGX_AGAIN;
done:
s->buffer->pos = p + 1;
s->state = (s->command != NGX_POP3_AUTH) ? sw_start : sw_argument;
return NGX_OK;
invalid:
s->state = sw_invalid;
/* skip invalid command till LF */
for ( /* void */ ; p < s->buffer->last; p++) {
if (*p == LF) {
s->state = sw_start;
s->buffer->pos = p + 1;
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
}
s->buffer->pos = p;
return NGX_AGAIN;
}
ngx_int_t
ngx_mail_imap_parse_command(ngx_mail_session_t *s)
{
u_char ch, *p, *c, *dst, *src, *end;
ngx_str_t *arg;
enum {
sw_start = 0,
sw_tag,
sw_invalid,
sw_spaces_before_command,
sw_command,
sw_spaces_before_argument,
sw_argument,
sw_backslash,
sw_literal,
sw_no_sync_literal_argument,
sw_start_literal_argument,
sw_literal_argument,
sw_end_literal_argument,
sw_almost_done
} state;
state = s->state;
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
switch (state) {
/* 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->tag_start + 1;
s->tag.data = s->tag_start;
state = sw_spaces_before_command;
break;
case CR:
case LF:
goto invalid;
default:
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')
&& (ch < '0' || ch > '9') && ch != '-' && ch != '.'
&& ch != '_')
{
goto invalid;
}
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:
case LF:
goto invalid;
default:
s->cmd_start = p;
state = sw_command;
break;
}
break;
case sw_command:
if (ch == ' ' || ch == CR || ch == LF) {
c = s->cmd_start;
switch (p - c) {
case 4:
if ((c[0] == 'N' || c[0] == 'n')
&& (c[1] == 'O'|| c[1] == 'o')
&& (c[2] == 'O'|| c[2] == 'o')
&& (c[3] == 'P'|| c[3] == 'p'))
{
s->command = NGX_IMAP_NOOP;
} else {
goto invalid;
}
break;
case 5:
if ((c[0] == 'L'|| c[0] == 'l')
&& (c[1] == 'O'|| c[1] == 'o')
&& (c[2] == 'G'|| c[2] == 'g')
&& (c[3] == 'I'|| c[3] == 'i')
&& (c[4] == 'N'|| c[4] == 'n'))
{
s->command = NGX_IMAP_LOGIN;
} else {
goto invalid;
}
break;
case 6:
if ((c[0] == 'L'|| c[0] == 'l')
&& (c[1] == 'O'|| c[1] == 'o')
&& (c[2] == 'G'|| c[2] == 'g')
&& (c[3] == 'O'|| c[3] == 'o')
&& (c[4] == 'U'|| c[4] == 'u')
&& (c[5] == 'T'|| c[5] == 't'))
{
s->command = NGX_IMAP_LOGOUT;
} else {
goto invalid;
}
break;
#if (NGX_MAIL_SSL)
case 8:
if ((c[0] == 'S'|| c[0] == 's')
&& (c[1] == 'T'|| c[1] == 't')
&& (c[2] == 'A'|| c[2] == 'a')
&& (c[3] == 'R'|| c[3] == 'r')
&& (c[4] == 'T'|| c[4] == 't')
&& (c[5] == 'T'|| c[5] == 't')
&& (c[6] == 'L'|| c[6] == 'l')
&& (c[7] == 'S'|| c[7] == 's'))
{
s->command = NGX_IMAP_STARTTLS;
} else {
goto invalid;
}
break;
#endif
case 10:
if ((c[0] == 'C'|| c[0] == 'c')
&& (c[1] == 'A'|| c[1] == 'a')
&& (c[2] == 'P'|| c[2] == 'p')
&& (c[3] == 'A'|| c[3] == 'a')
&& (c[4] == 'B'|| c[4] == 'b')
&& (c[5] == 'I'|| c[5] == 'i')
&& (c[6] == 'L'|| c[6] == 'l')
&& (c[7] == 'I'|| c[7] == 'i')
&& (c[8] == 'T'|| c[8] == 't')
&& (c[9] == 'Y'|| c[9] == 'y'))
{
s->command = NGX_IMAP_CAPABILITY;
} else {
goto invalid;
}
break;
case 12:
if ((c[0] == 'A'|| c[0] == 'a')
&& (c[1] == 'U'|| c[1] == 'u')
&& (c[2] == 'T'|| c[2] == 't')
&& (c[3] == 'H'|| c[3] == 'h')
&& (c[4] == 'E'|| c[4] == 'e')
&& (c[5] == 'N'|| c[5] == 'n')
&& (c[6] == 'T'|| c[6] == 't')
&& (c[7] == 'I'|| c[7] == 'i')
&& (c[8] == 'C'|| c[8] == 'c')
&& (c[9] == 'A'|| c[9] == 'a')
&& (c[10] == 'T'|| c[10] == 't')
&& (c[11] == 'E'|| c[11] == 'e'))
{
s->command = NGX_IMAP_AUTHENTICATE;
} else {
goto invalid;
}
break;
default:
goto invalid;
}
s->cmd.data = s->cmd_start;
s->cmd.len = p - s->cmd_start;
switch (ch) {
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
}
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
goto invalid;
}
break;
case sw_spaces_before_argument:
switch (ch) {
case ' ':
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
case '"':
if (s->args.nelts <= 2) {
s->quoted = 1;
s->arg_start = p + 1;
state = sw_argument;
break;
}
goto invalid;
case '{':
if (s->args.nelts <= 2) {
state = sw_literal;
break;
}
goto invalid;
default:
if (s->args.nelts <= 2) {
s->arg_start = p;
state = sw_argument;
break;
}
goto invalid;
}
break;
case sw_argument:
if (ch == ' ' && s->quoted) {
break;
}
switch (ch) {
case '"':
if (!s->quoted) {
break;
}
s->quoted = 0;
/* fall through */
case ' ':
case CR:
case LF:
arg = ngx_array_push(&s->args);
if (arg == NULL) {
return NGX_ERROR;
}
arg->len = p - s->arg_start;
arg->data = s->arg_start;
if (s->backslash) {
dst = s->arg_start;
end = p;
for (src = dst; src < end; dst++) {
*dst = *src;
if (*src++ == '\\') {
*dst = *src++;
}
}
arg->len = dst - s->arg_start;
s->backslash = 0;
}
s->arg_start = NULL;
switch (ch) {
case '"':
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
case '\\':
if (s->quoted) {
s->backslash = 1;
state = sw_backslash;
}
break;
}
break;
case sw_backslash:
switch (ch) {
case CR:
case LF:
goto invalid;
default:
state = sw_argument;
}
break;
case sw_literal:
if (ch >= '0' && ch <= '9') {
s->literal_len = s->literal_len * 10 + (ch - '0');
break;
}
if (ch == '}') {
state = sw_start_literal_argument;
break;
}
if (ch == '+') {
state = sw_no_sync_literal_argument;
break;
}
goto invalid;
case sw_no_sync_literal_argument:
if (ch == '}') {
s->no_sync_literal = 1;
state = sw_start_literal_argument;
break;
}
goto invalid;
case sw_start_literal_argument:
switch (ch) {
case CR:
break;
case LF:
s->buffer->pos = p + 1;
s->arg_start = p + 1;
if (s->no_sync_literal == 0) {
s->state = sw_literal_argument;
return NGX_IMAP_NEXT;
}
state = sw_literal_argument;
s->no_sync_literal = 0;
break;
default:
goto invalid;
}
break;
case sw_literal_argument:
if (s->literal_len && --s->literal_len) {
break;
}
arg = ngx_array_push(&s->args);
if (arg == NULL) {
return NGX_ERROR;
}
arg->len = p + 1 - s->arg_start;
arg->data = s->arg_start;
s->arg_start = NULL;
state = sw_end_literal_argument;
break;
case sw_end_literal_argument:
switch (ch) {
case '{':
if (s->args.nelts <= 2) {
state = sw_literal;
break;
}
goto invalid;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
default:
state = sw_spaces_before_argument;
break;
}
break;
case sw_almost_done:
switch (ch) {
case LF:
goto done;
default:
goto invalid;
}
}
}
s->buffer->pos = p;
s->state = state;
return NGX_AGAIN;
done:
s->buffer->pos = p + 1;
s->state = (s->command != NGX_IMAP_AUTHENTICATE) ? sw_start : sw_argument;
return NGX_OK;
invalid:
s->state = sw_invalid;
s->quoted = 0;
s->backslash = 0;
s->no_sync_literal = 0;
s->literal_len = 0;
/* 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;
}
ngx_int_t
ngx_mail_smtp_parse_command(ngx_mail_session_t *s)
{
u_char ch, *p, *c, c0, c1, c2, c3;
ngx_str_t *arg;
enum {
sw_start = 0,
sw_command,
sw_invalid,
sw_spaces_before_argument,
sw_argument,
sw_almost_done
} state;
state = s->state;
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
switch (state) {
/* SMTP command */
case sw_start:
s->cmd_start = p;
state = sw_command;
/* fall through */
case sw_command:
if (ch == ' ' || ch == CR || ch == LF) {
c = s->cmd_start;
if (p - c == 4) {
c0 = ngx_toupper(c[0]);
c1 = ngx_toupper(c[1]);
c2 = ngx_toupper(c[2]);
c3 = ngx_toupper(c[3]);
if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'O')
{
s->command = NGX_SMTP_HELO;
} else if (c0 == 'E' && c1 == 'H' && c2 == 'L' && c3 == 'O')
{
s->command = NGX_SMTP_EHLO;
} else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
{
s->command = NGX_SMTP_QUIT;
} else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
{
s->command = NGX_SMTP_AUTH;
} else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
{
s->command = NGX_SMTP_NOOP;
} else if (c0 == 'M' && c1 == 'A' && c2 == 'I' && c3 == 'L')
{
s->command = NGX_SMTP_MAIL;
} else if (c0 == 'R' && c1 == 'S' && c2 == 'E' && c3 == 'T')
{
s->command = NGX_SMTP_RSET;
} else if (c0 == 'R' && c1 == 'C' && c2 == 'P' && c3 == 'T')
{
s->command = NGX_SMTP_RCPT;
} else if (c0 == 'V' && c1 == 'R' && c2 == 'F' && c3 == 'Y')
{
s->command = NGX_SMTP_VRFY;
} else if (c0 == 'E' && c1 == 'X' && c2 == 'P' && c3 == 'N')
{
s->command = NGX_SMTP_EXPN;
} else if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'P')
{
s->command = NGX_SMTP_HELP;
} else {
goto invalid;
}
#if (NGX_MAIL_SSL)
} else if (p - c == 8) {
if ((c[0] == 'S'|| c[0] == 's')
&& (c[1] == 'T'|| c[1] == 't')
&& (c[2] == 'A'|| c[2] == 'a')
&& (c[3] == 'R'|| c[3] == 'r')
&& (c[4] == 'T'|| c[4] == 't')
&& (c[5] == 'T'|| c[5] == 't')
&& (c[6] == 'L'|| c[6] == 'l')
&& (c[7] == 'S'|| c[7] == 's'))
{
s->command = NGX_SMTP_STARTTLS;
} else {
goto invalid;
}
#endif
} else {
goto invalid;
}
s->cmd.data = s->cmd_start;
s->cmd.len = p - s->cmd_start;
switch (ch) {
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
}
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
goto invalid;
}
break;
case sw_invalid:
goto invalid;
case sw_spaces_before_argument:
switch (ch) {
case ' ':
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
default:
if (s->args.nelts <= 10) {
state = sw_argument;
s->arg_start = p;
break;
}
goto invalid;
}
break;
case sw_argument:
switch (ch) {
case ' ':
case CR:
case LF:
arg = ngx_array_push(&s->args);
if (arg == NULL) {
return NGX_ERROR;
}
arg->len = p - s->arg_start;
arg->data = s->arg_start;
s->arg_start = NULL;
switch (ch) {
case ' ':
state = sw_spaces_before_argument;
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
}
break;
default:
break;
}
break;
case sw_almost_done:
switch (ch) {
case LF:
goto done;
default:
goto invalid;
}
}
}
s->buffer->pos = p;
s->state = state;
return NGX_AGAIN;
done:
s->buffer->pos = p + 1;
s->state = (s->command != NGX_SMTP_AUTH) ? sw_start : sw_argument;
return NGX_OK;
invalid:
s->state = sw_invalid;
/* skip invalid command till LF */
for ( /* void */ ; p < s->buffer->last; p++) {
if (*p == LF) {
s->state = sw_start;
s->buffer->pos = p + 1;
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
}
s->buffer->pos = p;
return NGX_AGAIN;
}
ngx_int_t
ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
{
ngx_str_t *arg;
#if (NGX_MAIL_SSL)
if (ngx_mail_starttls_only(s, c)) {
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
#endif
if (s->args.nelts == 0) {
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
arg = s->args.elts;
if (arg[0].len == 5) {
if (ngx_strncasecmp(arg[0].data, (u_char *) "LOGIN", 5) == 0) {
if (s->args.nelts == 1) {
return NGX_MAIL_AUTH_LOGIN;
}
if (s->args.nelts == 2) {
return NGX_MAIL_AUTH_LOGIN_USERNAME;
}
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN", 5) == 0) {
if (s->args.nelts == 1) {
return NGX_MAIL_AUTH_PLAIN;
}
if (s->args.nelts == 2) {
return ngx_mail_auth_plain(s, c, 1);
}
}
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
if (arg[0].len == 8) {
if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
if (s->args.nelts != 1) {
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
return NGX_MAIL_AUTH_CRAM_MD5;
}
if (ngx_strncasecmp(arg[0].data, (u_char *) "EXTERNAL", 8) == 0) {
if (s->args.nelts == 1) {
return NGX_MAIL_AUTH_EXTERNAL;
}
if (s->args.nelts == 2) {
return ngx_mail_auth_external(s, c, 1);
}
}
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
return NGX_MAIL_PARSE_INVALID_COMMAND;
}