
/*
 * 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;
}
