diff options
author | Igor Sysoev <igor@sysoev.ru> | 2005-07-08 18:34:20 +0400 |
---|---|---|
committer | Igor Sysoev <igor@sysoev.ru> | 2005-07-08 18:34:20 +0400 |
commit | 5192b3651f2f44fb5769828a2a4060989c7e9c5f (patch) | |
tree | d1ef9dfd855e836c6f05b496be88dc835537d03f /src/imap | |
parent | 549c6c644976dc694765d77110ebd2504ff7ce2b (diff) |
nginx-0.1.38-RELEASE importrelease-0.1.38
*) Feature: the "limit_rate" directive is supported in in proxy and
FastCGI mode.
*) Feature: the "X-Accel-Limit-Rate" response header line is supported
in proxy and FastCGI mode.
*) Feature: the "break" directive.
*) Feature: the "log_not_found" directive.
*) Bugfix: the response status code was not changed when request was
redirected by the ""X-Accel-Redirect" header line.
*) Bugfix: the variables set by the "set" directive could not be used
in SSI.
*) Bugfix: the segmentation fault may occurred if the SSI page has more
than one remote subrequest.
*) Bugfix: nginx treated the backend response as invalid if the status
line in the header was transferred in two packets; the bug had
appeared in 0.1.29.
*) Feature: the "ssi_types" directive.
*) Feature: the "autoindex_exact_size" directive.
*) Bugfix: the ngx_http_autoindex_module did not support the long file
names in UTF-8.
*) Feature: the IMAP/POP3 proxy.
Diffstat (limited to 'src/imap')
-rw-r--r-- | src/imap/ngx_imap.h | 63 | ||||
-rw-r--r-- | src/imap/ngx_imap_auth_http_module.c | 648 | ||||
-rw-r--r-- | src/imap/ngx_imap_core_module.c | 159 | ||||
-rw-r--r-- | src/imap/ngx_imap_handler.c | 289 | ||||
-rw-r--r-- | src/imap/ngx_imap_parse.c | 447 | ||||
-rw-r--r-- | src/imap/ngx_imap_proxy_module.c | 296 |
6 files changed, 1697 insertions, 205 deletions
diff --git a/src/imap/ngx_imap.h b/src/imap/ngx_imap.h index 5ba390055..8a506db64 100644 --- a/src/imap/ngx_imap.h +++ b/src/imap/ngx_imap.h @@ -36,6 +36,12 @@ typedef struct { ngx_uint_t protocol; + ngx_buf_t *pop3_capability; + ngx_buf_t *imap_capability; + + ngx_array_t pop3_capabilities; + ngx_array_t imap_capabilities; + /* server ctx */ ngx_imap_conf_ctx_t *ctx; } ngx_imap_core_srv_conf_t; @@ -51,11 +57,20 @@ typedef struct { typedef enum { - ngx_pop3_start = 0, - ngx_pop3_user + ngx_imap_start = 0, + ngx_imap_login, + ngx_imap_user, + ngx_imap_passwd, } ngx_imap_state_e; +typedef enum { + ngx_pop3_start = 0, + ngx_pop3_user, + ngx_pop3_passwd +} ngx_po3_state_e; + + typedef struct { ngx_peer_connection_t upstream; ngx_buf_t *buffer; @@ -68,6 +83,7 @@ typedef struct { ngx_connection_t *connection; ngx_buf_t *buffer; + ngx_str_t out; void **ctx; void **main_conf; @@ -75,39 +91,55 @@ typedef struct { ngx_imap_proxy_ctx_t *proxy; - ngx_imap_state_e imap_state; + ngx_uint_t imap_state; unsigned protocol:1; + unsigned quoted:1; ngx_str_t login; ngx_str_t passwd; + ngx_str_t tag; + ngx_uint_t command; ngx_array_t args; + ngx_uint_t login_attempt; + /* used to parse IMAP/POP3 command */ ngx_uint_t state; + u_char *cmd_start; u_char *arg_start; u_char *arg_end; + ngx_uint_t literal_len; } ngx_imap_session_t; #define NGX_POP3_USER 1 #define NGX_POP3_PASS 2 -#define NGX_POP3_APOP 3 -#define NGX_POP3_STAT 4 -#define NGX_POP3_LIST 5 -#define NGX_POP3_RETR 6 -#define NGX_POP3_DELE 7 -#define NGX_POP3_NOOP 8 -#define NGX_POP3_RSET 9 -#define NGX_POP3_TOP 10 -#define NGX_POP3_UIDL 11 -#define NGX_POP3_QUIT 12 +#define NGX_POP3_CAPA 3 +#define NGX_POP3_QUIT 4 +#define NGX_POP3_NOOP 5 +#define NGX_POP3_APOP 6 +#define NGX_POP3_STAT 7 +#define NGX_POP3_LIST 8 +#define NGX_POP3_RETR 9 +#define NGX_POP3_DELE 10 +#define NGX_POP3_RSET 11 +#define NGX_POP3_TOP 12 +#define NGX_POP3_UIDL 13 + + +#define NGX_IMAP_LOGIN 1 +#define NGX_IMAP_LOGOUT 2 +#define NGX_IMAP_CAPABILITY 3 +#define NGX_IMAP_NOOP 4 + +#define NGX_IMAP_NEXT 5 -#define NGX_IMAP_PARSE_INVALID_COMMAND 10 +#define NGX_IMAP_PARSE_INVALID_COMMAND 20 #define NGX_IMAP_PROXY_INVALID 10 @@ -135,9 +167,12 @@ typedef struct { void ngx_imap_init_connection(ngx_connection_t *c); +void ngx_imap_auth_state(ngx_event_t *rev); +void ngx_pop3_auth_state(ngx_event_t *rev); void ngx_imap_close_connection(ngx_connection_t *c); void ngx_imap_session_internal_server_error(ngx_imap_session_t *s); +ngx_int_t ngx_imap_parse_command(ngx_imap_session_t *s); ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s); diff --git a/src/imap/ngx_imap_auth_http_module.c b/src/imap/ngx_imap_auth_http_module.c index fa984b855..e66593a4a 100644 --- a/src/imap/ngx_imap_auth_http_module.c +++ b/src/imap/ngx_imap_auth_http_module.c @@ -12,24 +12,54 @@ typedef struct { - ngx_peers_t *peers; + ngx_peers_t *peers; - ngx_msec_t timeout; + ngx_msec_t timeout; - ngx_str_t host_header; - ngx_str_t uri; + ngx_str_t host_header; + ngx_str_t uri; } ngx_imap_auth_http_conf_t; -typedef struct { - ngx_buf_t *request; - ngx_buf_t *response; - ngx_peer_connection_t peer; -} ngx_imap_auth_http_ctx_t; +typedef struct ngx_imap_auth_http_ctx_s ngx_imap_auth_http_ctx_t; + +typedef void (*ngx_imap_auth_http_handler_pt)(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx); + +struct ngx_imap_auth_http_ctx_s { + ngx_buf_t *request; + ngx_buf_t *response; + ngx_peer_connection_t peer; + + ngx_imap_auth_http_handler_pt handler; + + ngx_uint_t state; + ngx_uint_t hash; /* no needed ? */ + + u_char *header_name_start; + u_char *header_name_end; + u_char *header_start; + u_char *header_end; + + ngx_str_t addr; + ngx_str_t port; + ngx_str_t err; + + ngx_msec_t sleep; + + ngx_peers_t *peers; +}; static void ngx_imap_auth_http_write_handler(ngx_event_t *wev); static void ngx_imap_auth_http_read_handler(ngx_event_t *rev); +static void ngx_imap_auth_http_ignore_status_line(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx); +static void ngx_imap_auth_http_process_headers(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx); +static void ngx_imap_auth_sleep_handler(ngx_event_t *rev); +static ngx_int_t ngx_imap_auth_http_parse_header_line(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx); static void ngx_imap_auth_http_block_read(ngx_event_t *rev); static void ngx_imap_auth_http_dummy_handler(ngx_event_t *ev); static ngx_buf_t *ngx_imap_auth_http_create_request(ngx_imap_session_t *s, @@ -124,6 +154,8 @@ ngx_imap_auth_http_init(ngx_imap_session_t *s) ctx->peer.connection->read->handler = ngx_imap_auth_http_read_handler; ctx->peer.connection->write->handler = ngx_imap_auth_http_write_handler; + ctx->handler = ngx_imap_auth_http_ignore_status_line; + if (rc == NGX_OK) { ngx_imap_auth_http_write_handler(ctx->peer.connection->write); return; @@ -194,7 +226,6 @@ static void ngx_imap_auth_http_read_handler(ngx_event_t *rev) { ssize_t n, size; - ngx_peers_t *peers; ngx_connection_t *c; ngx_imap_session_t *s; ngx_imap_auth_http_ctx_t *ctx; @@ -224,24 +255,604 @@ ngx_imap_auth_http_read_handler(ngx_event_t *rev) } } - size = ctx->response->last - ctx->response->pos; + size = ctx->response->end - ctx->response->last; n = ngx_recv(c, ctx->response->pos, size); - if (n == NGX_ERROR || n == 0) { + if (n > 0) { + ctx->response->last += n; + + ctx->handler(s, ctx); + return; + } + + if (n == NGX_AGAIN) { + return; + } + + ngx_close_connection(ctx->peer.connection); + ngx_imap_session_internal_server_error(s); +} + + +static void +ngx_imap_auth_http_ignore_status_line(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx) +{ + u_char *p, ch; + enum { + sw_start = 0, + sw_H, + sw_HT, + sw_HTT, + sw_HTTP, + sw_skip, + sw_almost_done + } state; + + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, + "imap auth http process status line"); + + state = ctx->state; + + for (p = ctx->response->pos; p < ctx->response->last; p++) { + ch = *p; + + switch (state) { + + /* "HTTP/" */ + case sw_start: + if (ch == 'H') { + state = sw_H; + break; + } + goto next; + + case sw_H: + if (ch == 'T') { + state = sw_HT; + break; + } + goto next; + + case sw_HT: + if (ch == 'T') { + state = sw_HTT; + break; + } + goto next; + + case sw_HTT: + if (ch == 'P') { + state = sw_HTTP; + break; + } + goto next; + + case sw_HTTP: + if (ch == '/') { + state = sw_skip; + break; + } + goto next; + + /* any text until end of line */ + case sw_skip: + switch (ch) { + case CR: + state = sw_almost_done; + + break; + case LF: + goto done; + } + break; + + /* end of status line */ + case sw_almost_done: + if (ch == LF) { + goto done; + } + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server sent invalid response"); + ngx_close_connection(ctx->peer.connection); + ngx_imap_session_internal_server_error(s); + return; + } + } + + ctx->response->pos = p; + ctx->state = state; + + return; + +next: + + p = ctx->response->start - 1; + +done: + + ctx->response->pos = p + 1; + ctx->state = 0; + ctx->handler = ngx_imap_auth_http_process_headers; + ctx->handler(s, ctx); +} + + +static void +ngx_imap_auth_http_process_headers(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx) +{ + u_char *p; + size_t len, size; + ngx_int_t rc, port, n; + struct sockaddr_in *sin; + + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, + "imap auth http process headers"); + + for ( ;; ) { + rc = ngx_imap_auth_http_parse_header_line(s, ctx); + + if (rc == NGX_OK) { + +#if (NGX_DEBUG) + { + ngx_str_t key, value; + + key.len = ctx->header_name_end - ctx->header_name_start; + key.data = ctx->header_name_start; + value.len = ctx->header_end - ctx->header_start; + value.data = ctx->header_start; + + ngx_log_debug2(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, + "auth http header: \"%V: %V\"", + &key, &value); + } +#endif + + len = ctx->header_name_end - ctx->header_name_start; + + if (len == sizeof("Auth-Status") - 1 + && ngx_strncasecmp(ctx->header_name_start, "Auth-Status", + sizeof("Auth-Status") - 1) == 0) + { + len = ctx->header_end - ctx->header_start; + + if (len == 2 + && ctx->header_start[0] == 'O' + && ctx->header_start[1] == 'K') + { + continue; + } + + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + size = sizeof("-ERR") - 1 + len + sizeof(CRLF) - 1; + + } else { + size = s->tag.len + sizeof("NO") - 1 + len + + sizeof(CRLF) - 1; + } + + p = ngx_pcalloc(s->connection->pool, size); + if (p == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + ctx->err.data = p; + + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + *p++ = '-'; *p++ = 'E'; *p++ = 'R'; *p++ = 'R'; + + } else { + p = ngx_cpymem(p, s->tag.data, s->tag.len); + *p++ = 'N'; *p++ = 'O'; + } + + *p++ = ' '; + p = ngx_cpymem(p, ctx->header_start, len); + *p++ = CR; *p++ = LF; + + ctx->err.len = p - ctx->err.data; + + continue; + } + + if (len == sizeof("Auth-Server") - 1 + && ngx_strncasecmp(ctx->header_name_start, "Auth-Server", + sizeof("Auth-Server") - 1) == 0) + { + ctx->addr.len = ctx->header_end - ctx->header_start; + ctx->addr.data = ctx->header_start; + + continue; + } + + if (len == sizeof("Auth-Port") - 1 + && ngx_strncasecmp(ctx->header_name_start, "Auth-Port", + sizeof("Auth-Port") - 1) == 0) + { + ctx->port.len = ctx->header_end - ctx->header_start; + ctx->port.data = ctx->header_start; + + continue; + } + + if (len == sizeof("Auth-User") - 1 + && ngx_strncasecmp(ctx->header_name_start, "Auth-User", + sizeof("Auth-User") - 1) == 0) + { + s->login.len = ctx->header_end - ctx->header_start; + s->login.data = ctx->header_start; + + continue; + } + + if (len == sizeof("Auth-Wait") - 1 + && ngx_strncasecmp(ctx->header_name_start, "Auth-Wait", + sizeof("Auth-Wait") - 1) == 0) + { + n = ngx_atoi(ctx->header_start, + ctx->header_end - ctx->header_start); + + if (n != NGX_ERROR) { + ctx->sleep = n; + } + + continue; + } + + /* ignore other headers */ + + continue; + } + + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, + "auth http header done"); + + ngx_close_connection(ctx->peer.connection); + + if (ctx->err.len) { + (void) ngx_send(s->connection, ctx->err.data, ctx->err.len); + + if (ctx->sleep == 0) { + ngx_imap_close_connection(s->connection); + return; + } + + ngx_add_timer(s->connection->read, ctx->sleep * 1000); + + s->connection->read->handler = ngx_imap_auth_sleep_handler; + + return; + } + + if (ctx->addr.len == 0 || ctx->port.len == 0) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server did not send server or port"); + ngx_imap_session_internal_server_error(s); + return; + } + + ctx->peers = ngx_pcalloc(s->connection->pool, sizeof(ngx_peers_t)); + if (ctx->peers == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + sin = ngx_pcalloc(s->connection->pool, sizeof(struct sockaddr_in)); + if (sin == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + sin->sin_family = AF_INET; + + port = ngx_atoi(ctx->port.data, ctx->port.len); + if (port == NGX_ERROR || port < 1 || port > 65536) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server sent invalid server " + "port:\"%V\"", &ctx->port); + ngx_imap_session_internal_server_error(s); + return; + } + + sin->sin_port = htons((in_port_t) port); + + ctx->addr.data[ctx->addr.len] = '\0'; + sin->sin_addr.s_addr = inet_addr((char *) ctx->addr.data); + if (sin->sin_addr.s_addr == INADDR_NONE) { + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server sent invalid server " + "address:\"%V\"", &ctx->addr); + ngx_imap_session_internal_server_error(s); + return; + } + + ctx->peers->number = 1; + + ctx->peers->peer[0].sockaddr = (struct sockaddr *) sin; + ctx->peers->peer[0].socklen = sizeof(struct sockaddr_in); + + len = ctx->addr.len + 1 + ctx->port.len; + + ctx->peers->peer[0].name.len = len; + + ctx->peers->peer[0].name.data = ngx_palloc(s->connection->pool, + len); + if (ctx->peers->peer[0].name.data == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + len = ctx->addr.len; + + ngx_memcpy(ctx->peers->peer[0].name.data, ctx->addr.data, len); + + ctx->peers->peer[0].name.data[len++] = ':'; + + ngx_memcpy(ctx->peers->peer[0].name.data + len, + ctx->port.data, ctx->port.len); + + ctx->peers->peer[0].uri_separator = ""; + + ngx_imap_proxy_init(s, ctx->peers); + + return; + } + + if (rc == NGX_AGAIN ) { + return; + } + + /* rc == NGX_ERROR */ + + ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, + "auth http server sent invalid header in response"); ngx_close_connection(ctx->peer.connection); ngx_imap_session_internal_server_error(s); + + return; + } +} + + +static void +ngx_imap_auth_sleep_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_imap_session_t *s; + + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap auth sleep handler"); + + c = rev->data; + s = c->data; + + if (rev->timedout) { + + rev->timedout = 0; + + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + s->imap_state = ngx_pop3_start; + s->connection->read->handler = ngx_pop3_auth_state; + + } else { + s->imap_state = ngx_imap_start; + s->connection->read->handler = ngx_imap_auth_state; + } + + if (rev->ready) { + s->connection->read->handler(rev); + return; + } + + if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { + ngx_imap_close_connection(s->connection); + } + return; } + if (rev->active) { + if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { + ngx_imap_close_connection(s->connection); + } + } +} +static ngx_int_t +ngx_imap_auth_http_parse_header_line(ngx_imap_session_t *s, + ngx_imap_auth_http_ctx_t *ctx) +{ + u_char c, ch, *p; + ngx_uint_t hash; + enum { + sw_start = 0, + sw_name, + sw_space_before_value, + sw_value, + sw_space_after_value, + sw_almost_done, + sw_header_almost_done + } state; + + state = ctx->state; + hash = ctx->hash; + + for (p = ctx->response->pos; p < ctx->response->last; p++) { + ch = *p; + + switch (state) { + + /* first char */ + case sw_start: + + switch (ch) { + case CR: + ctx->header_end = p; + state = sw_header_almost_done; + break; + case LF: + ctx->header_end = p; + goto header_done; + default: + state = sw_name; + ctx->header_name_start = p; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + hash = c; + break; + } + + if (ch >= '0' && ch <= '9') { + hash = ch; + break; + } + + return NGX_ERROR; + } + break; + + /* header name */ + case sw_name: + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + hash += c; + break; + } + if (ch == ':') { + ctx->header_name_end = p; + state = sw_space_before_value; + break; + } + if (ch == '-') { + hash += ch; + break; + } - peers = NULL; + if (ch >= '0' && ch <= '9') { + hash += ch; + break; + } + + if (ch == CR) { + ctx->header_name_end = p; + ctx->header_start = p; + ctx->header_end = p; + state = sw_almost_done; + break; + } + + if (ch == LF) { + ctx->header_name_end = p; + ctx->header_start = p; + ctx->header_end = p; + goto done; + } + + return NGX_ERROR; + + /* space* before header value */ + case sw_space_before_value: + switch (ch) { + case ' ': + break; + case CR: + ctx->header_start = p; + ctx->header_end = p; + state = sw_almost_done; + break; + case LF: + ctx->header_start = p; + ctx->header_end = p; + goto done; + default: + ctx->header_start = p; + state = sw_value; + break; + } + break; + + /* header value */ + case sw_value: + switch (ch) { + case ' ': + ctx->header_end = p; + state = sw_space_after_value; + break; + case CR: + ctx->header_end = p; + state = sw_almost_done; + break; + case LF: + ctx->header_end = p; + goto done; + } + break; + + /* space* before end of header line */ + case sw_space_after_value: + switch (ch) { + case ' ': + break; + case CR: + state = sw_almost_done; + break; + case LF: + goto done; + default: + state = sw_value; + break; + } + break; + + /* end of header line */ + case sw_almost_done: + switch (ch) { + case LF: + goto done; + default: + return NGX_ERROR; + } - ngx_imap_proxy_init(s, peers); + /* end of header */ + case sw_header_almost_done: + switch (ch) { + case LF: + goto header_done; + default: + return NGX_ERROR; + } + } + } + + ctx->response->pos = p; + ctx->state = state; + ctx->hash = hash; + + return NGX_AGAIN; + +done: + + ctx->response->pos = p + 1; + ctx->state = sw_start; + ctx->hash = hash; + + return NGX_OK; + +header_done: + + ctx->response->pos = p + 1; + ctx->state = sw_start; + + return NGX_DONE; } @@ -288,6 +899,8 @@ ngx_imap_auth_http_create_request(ngx_imap_session_t *s, + sizeof("Auth-User: ") - 1 + s->login.len + sizeof(CRLF) - 1 + sizeof("Auth-Pass: ") - 1 + s->passwd.len + sizeof(CRLF) - 1 + sizeof("Auth-Protocol: imap" CRLF) - 1 + + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN + + sizeof(CRLF) - 1 + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len + sizeof(CRLF) - 1 + sizeof(CRLF) - 1; @@ -324,6 +937,9 @@ ngx_imap_auth_http_create_request(ngx_imap_session_t *s, sizeof("imap") - 1); *b->last++ = CR; *b->last++ = LF; + b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF, + s->login_attempt); + b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1); b->last = ngx_cpymem(b->last, s->connection->addr_text.data, s->connection->addr_text.len); @@ -338,7 +954,7 @@ ngx_imap_auth_http_create_request(ngx_imap_session_t *s, l.len = b->last - b->pos; l.data = b->pos; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, "imap auth http header:\n\"%V\"", &l); } #endif @@ -440,7 +1056,7 @@ ngx_imap_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } for (i = 0; i < ahcf->peers->number; i++) { - ahcf->peers->peer[i].uri_separator = ":"; + ahcf->peers->peer[i].uri_separator = ""; } ahcf->host_header = inet_upstream.host_header; diff --git a/src/imap/ngx_imap_core_module.c b/src/imap/ngx_imap_core_module.c index b4bde5282..e5d7a2050 100644 --- a/src/imap/ngx_imap_core_module.c +++ b/src/imap/ngx_imap_core_module.c @@ -18,6 +18,8 @@ static char *ngx_imap_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_imap_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_imap_core_capability(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_conf_enum_t ngx_imap_core_procotol[] = { @@ -27,6 +29,22 @@ static ngx_conf_enum_t ngx_imap_core_procotol[] = { }; +static ngx_str_t ngx_pop3_default_capabilities[] = { + ngx_string("TOP"), + ngx_string("USER"), + ngx_string("UIDL"), + ngx_null_string +}; + + +static ngx_str_t ngx_imap_default_capabilities[] = { + ngx_string("IMAP4"), + ngx_string("IMAP4rev1"), + ngx_string("UIDPLUS"), + ngx_null_string +}; + + static ngx_command_t ngx_imap_core_commands[] = { { ngx_string("server"), @@ -71,6 +89,20 @@ static ngx_command_t ngx_imap_core_commands[] = { offsetof(ngx_imap_core_srv_conf_t, timeout), NULL }, + { ngx_string("pop3_capabilities"), + NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_1MORE, + ngx_imap_core_capability, + NGX_IMAP_SRV_CONF_OFFSET, + offsetof(ngx_imap_core_srv_conf_t, pop3_capabilities), + NULL }, + + { ngx_string("imap_capabilities"), + NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_1MORE, + ngx_imap_core_capability, + NGX_IMAP_SRV_CONF_OFFSET, + offsetof(ngx_imap_core_srv_conf_t, imap_capabilities), + NULL }, + ngx_null_command }; @@ -121,7 +153,7 @@ ngx_imap_core_create_srv_conf(ngx_conf_t *cf) cscf = ngx_pcalloc(cf->pool, sizeof(ngx_imap_core_srv_conf_t)); if (cscf == NULL) { - return NGX_CONF_ERROR; + return NULL; } cscf->imap_client_buffer_size = NGX_CONF_UNSET_SIZE; @@ -129,6 +161,18 @@ ngx_imap_core_create_srv_conf(ngx_conf_t *cf) cscf->timeout = NGX_CONF_UNSET_MSEC; cscf->protocol = NGX_CONF_UNSET_UINT; + if (ngx_array_init(&cscf->pop3_capabilities, cf->pool, 4, sizeof(ngx_str_t)) + != NGX_OK) + { + return NULL; + } + + if (ngx_array_init(&cscf->imap_capabilities, cf->pool, 4, sizeof(ngx_str_t)) + != NGX_OK) + { + return NULL; + } + return cscf; } @@ -139,6 +183,11 @@ ngx_imap_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_imap_core_srv_conf_t *prev = parent; ngx_imap_core_srv_conf_t *conf = child; + size_t size; + ngx_buf_t *b; + ngx_str_t *c, *d; + ngx_uint_t i; + ngx_conf_merge_size_value(conf->imap_client_buffer_size, prev->imap_client_buffer_size, (size_t) ngx_pagesize); @@ -148,6 +197,88 @@ ngx_imap_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_unsigned_value(conf->protocol, prev->protocol, NGX_IMAP_IMAP_PROTOCOL); + + if (conf->pop3_capabilities.nelts == 0) { + conf->pop3_capabilities = prev->pop3_capabilities; + } + + if (conf->pop3_capabilities.nelts == 0) { + + for (d = ngx_pop3_default_capabilities; d->len; d++) { + c = ngx_array_push(&conf->pop3_capabilities); + if (c == NULL) { + return NGX_CONF_ERROR; + } + + *c = *d; + } + } + + size = sizeof("+OK Capability list follows" CRLF) - 1 + + sizeof("." CRLF) - 1; + + c = conf->pop3_capabilities.elts; + for (i = 0; i < conf->pop3_capabilities.nelts; i++) { + size += c[i].len + sizeof(CRLF) - 1; + } + + b = ngx_create_temp_buf(cf->pool, size); + if (b == NULL) { + return NGX_CONF_ERROR; + } + + b->last = ngx_cpymem(b->last, "+OK Capability list follows" CRLF, + sizeof("+OK Capability list follows" CRLF) - 1); + + for (i = 0; i < conf->pop3_capabilities.nelts; i++) { + b->last = ngx_cpymem(b->last, c[i].data, c[i].len); + *b->last++ = CR; *b->last++ = LF; + } + + *b->last++ = '.'; *b->last++ = CR; *b->last++ = LF; + + conf->pop3_capability = b; + + + if (conf->imap_capabilities.nelts == 0) { + conf->imap_capabilities = prev->imap_capabilities; + } + + if (conf->imap_capabilities.nelts == 0) { + + for (d = ngx_imap_default_capabilities; d->len; d++) { + c = ngx_array_push(&conf->imap_capabilities); + if (c == NULL) { + return NGX_CONF_ERROR; + } + + *c = *d; + } + } + + size = sizeof("* CAPABILITY") - 1 + sizeof(CRLF) - 1; + + c = conf->imap_capabilities.elts; + for (i = 0; i < conf->imap_capabilities.nelts; i++) { + size += 1 + c[i].len; + } + + b = ngx_create_temp_buf(cf->pool, size); + if (b == NULL) { + return NGX_CONF_ERROR; + } + + b->last = ngx_cpymem(b->last, "* CAPABILITY", sizeof("* CAPABILITY") - 1); + + for (i = 0; i < conf->imap_capabilities.nelts; i++) { + *b->last++ = ' '; + b->last = ngx_cpymem(b->last, c[i].data, c[i].len); + } + + *b->last++ = CR; *b->last++ = LF; + + conf->imap_capability = b; + return NGX_CONF_OK; } @@ -296,3 +427,29 @@ ngx_imap_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } + + +static char * +ngx_imap_core_capability(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + char *p = conf; + + ngx_str_t *c, *value; + ngx_uint_t i; + ngx_array_t *a; + + a = (ngx_array_t *) (p + cmd->offset); + + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + c = ngx_array_push(a); + if (c == NULL) { + return NGX_CONF_ERROR; + } + + *c = value[i]; + } + + return NGX_CONF_OK; +} diff --git a/src/imap/ngx_imap_handler.c b/src/imap/ngx_imap_handler.c index 680bf20d0..6464d4a85 100644 --- a/src/imap/ngx_imap_handler.c +++ b/src/imap/ngx_imap_handler.c @@ -8,20 +8,15 @@ #include <ngx_core.h> #include <ngx_event.h> #include <ngx_imap.h> -#include <nginx.h> static void ngx_imap_init_session(ngx_event_t *rev); - -static void ngx_pop3_auth_state(ngx_event_t *rev); -static ngx_int_t ngx_pop3_read_command(ngx_imap_session_t *s); - -static void ngx_imap_auth_state(ngx_event_t *rev); +static ngx_int_t ngx_imap_read_command(ngx_imap_session_t *s); static ngx_str_t greetings[] = { - ngx_string("+OK " NGINX_VER " ready" CRLF), - ngx_string("* OK " NGINX_VER " ready" CRLF) + ngx_string("+OK POP3 ready" CRLF), + ngx_string("* OK IMAP ready" CRLF) }; static ngx_str_t internal_server_errors[] = { @@ -32,6 +27,11 @@ static ngx_str_t internal_server_errors[] = { static u_char pop3_ok[] = "+OK" CRLF; static u_char pop3_invalid_command[] = "-ERR invalid command" CRLF; +static u_char imap_ok[] = "OK" CRLF; +static u_char imap_next[] = "+ OK" CRLF; +static u_char imap_bye[] = "* BYE" CRLF; +static u_char imap_invalid_command[] = "BAD invalid command" CRLF; + void ngx_imap_init_connection(ngx_connection_t *c) @@ -87,7 +87,7 @@ ngx_imap_init_session(ngx_event_t *rev) s = ngx_pcalloc(c->pool, sizeof(ngx_imap_session_t)); if (s == NULL) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } @@ -96,7 +96,7 @@ ngx_imap_init_session(ngx_event_t *rev) s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_imap_max_module); if (s->ctx == NULL) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } @@ -105,7 +105,7 @@ ngx_imap_init_session(ngx_event_t *rev) s->srv_conf = ctx->srv_conf; if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } @@ -115,16 +115,18 @@ ngx_imap_init_session(ngx_event_t *rev) if (cscf->protocol == NGX_IMAP_POP3_PROTOCOL) { size = 128; + s->imap_state = ngx_pop3_start; c->read->handler = ngx_pop3_auth_state; } else { size = cscf->imap_client_buffer_size; + s->imap_state = ngx_imap_start; c->read->handler = ngx_imap_auth_state; } s->buffer = ngx_create_temp_buf(c->pool, size); if (s->buffer == NULL) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } @@ -132,27 +134,194 @@ ngx_imap_init_session(ngx_event_t *rev) } -static void +void ngx_imap_auth_state(ngx_event_t *rev) { - ngx_connection_t *c; + u_char *text, *last, *out, *p; + ssize_t size, text_len, last_len; + ngx_str_t *arg; + ngx_int_t rc; + ngx_uint_t quit, tag; + ngx_connection_t *c; + ngx_imap_session_t *s; + ngx_imap_core_srv_conf_t *cscf; c = rev->data; + s = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth state"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_imap_close_connection(c); + return; + } + + rc = ngx_imap_read_command(s); + + ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth: %i", rc); + + if (rc == NGX_AGAIN || rc == NGX_ERROR) { + return; + } + + quit = 0; + tag = 1; + + text = NULL; + text_len = 0; + + last = imap_ok; + last_len = sizeof(imap_ok) - 1; + + if (rc == NGX_OK) { + + ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth command: %i", + s->command); + + switch (s->command) { + + case NGX_IMAP_LOGIN: + if (s->args.nelts == 2) { + + arg = s->args.elts; + + s->login.len = arg[0].len; + s->login.data = ngx_palloc(c->pool, s->login.len); + if (s->login.data == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + ngx_memcpy(s->login.data, arg[0].data, s->login.len); + + s->passwd.len = arg[1].len; + s->passwd.data = ngx_palloc(c->pool, s->passwd.len); + if (s->passwd.data == NULL) { + ngx_imap_session_internal_server_error(s); + return; + } + + ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len); + + ngx_log_debug2(NGX_LOG_DEBUG_IMAP, c->log, 0, + "imap login:\"%V\" passwd:\"%V\"", + &s->login, &s->passwd); + + s->args.nelts = 0; + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; + + if (rev->timer_set) { + ngx_del_timer(rev); + } + + s->login_attempt++; + + ngx_imap_auth_http_init(s); + + return; + + } else { + rc = NGX_IMAP_PARSE_INVALID_COMMAND; + } + + break; + + case NGX_IMAP_CAPABILITY: + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + text = cscf->imap_capability->pos; + text_len = cscf->imap_capability->last - cscf->imap_capability->pos; + break; + + case NGX_IMAP_LOGOUT: + text = imap_bye; + text_len = sizeof(imap_bye) - 1; + quit = 1; + break; + + case NGX_IMAP_NOOP: + break; + + default: + rc = NGX_IMAP_PARSE_INVALID_COMMAND; + break; + } - ngx_imap_close_connection(c); + } else if (rc == NGX_IMAP_NEXT) { + last = imap_next; + last_len = sizeof(imap_next) - 1; + tag = 0; + } + + if (rc == NGX_IMAP_PARSE_INVALID_COMMAND) { + last = imap_invalid_command; + last_len = sizeof(imap_invalid_command) - 1; + } + + if (tag) { + if (s->out.len < text_len + s->tag.len + last_len) { + + s->out.len = text_len + s->tag.len + last_len; + s->out.data = ngx_palloc(c->pool, s->out.len); + if (s->out.data == NULL) { + ngx_imap_close_connection(c); + return; + } + } + + out = s->out.data; + p = out; + + if (text) { + p = ngx_cpymem(p, text, text_len); + } + p = ngx_cpymem(p, s->tag.data, s->tag.len); + ngx_memcpy(p, last, last_len); + + size = text_len + s->tag.len + last_len; + + } else { + out = last; + size = last_len; + } + + if (ngx_send(c, out, size) < size) { + /* + * we treat the incomplete sending as NGX_ERROR + * because it is very strange here + */ + ngx_imap_close_connection(c); + return; + } + + if (rc == NGX_IMAP_NEXT) { + return; + } + + if (quit) { + ngx_imap_close_connection(c); + return; + } + + s->args.nelts = 0; + s->buffer->pos = s->buffer->start; + s->buffer->last = s->buffer->start; + s->tag.len = 0; } -static void +void ngx_pop3_auth_state(ngx_event_t *rev) { - u_char *text; - ssize_t size; - ngx_int_t rc; - ngx_uint_t quit; - ngx_str_t *arg; - ngx_connection_t *c; - ngx_imap_session_t *s; + u_char *text; + ssize_t size; + ngx_int_t rc; + ngx_uint_t quit; + ngx_str_t *arg; + ngx_connection_t *c; + ngx_imap_session_t *s; + ngx_imap_core_srv_conf_t *cscf; c = rev->data; s = c->data; @@ -165,7 +334,7 @@ ngx_pop3_auth_state(ngx_event_t *rev) return; } - rc = ngx_pop3_read_command(s); + rc = ngx_imap_read_command(s); if (rc == NGX_AGAIN || rc == NGX_ERROR) { return; @@ -188,16 +357,16 @@ ngx_pop3_auth_state(ngx_event_t *rev) arg = s->args.elts; s->login.len = arg[0].len; - s->login.data = ngx_palloc(c->pool, s->login.len + 1); + s->login.data = ngx_palloc(c->pool, s->login.len); if (s->login.data == NULL) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } - ngx_cpystrn(s->login.data, arg[0].data, s->login.len + 1); + ngx_memcpy(s->login.data, arg[0].data, s->login.len); ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, - "pop3 login: \"%s\"", s->login.data); + "pop3 login: \"%V\"", &s->login); } else { rc = NGX_IMAP_PARSE_INVALID_COMMAND; @@ -205,10 +374,19 @@ ngx_pop3_auth_state(ngx_event_t *rev) break; + case NGX_POP3_CAPA: + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + text = cscf->pop3_capability->pos; + size = cscf->pop3_capability->last - cscf->pop3_capability->pos; + break; + case NGX_POP3_QUIT: quit = 1; break; + case NGX_POP3_NOOP: + break; + default: s->imap_state = ngx_pop3_start; rc = NGX_IMAP_PARSE_INVALID_COMMAND; @@ -227,20 +405,25 @@ ngx_pop3_auth_state(ngx_event_t *rev) arg = s->args.elts; s->passwd.len = arg[0].len; - s->passwd.data = ngx_palloc(c->pool, s->passwd.len + 1); + s->passwd.data = ngx_palloc(c->pool, s->passwd.len); if (s->passwd.data == NULL) { - ngx_imap_close_connection(c); + ngx_imap_session_internal_server_error(s); return; } - ngx_cpystrn(s->passwd.data, arg[0].data, s->passwd.len + 1); + ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len); ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, - "pop3 passwd: \"%s\"", s->passwd.data); + "pop3 passwd: \"%V\"", &s->passwd); + s->args.nelts = 0; s->buffer->pos = s->buffer->start; s->buffer->last = s->buffer->start; + if (rev->timer_set) { + ngx_del_timer(rev); + } + ngx_imap_auth_http_init(s); return; @@ -251,10 +434,19 @@ ngx_pop3_auth_state(ngx_event_t *rev) break; + case NGX_POP3_CAPA: + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + text = cscf->pop3_capability->pos; + size = cscf->pop3_capability->last - cscf->pop3_capability->pos; + break; + case NGX_POP3_QUIT: quit = 1; break; + case NGX_POP3_NOOP: + break; + default: s->imap_state = ngx_pop3_start; rc = NGX_IMAP_PARSE_INVALID_COMMAND; @@ -262,6 +454,10 @@ ngx_pop3_auth_state(ngx_event_t *rev) } break; + + /* suppress warinings */ + case ngx_pop3_passwd: + break; } } @@ -291,7 +487,7 @@ ngx_pop3_auth_state(ngx_event_t *rev) static ngx_int_t -ngx_pop3_read_command(ngx_imap_session_t *s) +ngx_imap_read_command(ngx_imap_session_t *s) { ssize_t n; ngx_int_t rc; @@ -310,16 +506,23 @@ ngx_pop3_read_command(ngx_imap_session_t *s) if (n == NGX_AGAIN) { if (ngx_handle_read_event(s->connection->read, 0) == NGX_ERROR) { - ngx_imap_close_connection(s->connection); + ngx_imap_session_internal_server_error(s); return NGX_ERROR; } return NGX_AGAIN; } - rc = ngx_pop3_parse_command(s); + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + rc = ngx_pop3_parse_command(s); + } else { + rc = ngx_imap_parse_command(s); + } - if (rc == NGX_AGAIN || rc == NGX_IMAP_PARSE_INVALID_COMMAND) { + if (rc == NGX_AGAIN + || rc == NGX_IMAP_NEXT + || rc == NGX_IMAP_PARSE_INVALID_COMMAND) + { return rc; } @@ -332,20 +535,6 @@ ngx_pop3_read_command(ngx_imap_session_t *s) } -#if 0 - -void -ngx_imap_close_session(ngx_imap_session_t *s) -{ - ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, - "close imap session"); - - ngx_imap_close_connection(s->connection); -} - -#endif - - void ngx_imap_session_internal_server_error(ngx_imap_session_t *s) { diff --git a/src/imap/ngx_imap_parse.c b/src/imap/ngx_imap_parse.c index 6c1c3e95a..e3923e892 100644 --- a/src/imap/ngx_imap_parse.c +++ b/src/imap/ngx_imap_parse.c @@ -10,64 +10,383 @@ #include <ngx_imap.h> -ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) +ngx_int_t ngx_imap_parse_command(ngx_imap_session_t *s) { u_char ch, *p, *c; ngx_str_t *arg; enum { sw_start = 0, + sw_spaces_before_command, + sw_command, sw_spaces_before_argument, sw_argument, - sw_almost_done, - sw_done + sw_literal, + sw_start_literal_argument, + sw_literal_argument, + sw_end_literal_argument, + sw_almost_done } state; state = s->state; - p = s->buffer->pos; - while (p < s->buffer->last && state < sw_done) { - ch = *p++; + for (p = s->buffer->pos; p < s->buffer->last; p++) { + ch = *p; switch (state) { - /* POP3 command */ + /* IMAP tag */ + case sw_start: + switch (ch) { + case ' ': + s->tag.len = p - s->buffer->start + 1; + s->tag.data = s->buffer->start; + state = sw_spaces_before_command; + break; + case CR: + s->state = sw_start; + return NGX_IMAP_PARSE_INVALID_COMMAND; + case LF: + s->state = sw_start; + return NGX_IMAP_PARSE_INVALID_COMMAND; + } + break; + + case sw_spaces_before_command: + switch (ch) { + case ' ': + break; + case CR: + s->state = sw_start; + return NGX_IMAP_PARSE_INVALID_COMMAND; + case LF: + s->state = sw_start; + return NGX_IMAP_PARSE_INVALID_COMMAND; + 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; + + 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; + + default: + goto invalid; + } + + 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; + s->arg_end = p; + break; + case LF: + s->arg_end = p; + 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: + 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; + 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; + } + 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; + } + goto invalid; + + case sw_start_literal_argument: + switch (ch) { + case CR: + break; + case LF: + s->buffer->pos = p + 1; + s->arg_start = p + 1; + s->state = sw_literal_argument; + return NGX_IMAP_NEXT; + } + goto invalid; + + case sw_literal_argument: + if (--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: + goto invalid; + } + 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; + + if (s->arg_start) { + arg = ngx_array_push(&s->args); + if (arg == NULL) { + return NGX_ERROR; + } + arg->len = s->arg_end - s->arg_start; + arg->data = s->arg_start; + s->arg_start = NULL; + s->cmd_start = NULL; + s->quoted = 0; + s->literal_len = 0; + } + + s->state = sw_start; + + return NGX_OK; + +invalid: + + s->state = sw_start; + s->quoted = 0; + s->literal_len = 0; + + return NGX_IMAP_PARSE_INVALID_COMMAND; +} + + +ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) +{ + u_char ch, *p, *c, c0, c1, c2, c3; + ngx_str_t *arg; + enum { + sw_start = 0, + 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: if (ch == ' ' || ch == CR || ch == LF) { c = s->buffer->start; - if (p - 1 - c == 4) { + 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 (c[0] == 'U' && c[1] == 'S' - && c[2] == 'E' && c[3] == 'R') + if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R') { s->command = NGX_POP3_USER; - } else if (c[0] == 'P' && c[1] == 'A' - && c[2] == 'S' && c[3] == 'S') + } else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S') { s->command = NGX_POP3_PASS; - } else if (c[0] == 'Q' && c[1] == 'U' - && c[2] == 'I' && c[3] == 'T') + } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T') { s->command = NGX_POP3_QUIT; -#if 0 - } else if (c[0] == 'N' && c[1] == 'O' - && c[2] == 'O' && c[3] == 'P') + } else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A') + { + s->command = NGX_POP3_CAPA; + + } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P') { s->command = NGX_POP3_NOOP; -#endif } else { - s->state = sw_start; - return NGX_IMAP_PARSE_INVALID_COMMAND; + goto invalid; } } else { - s->state = sw_start; - return NGX_IMAP_PARSE_INVALID_COMMAND; + goto invalid; } switch (ch) { @@ -78,45 +397,38 @@ ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) state = sw_almost_done; break; case LF: - state = sw_done; - break; + goto done; } break; } - if (ch < 'A' || ch > 'Z') { - s->state = sw_start; - return NGX_IMAP_PARSE_INVALID_COMMAND; + if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) { + goto invalid; } break; - /* the spaces before the argument */ case sw_spaces_before_argument: switch (ch) { case ' ': break; case CR: state = sw_almost_done; - s->arg_end = p - 1; + s->arg_end = p; break; case LF: - state = sw_done; - s->arg_end = p - 1; - break; + s->arg_end = p; + goto done; default: - if (s->args.nelts > 2) { - s->state = sw_start; - return NGX_IMAP_PARSE_INVALID_COMMAND; + if (s->args.nelts <= 2) { + state = sw_argument; + s->arg_start = p; + break; } - - state = sw_argument; - s->arg_start = p - 1; - break; + goto invalid; } break; - /* the argument */ case sw_argument: switch (ch) { case ' ': @@ -126,7 +438,7 @@ ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) if (arg == NULL) { return NGX_ERROR; } - arg->len = p - 1 - s->arg_start; + arg->len = p - s->arg_start; arg->data = s->arg_start; s->arg_start = NULL; @@ -138,8 +450,7 @@ ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) state = sw_almost_done; break; case LF: - state = sw_done; - break; + goto done; } break; @@ -148,42 +459,42 @@ ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s) } break; - /* end of request line */ case sw_almost_done: switch (ch) { case LF: - state = sw_done; - break; + goto done; default: - s->state = sw_start; - return NGX_IMAP_PARSE_INVALID_COMMAND; + goto invalid; } - break; - - /* suppress warning */ - case sw_done: - break; } } s->buffer->pos = p; + s->state = state; - if (state == sw_done) { - if (s->arg_start) { - arg = ngx_array_push(&s->args); - if (arg == NULL) { - return NGX_ERROR; - } - arg->len = s->arg_end - s->arg_start; - arg->data = s->arg_start; - s->arg_start = NULL; - } + return NGX_AGAIN; + +done: - s->state = sw_start; - return NGX_OK; + s->buffer->pos = p + 1; - } else { - s->state = state; - return NGX_AGAIN; + if (s->arg_start) { + arg = ngx_array_push(&s->args); + if (arg == NULL) { + return NGX_ERROR; + } + arg->len = s->arg_end - s->arg_start; + arg->data = s->arg_start; + s->arg_start = NULL; } + + s->state = sw_start; + + return NGX_OK; + +invalid: + + s->state = sw_start; + + return NGX_IMAP_PARSE_INVALID_COMMAND; } diff --git a/src/imap/ngx_imap_proxy_module.c b/src/imap/ngx_imap_proxy_module.c index b3bedae58..5177e289f 100644 --- a/src/imap/ngx_imap_proxy_module.c +++ b/src/imap/ngx_imap_proxy_module.c @@ -17,16 +17,23 @@ typedef struct { static void ngx_imap_proxy_block_read(ngx_event_t *rev); -static void ngx_imap_proxy_auth_handler(ngx_event_t *rev); +static void ngx_imap_proxy_imap_handler(ngx_event_t *rev); +static void ngx_imap_proxy_pop3_handler(ngx_event_t *rev); static void ngx_imap_proxy_dummy_handler(ngx_event_t *ev); -static ngx_int_t ngx_imap_proxy_read_response(ngx_imap_session_t *s); +static ngx_int_t ngx_imap_proxy_read_response(ngx_imap_session_t *s, + ngx_uint_t what); static void ngx_imap_proxy_handler(ngx_event_t *ev); +static void ngx_imap_proxy_internal_server_error(ngx_imap_session_t *s); static void ngx_imap_proxy_close_session(ngx_imap_session_t *s); static void *ngx_imap_proxy_create_conf(ngx_conf_t *cf); static char *ngx_imap_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child); +#define NGX_IMAP_WAIT_OK 0 +#define NGX_IMAP_WAIT_NEXT 1 + + static ngx_command_t ngx_imap_proxy_commands[] = { { ngx_string("proxy"), NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_FLAG, @@ -61,12 +68,13 @@ ngx_module_t ngx_imap_proxy_module = { void ngx_imap_proxy_init(ngx_imap_session_t *s, ngx_peers_t *peers) { - ngx_int_t rc; - ngx_imap_proxy_ctx_t *p; + ngx_int_t rc; + ngx_imap_proxy_ctx_t *p; + ngx_imap_core_srv_conf_t *cscf; p = ngx_pcalloc(s->connection->pool, sizeof(ngx_imap_proxy_ctx_t)); if (p == NULL) { - ngx_imap_close_connection(s->connection); + ngx_imap_session_internal_server_error(s); return; } @@ -79,16 +87,27 @@ ngx_imap_proxy_init(ngx_imap_session_t *s, ngx_peers_t *peers) rc = ngx_event_connect_peer(&p->upstream); if (rc == NGX_ERROR) { - ngx_imap_proxy_close_session(s); + ngx_imap_session_internal_server_error(s); return; } + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + ngx_add_timer(p->upstream.connection->read, cscf->timeout); + p->upstream.connection->data = s; p->upstream.connection->pool = s->connection->pool; s->connection->read->handler = ngx_imap_proxy_block_read; - p->upstream.connection->read->handler = ngx_imap_proxy_auth_handler; p->upstream.connection->write->handler = ngx_imap_proxy_dummy_handler; + + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + p->upstream.connection->read->handler = ngx_imap_proxy_pop3_handler; + s->imap_state = ngx_pop3_start; + + } else { + p->upstream.connection->read->handler = ngx_imap_proxy_imap_handler; + s->imap_state = ngx_imap_start; + } } @@ -110,106 +129,242 @@ ngx_imap_proxy_block_read(ngx_event_t *rev) static void -ngx_imap_proxy_auth_handler(ngx_event_t *rev) +ngx_imap_proxy_imap_handler(ngx_event_t *rev) { - u_char *p; - ngx_int_t rc; - ngx_str_t line; - ngx_connection_t *c; - ngx_imap_session_t *s; + u_char *p; + ngx_int_t rc; + ngx_str_t line; + ngx_connection_t *c; + ngx_imap_session_t *s; + ngx_imap_core_srv_conf_t *cscf; - ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy auth handler"); + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, + "imap proxy imap auth handler"); c = rev->data; s = c->data; if (rev->timedout) { - ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); - ngx_imap_proxy_close_session(s); + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "upstream timed out"); + ngx_imap_proxy_internal_server_error(s); return; } if (s->proxy->buffer == NULL) { - s->proxy->buffer = ngx_create_temp_buf(c->pool, /* STUB */ 4096); + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + + s->proxy->buffer = ngx_create_temp_buf(c->pool, + cscf->proxy_buffer_size); if (s->proxy->buffer == NULL) { - ngx_imap_proxy_close_session(s); + ngx_imap_proxy_internal_server_error(s); return; } } - rc = ngx_imap_proxy_read_response(s); + rc = ngx_imap_proxy_read_response(s, s->imap_state == ngx_imap_start ? + NGX_IMAP_WAIT_OK : NGX_IMAP_WAIT_NEXT); if (rc == NGX_AGAIN) { return; } if (rc == NGX_ERROR) { - /* TODO: ngx_imap_proxy_finalize_session(s, NGX_IMAP_INTERNAL_ERROR) */ - ngx_imap_proxy_close_session(s); + ngx_imap_proxy_internal_server_error(s); return; } - if (s->imap_state == ngx_pop3_start) { + switch (s->imap_state) { + + case ngx_imap_start: + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, + "imap proxy send login"); + line.len = s->tag.len + sizeof("LOGIN ") - 1 + + 1 + NGX_SIZE_T_LEN + 1 + 2; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_imap_proxy_internal_server_error(s); + return; + } + + line.len = ngx_sprintf(line.data, "%VLOGIN {%uz}" CRLF, + &s->tag, s->login.len) + - line.data; + + s->imap_state = ngx_imap_login; + break; + + case ngx_imap_login: ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send user"); - line.len = sizeof("USER ") + s->login.len - 1 + 2; + line.len = s->login.len + 1 + NGX_SIZE_T_LEN + 1 + 2; line.data = ngx_palloc(c->pool, line.len); if (line.data == NULL) { - ngx_imap_proxy_close_session(s); + ngx_imap_proxy_internal_server_error(s); return; } - p = ngx_cpymem(line.data, "USER ", sizeof("USER ") - 1); - p = ngx_cpymem(p, s->login.data, s->login.len); - *p++ = CR; *p = LF; + line.len = ngx_sprintf(line.data, "%V{%uz}" CRLF, + &s->login, s->passwd.len) + - line.data; + + s->imap_state = ngx_imap_user; + break; + + case ngx_imap_user: + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, + "imap proxy send passwd"); - if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) { - /* - * we treat the incomplete sending as NGX_ERROR - * because it is very strange here - */ - ngx_imap_close_connection(c); + line.len = s->passwd.len + 2; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_imap_proxy_internal_server_error(s); return; } - s->imap_state = ngx_pop3_user; + p = ngx_cpymem(line.data, s->passwd.data, s->passwd.len); + *p++ = CR; *p = LF; + + s->imap_state = ngx_imap_passwd; + break; - s->proxy->buffer->pos = s->proxy->buffer->start; - s->proxy->buffer->last = s->proxy->buffer->start; + default: +#if (NGX_SUPPRESS_WARN) + line.len = 0; + line.data = NULL; +#endif + break; + } + if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) { + /* + * we treat the incomplete sending as NGX_ERROR + * because it is very strange here + */ + ngx_imap_proxy_internal_server_error(s); return; } - ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send pass"); + s->proxy->buffer->pos = s->proxy->buffer->start; + s->proxy->buffer->last = s->proxy->buffer->start; - line.len = sizeof("PASS ") + s->passwd.len - 1 + 2; - line.data = ngx_palloc(c->pool, line.len); - if (line.data == NULL) { - ngx_imap_proxy_close_session(s); + if (s->imap_state == ngx_imap_passwd) { + s->connection->read->handler = ngx_imap_proxy_handler; + s->connection->write->handler = ngx_imap_proxy_handler; + rev->handler = ngx_imap_proxy_handler; + c->write->handler = ngx_imap_proxy_handler; + } +} + + +static void +ngx_imap_proxy_pop3_handler(ngx_event_t *rev) +{ + u_char *p; + ngx_int_t rc; + ngx_str_t line; + ngx_connection_t *c; + ngx_imap_session_t *s; + ngx_imap_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, + "imap proxy pop3 auth handler"); + + c = rev->data; + s = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "upstream timed out"); + ngx_imap_proxy_internal_server_error(s); + return; + } + + if (s->proxy->buffer == NULL) { + cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module); + + s->proxy->buffer = ngx_create_temp_buf(c->pool, + cscf->proxy_buffer_size); + if (s->proxy->buffer == NULL) { + ngx_imap_proxy_internal_server_error(s); + return; + } + } + + rc = ngx_imap_proxy_read_response(s, NGX_IMAP_WAIT_OK); + + if (rc == NGX_AGAIN) { return; } - p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1); - p = ngx_cpymem(p, s->passwd.data, s->passwd.len); - *p++ = CR; *p = LF; + if (rc == NGX_ERROR) { + ngx_imap_proxy_internal_server_error(s); + return; + } + + switch (s->imap_state) { + + case ngx_pop3_start: + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send user"); + + line.len = sizeof("USER ") - 1 + s->login.len + 2; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_imap_proxy_internal_server_error(s); + return; + } + + p = ngx_cpymem(line.data, "USER ", sizeof("USER ") - 1); + p = ngx_cpymem(p, s->login.data, s->login.len); + *p++ = CR; *p = LF; + + s->imap_state = ngx_pop3_user; + break; + + case ngx_pop3_user: + ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send pass"); + + line.len = sizeof("PASS ") - 1 + s->passwd.len + 2; + line.data = ngx_palloc(c->pool, line.len); + if (line.data == NULL) { + ngx_imap_proxy_internal_server_error(s); + return; + } + + p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1); + p = ngx_cpymem(p, s->passwd.data, s->passwd.len); + *p++ = CR; *p = LF; + + s->imap_state = ngx_pop3_passwd; + break; + + default: +#if (NGX_SUPPRESS_WARN) + line.len = 0; + line.data = NULL; +#endif + break; + } if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) { /* * we treat the incomplete sending as NGX_ERROR * because it is very strange here */ - ngx_imap_close_connection(c); + ngx_imap_proxy_internal_server_error(s); return; } s->proxy->buffer->pos = s->proxy->buffer->start; s->proxy->buffer->last = s->proxy->buffer->start; - s->connection->read->handler = ngx_imap_proxy_handler; - s->connection->write->handler = ngx_imap_proxy_handler; - rev->handler = ngx_imap_proxy_handler; - c->write->handler = ngx_imap_proxy_handler; + if (s->imap_state == ngx_pop3_passwd) { + s->connection->read->handler = ngx_imap_proxy_handler; + s->connection->write->handler = ngx_imap_proxy_handler; + rev->handler = ngx_imap_proxy_handler; + c->write->handler = ngx_imap_proxy_handler; + } } @@ -221,7 +376,7 @@ ngx_imap_proxy_dummy_handler(ngx_event_t *ev) static ngx_int_t -ngx_imap_proxy_read_response(ngx_imap_session_t *s) +ngx_imap_proxy_read_response(ngx_imap_session_t *s, ngx_uint_t what) { u_char *p; ssize_t n; @@ -259,17 +414,31 @@ ngx_imap_proxy_read_response(ngx_imap_session_t *s) p = b->pos; - if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') { - return NGX_OK; - } + if (s->protocol == NGX_IMAP_POP3_PROTOCOL) { + if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') { + return NGX_OK; + } - if (p[0] == '-' && p[1] == 'E' && p[2] == 'R' && p[3] == 'R') { - return NGX_IMAP_PROXY_ERROR; + if (p[0] == '-' && p[1] == 'E' && p[2] == 'R' && p[3] == 'R') { + return NGX_IMAP_PROXY_ERROR; + } + + } else { + if (what == NGX_IMAP_WAIT_OK) { + if (p[0] == '*' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') { + return NGX_OK; + } + + } else { + if (p[0] == '+' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') { + return NGX_OK; + } + } } *(b->last - 2) = '\0'; ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, - "upstream sent invalid greeting line: \"%s\"", p); + "upstream sent invalid response: \"%s\"", p); return NGX_IMAP_PROXY_INVALID; } @@ -397,6 +566,21 @@ ngx_imap_proxy_handler(ngx_event_t *ev) static void +ngx_imap_proxy_internal_server_error(ngx_imap_session_t *s) +{ + if (s->proxy->upstream.connection) { + ngx_log_debug1(NGX_LOG_DEBUG_IMAP, s->connection->log, 0, + "close imap proxy connection: %d", + s->proxy->upstream.connection->fd); + + ngx_close_connection(s->proxy->upstream.connection); + } + + ngx_imap_session_internal_server_error(s); +} + + +static void ngx_imap_proxy_close_session(ngx_imap_session_t *s) { if (s->proxy->upstream.connection) { |