changeset 336:1c519aff5c0c NGINX_0_6_12

nginx 0.6.12 *) Change: mail proxy was split on three modules: pop3, imap and smtp. *) Feature: the --without-mail_pop3_module, --without-mail_imap_module, and --without-mail_smtp_module configuration parameters. *) Feature: the "smtp_greeting_delay" and "smtp_client_buffer" directives of the ngx_mail_smtp_module. *) Bugfix: the trailing wildcards did not work; bug appeared in 0.6.9. *) Bugfix: nginx could not start on Solaris if the shared PCRE library located in non-standard place was used. *) Bugfix: the "proxy_hide_header" and "fastcgi_hide_header" directives did not hide response header lines whose name was longer than 32 characters. Thanks to Manlio Perillo.
author Igor Sysoev <http://sysoev.ru>
date Fri, 21 Sep 2007 00:00:00 +0400
parents 9a32ae248b7a
children b3475df8722a
files CHANGES CHANGES.ru auto/lib/pcre/conf auto/modules auto/options auto/os/solaris auto/sources src/core/nginx.h src/core/ngx_hash.c src/event/ngx_event_connect.c src/http/modules/ngx_http_empty_gif_module.c src/http/modules/ngx_http_fastcgi_module.c src/http/modules/ngx_http_proxy_module.c src/http/modules/perl/nginx.pm src/http/ngx_http.c src/http/ngx_http_core_module.c src/http/ngx_http_core_module.h src/http/ngx_http_request.c src/http/ngx_http_upstream.c src/http/ngx_http_upstream.h src/http/ngx_http_upstream_round_robin.c src/mail/ngx_mail.c src/mail/ngx_mail.h src/mail/ngx_mail_auth_http_module.c src/mail/ngx_mail_core_module.c src/mail/ngx_mail_handler.c src/mail/ngx_mail_imap_handler.c src/mail/ngx_mail_imap_module.c src/mail/ngx_mail_imap_module.h src/mail/ngx_mail_parse.c src/mail/ngx_mail_pop3_handler.c src/mail/ngx_mail_pop3_module.c src/mail/ngx_mail_pop3_module.h src/mail/ngx_mail_proxy_module.c src/mail/ngx_mail_smtp_handler.c src/mail/ngx_mail_smtp_module.c src/mail/ngx_mail_smtp_module.h src/mail/ngx_mail_ssl_module.c
diffstat 38 files changed, 3144 insertions(+), 2388 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES
+++ b/CHANGES
@@ -1,4 +1,40 @@
 
+Changes with nginx 0.6.12                                        21 Sep 2007
+
+    *) Change: mail proxy was split on three modules: pop3, imap and smtp.
+
+    *) Feature: the --without-mail_pop3_module, --without-mail_imap_module, 
+       and --without-mail_smtp_module configuration parameters.
+
+    *) Feature: the "smtp_greeting_delay" and "smtp_client_buffer" 
+       directives of the ngx_mail_smtp_module.
+
+    *) Bugfix: the trailing wildcards did not work; bug appeared in 0.6.9.
+
+    *) Bugfix: nginx could not start on Solaris if the shared PCRE library 
+       located in non-standard place was used.
+
+    *) Bugfix: the "proxy_hide_header" and "fastcgi_hide_header" directives 
+       did not hide response header lines whose name was longer than 32 
+       characters.
+       Thanks to Manlio Perillo.
+
+
+Changes with nginx 0.6.11                                        11 Sep 2007
+
+    *) Bugfix: active connection counter always increased if mail proxy was 
+       used.
+
+    *) Bugfix: if backend returned response header only using non-buffered 
+       proxy, then nginx closed backend connection on timeout.
+
+    *) Bugfix: nginx did not support several "Connection" request header 
+       lines.
+
+    *) Bugfix: if the "max_fails" was set for upstream server, then after 
+       first failure server weight was always one; bug appeared in 0.6.6.
+
+
 Changes with nginx 0.6.10                                        03 Sep 2007
 
     *) Feature: the "open_file_cache", "open_file_cache_retest", and 
--- a/CHANGES.ru
+++ b/CHANGES.ru
@@ -1,4 +1,43 @@
 
+Изменения в nginx 0.6.12                                          21.09.2007
+
+    *) Изменение: почтовый прокси-сервер разделён на три модуля: pop3, imap 
+       и smtp.
+
+    *) Добавление: параметры конфигурации --without-mail_pop3_module, 
+       --without-mail_imap_module и --without-mail_smtp_module.
+
+    *) Добавление: директивы smtp_greeting_delay и smtp_client_buffer 
+       модуля ngx_mail_smtp_module.
+
+    *) Исправление: wildcard в конце имени сервера не работали; ошибка 
+       появилась в 0.6.9.
+
+    *) Исправление: при использовании разделяемой библиотеки PCRE, 
+       расположенной в нестандартном месте, nginx не запускался на Solaris.
+
+    *) Исправление: директивы proxy_hide_header и fastcgi_hide_header не 
+       скрывали строки заголовка ответа с именем больше 32 символов.
+       Спасибо Manlio Perillo.
+
+
+Изменения в nginx 0.6.11                                          11.09.2007
+
+    *) Исправление: счётчик активных соединений всегда рос при 
+       использовании почтового прокси-сервера.
+
+    *) Исправление: если бэкенд возвращал только заголовок ответа при 
+       небуферизированном проксировании, то nginx закрывал соединение с 
+       бэкендом по таймауту.
+
+    *) Исправление: nginx не поддерживал несколько строк "Connection" в 
+       заголовке запроса.
+
+    *) Исправление: если в сервере апстрима был задан max_fails, то после 
+       первой же неудачной попытки вес сервера навсегда становился равным 
+       одному; ошибка появилась в 0.6.6.
+
+
 Изменения в nginx 0.6.10                                          03.09.2007
 
     *) Добавление: директивы open_file_cache, open_file_cache_retest и 
--- a/auto/lib/pcre/conf
+++ b/auto/lib/pcre/conf
@@ -77,7 +77,6 @@ if [ $PCRE != NONE ]; then
             CORE_DEPS="$CORE_DEPS $PCRE/pcre.h"
             LINK_DEPS="$LINK_DEPS $PCRE/.libs/libpcre.a"
             CORE_LIBS="$CORE_LIBS $PCRE/.libs/libpcre.a"
-            #CORE_LIBS="$CORE_LIBS -L $PCRE/.libs -lpcre"
         ;;
 
     esac
@@ -111,7 +110,13 @@ else
             ngx_feature_run=no
             ngx_feature_incs="#include <pcre.h>"
             ngx_feature_path="/usr/local/include"
-            ngx_feature_libs="-L /usr/local/lib -lpcre"
+
+            if [ $NGX_RPATH = YES ]; then
+                ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lpcre"
+            else
+                ngx_feature_libs="-L/usr/local/lib -lpcre"
+            fi
+
             ngx_feature_test="pcre *re;
                               re = pcre_compile(NULL, 0, NULL, 0, NULL)"
             . auto/feature
@@ -160,7 +165,13 @@ else
                 ngx_feature_run=no
                 ngx_feature_incs="#include <pcre.h>"
                 ngx_feature_path="/usr/pkg/include"
-                ngx_feature_libs="-L /usr/pkg/lib -lpcre"
+
+                if [ $NGX_RPATH = YES ]; then
+                    ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lpcre"
+                else
+                    ngx_feature_libs="-L/usr/pkg/lib -lpcre"
+                fi
+
                 ngx_feature_test="pcre *re;
                                   re = pcre_compile(NULL, 0, NULL, 0, NULL)"
                 . auto/feature
@@ -185,7 +196,13 @@ else
                 ngx_feature_run=no
                 ngx_feature_incs="#include <pcre.h>"
                 ngx_feature_path="/opt/local/include"
-                ngx_feature_libs="-L/opt/local/lib -lpcre"
+
+                if [ $NGX_RPATH = YES ]; then
+                    ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lpcre"
+                else
+                    ngx_feature_libs="-L/opt/local/lib -lpcre"
+                fi
+
                 ngx_feature_test="pcre *re;
                                   re = pcre_compile(NULL, 0, NULL, 0, NULL)"
                 . auto/feature
--- a/auto/modules
+++ b/auto/modules
@@ -308,8 +308,6 @@ fi
 
 
 if [ $MAIL_SSL = YES ]; then
-    MAIL_DEPS="$MAIL_DEPS $MAIL_SSL_DEPS"
-    MAIL_SRCS="$MAIL_SRCS $MAIL_SSL_SRCS"
     have=NGX_MAIL_SSL . auto/have
     USE_OPENSSL=YES
 fi
@@ -341,6 +339,26 @@ if [ $MAIL = YES ]; then
 
     if [ $MAIL_SSL = YES ]; then
         modules="$modules $MAIL_SSL_MODULE"
+        MAIL_DEPS="$MAIL_DEPS $MAIL_SSL_DEPS"
+        MAIL_SRCS="$MAIL_SRCS $MAIL_SSL_SRCS"
+    fi
+
+    if [ $MAIL_POP3 = YES ]; then
+        modules="$modules $MAIL_POP3_MODULE"
+        MAIL_DEPS="$MAIL_DEPS $MAIL_POP3_DEPS"
+        MAIL_SRCS="$MAIL_SRCS $MAIL_POP3_SRCS"
+    fi
+
+    if [ $MAIL_IMAP = YES ]; then
+        modules="$modules $MAIL_IMAP_MODULE"
+        MAIL_DEPS="$MAIL_DEPS $MAIL_IMAP_DEPS"
+        MAIL_SRCS="$MAIL_SRCS $MAIL_IMAP_SRCS"
+    fi
+
+    if [ $MAIL_SMTP = YES ]; then
+        modules="$modules $MAIL_SMTP_MODULE"
+        MAIL_DEPS="$MAIL_DEPS $MAIL_SMTP_DEPS"
+        MAIL_SRCS="$MAIL_SRCS $MAIL_SMTP_SRCS"
     fi
 
     modules="$modules $MAIL_AUTH_HTTP_MODULE"
--- a/auto/options
+++ b/auto/options
@@ -23,6 +23,8 @@ NGX_CC_OPT=
 NGX_LD_OPT=
 CPU=NO
 
+NGX_RPATH=NO
+
 NGX_TEST_BUILD_DEVPOLL=NO
 NGX_TEST_BUILD_EVENTPORT=NO
 NGX_TEST_BUILD_EPOLL=NO
@@ -81,6 +83,9 @@ HTTP_STUB_STATUS=NO
 
 MAIL=NO
 MAIL_SSL=NO
+MAIL_POP3=YES
+MAIL_IMAP=YES
+MAIL_SMTP=YES
 
 NGX_ADDONS=
 
@@ -190,6 +195,9 @@ do
         # STUB
         --with-imap)                     MAIL=YES                   ;;
         --with-imap_ssl_module)          MAIL_SSL=YES               ;;
+        --without-mail_pop3_module)      MAIL_POP3=NO               ;;
+        --without-mail_imap_module)      MAIL_IMAP=NO               ;;
+        --without-mail_smtp_module)      MAIL_SMTP=NO               ;;
 
         --add-module=*)                  NGX_ADDONS="$NGX_ADDONS $value" ;;
 
--- a/auto/os/solaris
+++ b/auto/os/solaris
@@ -9,6 +9,8 @@ CORE_DEPS="$UNIX_DEPS $SOLARIS_DEPS"
 CORE_SRCS="$UNIX_SRCS $SOLARIS_SRCS "
 CORE_LIBS="$CORE_LIBS -lsocket -lnsl -lrt"
 
+NGX_RPATH=YES
+
 # Solaris's make does not support a blank line between target and rules
 ngx_spacer=
 
--- a/auto/sources
+++ b/auto/sources
@@ -425,6 +425,21 @@ MAIL_SRCS="src/mail/ngx_mail.c \
            src/mail/ngx_mail_handler.c \
            src/mail/ngx_mail_parse.c"
 
+MAIL_POP3_MODULE="ngx_mail_pop3_module"
+MAIL_POP3_DEPS="src/mail/ngx_mail_pop3_module.h"
+MAIL_POP3_SRCS="src/mail/ngx_mail_pop3_module.c \
+                src/mail/ngx_mail_pop3_handler.c"
+
+MAIL_IMAP_MODULE="ngx_mail_imap_module"
+MAIL_IMAP_DEPS="src/mail/ngx_mail_imap_module.h"
+MAIL_IMAP_SRCS="src/mail/ngx_mail_imap_module.c \
+                src/mail/ngx_mail_imap_handler.c"
+
+MAIL_SMTP_MODULE="ngx_mail_smtp_module"
+MAIL_SMTP_DEPS="src/mail/ngx_mail_smtp_module.h"
+MAIL_SMTP_SRCS="src/mail/ngx_mail_smtp_module.c \
+                src/mail/ngx_mail_smtp_handler.c"
+
 MAIL_SSL_MODULE="ngx_mail_ssl_module"
 MAIL_SSL_DEPS="src/mail/ngx_mail_ssl_module.h"
 MAIL_SSL_SRCS="src/mail/ngx_mail_ssl_module.c"
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -8,7 +8,7 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define NGINX_VERSION      "0.6.11"
+#define NGINX_VERSION      "0.6.12"
 #define NGINX_VER          "nginx/" NGINX_VERSION
 
 #define NGINX_VAR          "NGINX"
--- a/src/core/ngx_hash.c
+++ b/src/core/ngx_hash.c
@@ -896,7 +896,7 @@ wildcard:
             return NGX_ERROR;
         }
 
-        ngx_cpystrn(p, key->data, last - 1);
+        ngx_cpystrn(p, key->data, last);
 
         hwc = &ha->dns_wc_tail;
         keys = &ha->dns_wc_tail_hash[k];
--- a/src/event/ngx_event_connect.c
+++ b/src/event/ngx_event_connect.c
@@ -14,7 +14,7 @@ ngx_int_t
 ngx_event_connect_peer(ngx_peer_connection_t *pc)
 {
     int                rc;
-    u_int              event;
+    ngx_int_t          event;
     ngx_err_t          err;
     ngx_uint_t         level;
     ngx_socket_t       s;
--- a/src/http/modules/ngx_http_empty_gif_module.c
+++ b/src/http/modules/ngx_http_empty_gif_module.c
@@ -128,11 +128,7 @@ ngx_http_empty_gif_handler(ngx_http_requ
     if (r->method == NGX_HTTP_HEAD) {
         r->headers_out.status = NGX_HTTP_OK;
 
-        rc = ngx_http_send_header(r);
-
-        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
-            return rc;
-        }
+        return ngx_http_send_header(r);
     }
 
     b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
--- a/src/http/modules/ngx_http_fastcgi_module.c
+++ b/src/http/modules/ngx_http_fastcgi_module.c
@@ -1135,7 +1135,7 @@ ngx_http_fastcgi_process_header(ngx_http
 
                 } else {
                     for (i = 0; i < h->key.len; i++) {
-                        h->lowcase_key[i] = ngx_tolower(h->lowcase_key[i]);
+                        h->lowcase_key[i] = ngx_tolower(h->key.data[i]);
                     }
                 }
 
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -553,7 +553,7 @@ ngx_http_proxy_create_request(ngx_http_r
 
     } else {
         unparsed_uri = 0;
-        if (r->quoted_uri) {
+        if (r->quoted_uri || r->internal) {
             escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len,
                                         r->uri.len - loc_len, NGX_ESCAPE_URI);
         }
@@ -1141,7 +1141,7 @@ ngx_http_proxy_process_header(ngx_http_r
 
             } else {
                 for (i = 0; i < h->key.len; i++) {
-                    h->lowcase_key[i] = ngx_tolower(h->lowcase_key[i]);
+                    h->lowcase_key[i] = ngx_tolower(h->key.data[i]);
                 }
             }
 
--- a/src/http/modules/perl/nginx.pm
+++ b/src/http/modules/perl/nginx.pm
@@ -47,7 +47,7 @@ our @EXPORT = qw(
     HTTP_INSUFFICIENT_STORAGE
 );
 
-our $VERSION = '0.6.11';
+our $VERSION = '0.6.12';
 
 require XSLoader;
 XSLoader::load('nginx', $VERSION);
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -553,8 +553,8 @@ ngx_http_block(ngx_conf_t *cf, ngx_comma
 
                         if (in_addr[a].default_server) {
                             ngx_log_error(NGX_LOG_ERR, cf->log, 0,
-                                        "the duplicate default server in %V:%d",
-                                        &lscf[l].file_name, lscf[l].line);
+                                      "the duplicate default server in %s:%ui",
+                                       &lscf[l].file_name, lscf[l].line);
 
                             return NGX_CONF_ERROR;
                         }
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -2627,7 +2627,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx
     ls->family = AF_INET;
     ls->addr = u.addr.in_addr;
     ls->port = u.port;
-    ls->file_name = cf->conf_file->file.name;
+    ls->file_name = cf->conf_file->file.name.data;
     ls->line = cf->conf_file->line;
     ls->conf.backlog = NGX_LISTEN_BACKLOG;
     ls->conf.rcvbuf = -1;
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -38,8 +38,8 @@ typedef struct {
     in_port_t                  port;
     int                        family;
 
-    ngx_str_t                  file_name;
-    ngx_int_t                  line;
+    u_char                    *file_name;
+    ngx_uint_t                 line;
 
     ngx_http_listen_conf_t     conf;
 } ngx_http_listen_t;
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -21,6 +21,8 @@ static ngx_int_t ngx_http_process_header
     ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_process_unique_header_line(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_process_cookie(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
 
@@ -71,7 +73,7 @@ ngx_http_header_t  ngx_http_headers_in[]
                  ngx_http_process_unique_header_line },
 
     { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
-                 ngx_http_process_unique_header_line },
+                 ngx_http_process_connection },
 
     { ngx_string("If-Modified-Since"),
                  offsetof(ngx_http_headers_in_t, if_modified_since),
@@ -1199,6 +1201,21 @@ ngx_http_process_unique_header_line(ngx_
 
 
 static ngx_int_t
+ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    if (ngx_strstr(h->value.data, "close")) {
+        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
+
+    } else if (ngx_strstr(h->value.data, "keep-alive")) {
+        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
 ngx_http_process_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
     ngx_uint_t offset)
 {
@@ -1294,7 +1311,7 @@ ngx_http_process_request_header(ngx_http
         return NGX_ERROR;
     }
 
-    if (r->method & (NGX_HTTP_TRACE)) {
+    if (r->method & NGX_HTTP_TRACE) {
         ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                       "client sent TRACE method");
         ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED);
@@ -1317,26 +1334,11 @@ ngx_http_process_request_header(ngx_http
         return NGX_ERROR;
     }
 
-    if (r->headers_in.connection) {
-        if (r->headers_in.connection->value.len == 5
-            && ngx_strcasecmp(r->headers_in.connection->value.data,
-                              (u_char *) "close")
-               == 0)
-        {
-            r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
-
-        } else if (r->headers_in.connection->value.len == 10
-                   && ngx_strcasecmp(r->headers_in.connection->value.data,
-                                     (u_char *) "keep-alive")
-                      == 0)
-        {
-            r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
-
-            if (r->headers_in.keep_alive) {
-                r->headers_in.keep_alive_n =
-                                ngx_atotm(r->headers_in.keep_alive->value.data,
-                                          r->headers_in.keep_alive->value.len);
-            }
+    if (r->headers_in.connection_type == NGX_HTTP_CONNECTION_KEEP_ALIVE) {
+        if (r->headers_in.keep_alive) {
+            r->headers_in.keep_alive_n =
+                            ngx_atotm(r->headers_in.keep_alive->value.data,
+                                      r->headers_in.keep_alive->value.len);
         }
     }
 
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -1290,6 +1290,7 @@ ngx_http_upstream_test_connect(ngx_conne
 
     if (ngx_event_flags & NGX_USE_KQUEUE_EVENT)  {
         if (c->write->pending_eof) {
+            c->log->action = "connecting to upstream";
             (void) ngx_connection_error(c, c->write->kq_errno,
                                     "kevent() reported that connect() failed");
             return NGX_ERROR;
@@ -1313,6 +1314,7 @@ ngx_http_upstream_test_connect(ngx_conne
         }
 
         if (err) {
+            c->log->action = "connecting to upstream";
             (void) ngx_connection_error(c, err, "connect() failed");
             return NGX_ERROR;
         }
@@ -1490,6 +1492,11 @@ ngx_http_upstream_send_response(ngx_http
                 ngx_http_upstream_finalize_request(r, u, 0);
                 return;
             }
+
+            if (u->peer.connection->read->ready) {
+                ngx_http_upstream_process_non_buffered_body(
+                                                     u->peer.connection->read);
+            }
         }
 
         return;
@@ -3216,7 +3223,7 @@ ngx_http_upstream_add(ngx_conf_t *cf, ng
             ngx_log_error(NGX_LOG_WARN, cf->log, 0,
                           "upstream \"%V\" may not have port %d in %s:%ui",
                           &u->host, uscfp[i]->port,
-                          uscfp[i]->file_name.data, uscfp[i]->line);
+                          uscfp[i]->file_name, uscfp[i]->line);
             return NULL;
         }
 
@@ -3240,7 +3247,7 @@ ngx_http_upstream_add(ngx_conf_t *cf, ng
 
     uscf->flags = flags;
     uscf->host = u->host;
-    uscf->file_name = cf->conf_file->file.name;
+    uscf->file_name = cf->conf_file->file.name.data;
     uscf->line = cf->conf_file->line;
     uscf->port = u->port;
     uscf->default_port = u->default_port;
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -92,7 +92,7 @@ struct ngx_http_upstream_srv_conf_s {
 
     ngx_uint_t                      flags;
     ngx_str_t                       host;
-    ngx_str_t                       file_name;
+    u_char                         *file_name;
     ngx_uint_t                      line;
     in_port_t                       port;
     in_port_t                       default_port;
--- a/src/http/ngx_http_upstream_round_robin.c
+++ b/src/http/ngx_http_upstream_round_robin.c
@@ -136,7 +136,7 @@ ngx_http_upstream_init_round_robin(ngx_c
     if (us->port == 0 && us->default_port == 0) {
         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                       "no port in upstream \"%V\" in %s:%ui",
-                      &us->host, us->file_name.data, us->line);
+                      &us->host, us->file_name, us->line);
         return NGX_ERROR;
     }
 
@@ -149,7 +149,7 @@ ngx_http_upstream_init_round_robin(ngx_c
         if (u.err) {
             ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                           "%s in upstream \"%V\" in %s:%ui",
-                          u.err, &us->host, us->file_name.data, us->line);
+                          u.err, &us->host, us->file_name, us->line);
         }
 
         return NGX_ERROR;
@@ -498,7 +498,7 @@ ngx_http_upstream_get_peer(ngx_http_upst
                 }
 
                 if (peer[n].current_weight * 1000 / peer[i].current_weight
-                    >= peer[n].weight * 1000 / peer[i].weight)
+                    > peer[n].weight * 1000 / peer[i].weight)
                 {
                     return n;
                 }
--- a/src/mail/ngx_mail.c
+++ b/src/mail/ngx_mail.c
@@ -185,6 +185,8 @@ ngx_mail_block(ngx_conf_t *cf, ngx_comma
 
         /* init mail{} main_conf's */
 
+        cf->ctx = ctx;
+
         if (module->init_main_conf) {
             rv = module->init_main_conf(cf, ctx->main_conf[mi]);
             if (rv != NGX_CONF_OK) {
@@ -197,6 +199,8 @@ ngx_mail_block(ngx_conf_t *cf, ngx_comma
 
             /* merge the server{}s' srv_conf's */
 
+            cf->ctx = cscfp[s]->ctx;
+
             if (module->merge_srv_conf) {
                 rv = module->merge_srv_conf(cf,
                                             ctx->srv_conf[mi],
@@ -209,8 +213,6 @@ ngx_mail_block(ngx_conf_t *cf, ngx_comma
         }
     }
 
-    /* mail{}'s cf->ctx was needed while the configuration merging */
-
     *cf = pcf;
 
 
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -73,55 +73,27 @@ typedef struct {
 #define NGX_MAIL_IMAP_PROTOCOL  1
 #define NGX_MAIL_SMTP_PROTOCOL  2
 
-typedef struct {
-    ngx_msec_t              timeout;
+
+typedef struct ngx_mail_protocol_s  ngx_mail_protocol_t;
+
 
-    size_t                  imap_client_buffer_size;
+typedef struct {
+    ngx_mail_protocol_t    *protocol;
 
-    ngx_uint_t              protocol;
+    ngx_msec_t              timeout;
 
     ngx_flag_t              so_keepalive;
 
-    ngx_str_t               pop3_capability;
-    ngx_str_t               pop3_starttls_capability;
-    ngx_str_t               pop3_starttls_only_capability;
-    ngx_str_t               pop3_auth_capability;
-
-    ngx_str_t               imap_capability;
-    ngx_str_t               imap_starttls_capability;
-    ngx_str_t               imap_starttls_only_capability;
+    ngx_str_t               server_name;
 
-    ngx_str_t               smtp_capability;
-    ngx_str_t               smtp_starttls_capability;
-    ngx_str_t               smtp_starttls_only_capability;
-
-    ngx_str_t               server_name;
-    ngx_str_t               smtp_server_name;
-    ngx_str_t               smtp_greeting;
-
-    ngx_uint_t              pop3_auth_methods;
-    ngx_uint_t              imap_auth_methods;
-    ngx_uint_t              smtp_auth_methods;
-
-    ngx_array_t             pop3_capabilities;
-    ngx_array_t             imap_capabilities;
-    ngx_array_t             smtp_capabilities;
+    u_char                 *file_name;
+    ngx_int_t               line;
 
     /* server ctx */
     ngx_mail_conf_ctx_t    *ctx;
 } ngx_mail_core_srv_conf_t;
 
 
-typedef struct {
-    void                 *(*create_main_conf)(ngx_conf_t *cf);
-    char                 *(*init_main_conf)(ngx_conf_t *cf, void *conf);
-
-    void                 *(*create_srv_conf)(ngx_conf_t *cf);
-    char                 *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
-                                void *conf);
-} ngx_mail_module_t;
-
-
 typedef enum {
     ngx_pop3_start = 0,
     ngx_pop3_user,
@@ -179,9 +151,9 @@ typedef struct {
 
     ngx_uint_t              mail_state;
 
+    unsigned                protocol:3;
     unsigned                blocked:1;
     unsigned                quit:1;
-    unsigned                protocol:2;
     unsigned                quoted:1;
     unsigned                backslash:1;
     unsigned                no_sync_literal:1;
@@ -196,6 +168,7 @@ typedef struct {
     ngx_str_t               salt;
     ngx_str_t               tag;
     ngx_str_t               tagged_line;
+    ngx_str_t               text;
 
     ngx_str_t              *addr_text;
     ngx_str_t               smtp_helo;
@@ -205,7 +178,7 @@ typedef struct {
 
     ngx_uint_t              login_attempt;
 
-    /* used to parse IMAP/POP3/SMTP command */
+    /* used to parse POP3/IMAP/SMTP command */
 
     ngx_uint_t              state;
     u_char                 *cmd_start;
@@ -279,10 +252,43 @@ typedef struct {
 #define NGX_MAIL_PARSE_INVALID_COMMAND  20
 
 
-#define NGX_MAIL_MODULE      0x4C49414D     /* "MAIL" */
+typedef void (*ngx_mail_init_session_pt)(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+typedef void (*ngx_mail_init_protocol_pt)(ngx_event_t *rev);
+typedef void (*ngx_mail_auth_state_pt)(ngx_event_t *rev);
+typedef ngx_int_t (*ngx_mail_parse_command_pt)(ngx_mail_session_t *s);
+
+
+struct ngx_mail_protocol_s {
+    ngx_str_t                   name;
+    in_port_t                   port[4];
+    ngx_uint_t                  type;
+
+    ngx_mail_init_session_pt    init_session;
+    ngx_mail_init_protocol_pt   init_protocol;
+    ngx_mail_parse_command_pt   parse_command;
+    ngx_mail_auth_state_pt      auth_state;
 
-#define NGX_MAIL_MAIN_CONF   0x02000000
-#define NGX_MAIL_SRV_CONF    0x04000000
+    ngx_str_t                   internal_server_error;
+};
+
+
+typedef struct {
+    ngx_mail_protocol_t        *protocol;
+
+    void                       *(*create_main_conf)(ngx_conf_t *cf);
+    char                       *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+    void                       *(*create_srv_conf)(ngx_conf_t *cf);
+    char                       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+                                      void *conf);
+} ngx_mail_module_t;
+
+
+#define NGX_MAIL_MODULE         0x4C49414D     /* "MAIL" */
+
+#define NGX_MAIL_MAIN_CONF      0x02000000
+#define NGX_MAIL_SRV_CONF       0x04000000
 
 
 #define NGX_MAIL_MAIN_CONF_OFFSET  offsetof(ngx_mail_conf_ctx_t, main_conf)
@@ -304,17 +310,36 @@ typedef struct {
     ((ngx_mail_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
 
 
+#if (NGX_MAIL_SSL)
+void ngx_mail_starttls_handler(ngx_event_t *rev);
+ngx_int_t ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c);
+#endif
+
+
 void ngx_mail_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_mail_core_srv_conf_t *cscf);
+ngx_int_t ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_username(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_login_password(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *prefix, size_t len);
+ngx_int_t ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c);
+
 void ngx_mail_send(ngx_event_t *wev);
-void ngx_pop3_auth_state(ngx_event_t *rev);
-void ngx_imap_auth_state(ngx_event_t *rev);
-void ngx_smtp_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c);
 void ngx_mail_close_connection(ngx_connection_t *c);
 void ngx_mail_session_internal_server_error(ngx_mail_session_t *s);
+u_char *ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len);
 
-ngx_int_t ngx_pop3_parse_command(ngx_mail_session_t *s);
-ngx_int_t ngx_imap_parse_command(ngx_mail_session_t *s);
-ngx_int_t ngx_smtp_parse_command(ngx_mail_session_t *s);
+
+char *ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 
 
 /* STUB */
--- a/src/mail/ngx_mail_auth_http_module.c
+++ b/src/mail/ngx_mail_auth_http_module.c
@@ -111,6 +111,8 @@ static ngx_command_t  ngx_mail_auth_http
 
 
 static ngx_mail_module_t  ngx_mail_auth_http_module_ctx = {
+    NULL,                                  /* protocol */
+
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */
 
@@ -135,7 +137,6 @@ ngx_module_t  ngx_mail_auth_http_module 
 };
 
 
-static char       *ngx_mail_auth_http_protocol[] = { "pop3", "imap", "smtp" };
 static ngx_str_t   ngx_mail_auth_http_method[] = {
     ngx_string("plain"),
     ngx_string("plain"),
@@ -145,6 +146,7 @@ static ngx_str_t   ngx_mail_auth_http_me
 
 static ngx_str_t   ngx_mail_smtp_errcode = ngx_string("535 5.7.0");
 
+
 void
 ngx_mail_auth_http_init(ngx_mail_session_t *s)
 {
@@ -239,7 +241,7 @@ ngx_mail_auth_http_write_handler(ngx_eve
     if (wev->timedout) {
         ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
                       "auth http server %V timed out", ctx->peer.name);
-        ngx_close_connection(ctx->peer.connection);
+        ngx_close_connection(c);
         ngx_destroy_pool(ctx->pool);
         ngx_mail_session_internal_server_error(s);
         return;
@@ -250,7 +252,7 @@ ngx_mail_auth_http_write_handler(ngx_eve
     n = ngx_send(c, ctx->request->pos, size);
 
     if (n == NGX_ERROR) {
-        ngx_close_connection(ctx->peer.connection);
+        ngx_close_connection(c);
         ngx_destroy_pool(ctx->pool);
         ngx_mail_session_internal_server_error(s);
         return;
@@ -267,7 +269,7 @@ ngx_mail_auth_http_write_handler(ngx_eve
             }
 
             if (ngx_handle_write_event(wev, 0) == NGX_ERROR) {
-                ngx_close_connection(ctx->peer.connection);
+                ngx_close_connection(c);
                 ngx_destroy_pool(ctx->pool);
                 ngx_mail_session_internal_server_error(s);
             }
@@ -302,7 +304,7 @@ ngx_mail_auth_http_read_handler(ngx_even
     if (rev->timedout) {
         ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
                       "auth http server %V timed out", ctx->peer.name);
-        ngx_close_connection(ctx->peer.connection);
+        ngx_close_connection(c);
         ngx_destroy_pool(ctx->pool);
         ngx_mail_session_internal_server_error(s);
         return;
@@ -311,7 +313,7 @@ ngx_mail_auth_http_read_handler(ngx_even
     if (ctx->response == NULL) {
         ctx->response = ngx_create_temp_buf(ctx->pool, 1024);
         if (ctx->response == NULL) {
-            ngx_close_connection(ctx->peer.connection);
+            ngx_close_connection(c);
             ngx_destroy_pool(ctx->pool);
             ngx_mail_session_internal_server_error(s);
             return;
@@ -333,7 +335,7 @@ ngx_mail_auth_http_read_handler(ngx_even
         return;
     }
 
-    ngx_close_connection(ctx->peer.connection);
+    ngx_close_connection(c);
     ngx_destroy_pool(ctx->pool);
     ngx_mail_session_internal_server_error(s);
 }
@@ -749,7 +751,8 @@ ngx_mail_auth_http_process_headers(ngx_m
                 return;
             }
 
-            if (s->passwd.data == NULL && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
+            if (s->passwd.data == NULL
+                && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
             {
                 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                               "auth http server %V did not send password",
@@ -868,45 +871,30 @@ ngx_mail_auth_sleep_handler(ngx_event_t 
             return;
         }
 
-        switch (s->protocol) {
-
-        case NGX_MAIL_POP3_PROTOCOL:
-            s->mail_state = ngx_pop3_start;
-            s->connection->read->handler = ngx_pop3_auth_state;
-            break;
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
-        case NGX_MAIL_IMAP_PROTOCOL:
-            s->mail_state = ngx_imap_start;
-            s->connection->read->handler = ngx_imap_auth_state;
-            break;
+        rev->handler = cscf->protocol->auth_state;
 
-        default: /* NGX_MAIL_SMTP_PROTOCOL */
-            s->mail_state = ngx_smtp_start;
-            s->connection->read->handler = ngx_smtp_auth_state;
-            break;
-        }
-
+        s->mail_state = 0;
         s->auth_method = NGX_MAIL_AUTH_PLAIN;
 
         c->log->action = "in auth state";
 
-        ngx_mail_send(s->connection->write);
+        ngx_mail_send(c->write);
 
         if (c->destroyed) {
             return;
         }
 
-        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
         ngx_add_timer(rev, cscf->timeout);
 
         if (rev->ready) {
-            s->connection->read->handler(rev);
+            rev->handler(rev);
             return;
         }
 
         if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
-            ngx_mail_close_connection(s->connection);
+            ngx_mail_close_connection(c);
         }
 
         return;
@@ -914,7 +902,7 @@ ngx_mail_auth_sleep_handler(ngx_event_t 
 
     if (rev->active) {
         if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
-            ngx_mail_close_connection(s->connection);
+            ngx_mail_close_connection(c);
         }
     }
 }
@@ -1147,9 +1135,10 @@ static ngx_buf_t *
 ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool,
     ngx_mail_auth_http_conf_t *ahcf)
 {
-    size_t      len;
-    ngx_buf_t  *b;
-    ngx_str_t   login, passwd;
+    size_t                     len;
+    ngx_buf_t                 *b;
+    ngx_str_t                  login, passwd;
+    ngx_mail_core_srv_conf_t  *cscf;
 
     if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
         return NULL;
@@ -1159,6 +1148,8 @@ ngx_mail_auth_http_create_request(ngx_ma
         return NULL;
     }
 
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
     len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
           + sizeof("Host: ") - 1 + ahcf->host_header.len + sizeof(CRLF) - 1
           + sizeof("Auth-Method: ") - 1
@@ -1167,7 +1158,8 @@ ngx_mail_auth_http_create_request(ngx_ma
           + sizeof("Auth-User: ") - 1 + login.len + sizeof(CRLF) - 1
           + sizeof("Auth-Pass: ") - 1 + passwd.len + sizeof(CRLF) - 1
           + sizeof("Auth-Salt: ") - 1 + s->salt.len
-          + sizeof("Auth-Protocol: imap" CRLF) - 1
+          + sizeof("Auth-Protocol: ") - 1 + cscf->protocol->name.len
+                + sizeof(CRLF) - 1
           + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
                 + sizeof(CRLF) - 1
           + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len
@@ -1214,8 +1206,8 @@ ngx_mail_auth_http_create_request(ngx_ma
 
     b->last = ngx_cpymem(b->last, "Auth-Protocol: ",
                          sizeof("Auth-Protocol: ") - 1);
-    b->last = ngx_cpymem(b->last, ngx_mail_auth_http_protocol[s->protocol],
-                         sizeof("imap") - 1);
+    b->last = ngx_cpymem(b->last, cscf->protocol->name.data,
+                         cscf->protocol->name.len);
     *b->last++ = CR; *b->last++ = LF;
 
     b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF,
--- a/src/mail/ngx_mail_core_module.c
+++ b/src/mail/ngx_mail_core_module.c
@@ -18,90 +18,10 @@ static char *ngx_mail_core_server(ngx_co
     void *conf);
 static char *ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
-static char *ngx_mail_core_capability(ngx_conf_t *cf, ngx_command_t *cmd,
+static char *ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 
 
-static ngx_conf_enum_t  ngx_mail_core_procotol[] = {
-    { ngx_string("pop3"), NGX_MAIL_POP3_PROTOCOL },
-    { ngx_string("imap"), NGX_MAIL_IMAP_PROTOCOL },
-    { ngx_string("smtp"), NGX_MAIL_SMTP_PROTOCOL },
-    { ngx_null_string, 0 }
-};
-
-
-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_conf_bitmask_t  ngx_pop3_auth_methods[] = {
-    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
-    { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
-    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
-    { ngx_null_string, 0 }
-};
-
-
-static ngx_conf_bitmask_t  ngx_imap_auth_methods[] = {
-    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
-    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
-    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
-    { ngx_null_string, 0 }
-};
-
-
-static ngx_conf_bitmask_t  ngx_smtp_auth_methods[] = {
-    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
-    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
-    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
-    { ngx_null_string, 0 }
-};
-
-
-static ngx_str_t  ngx_imap_auth_methods_names[] = {
-    ngx_string("AUTH=PLAIN"),
-    ngx_string("AUTH=LOGIN"),
-    ngx_null_string,  /* APOP */
-    ngx_string("AUTH=CRAM-MD5")
-};
-
-
-static ngx_str_t  ngx_smtp_auth_methods_names[] = {
-    ngx_string("PLAIN"),
-    ngx_string("LOGIN"),
-    ngx_null_string,  /* APOP */
-    ngx_string("CRAM-MD5")
-};
-
-
-static ngx_str_t  ngx_pop3_auth_plain_capability =
-    ngx_string("+OK methods supported:" CRLF
-               "LOGIN" CRLF
-               "PLAIN" CRLF
-               "." CRLF);
-
-
-static ngx_str_t  ngx_pop3_auth_cram_md5_capability =
-    ngx_string("+OK methods supported:" CRLF
-               "LOGIN" CRLF
-               "PLAIN" CRLF
-               "CRAM-MD5" CRLF
-               "." CRLF);
-
-
-
 static ngx_command_t  ngx_mail_core_commands[] = {
 
     { ngx_string("server"),
@@ -114,22 +34,15 @@ static ngx_command_t  ngx_mail_core_comm
     { ngx_string("listen"),
       NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
       ngx_mail_core_listen,
-      0,
+      NGX_MAIL_SRV_CONF_OFFSET,
       0,
       NULL },
 
     { ngx_string("protocol"),
       NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_enum_slot,
+      ngx_mail_core_protocol,
       NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, protocol),
-      &ngx_mail_core_procotol },
-
-    { ngx_string("imap_client_buffer"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
-      ngx_conf_set_size_slot,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, imap_client_buffer_size),
+      0,
       NULL },
 
     { ngx_string("so_keepalive"),
@@ -146,27 +59,6 @@ static ngx_command_t  ngx_mail_core_comm
       offsetof(ngx_mail_core_srv_conf_t, timeout),
       NULL },
 
-    { ngx_string("pop3_capabilities"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_mail_core_capability,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, pop3_capabilities),
-      NULL },
-
-    { ngx_string("imap_capabilities"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_mail_core_capability,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, imap_capabilities),
-      NULL },
-
-    { ngx_string("smtp_capabilities"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_mail_core_capability,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, smtp_capabilities),
-      NULL },
-
     { ngx_string("server_name"),
       NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -174,39 +66,13 @@ static ngx_command_t  ngx_mail_core_comm
       offsetof(ngx_mail_core_srv_conf_t, server_name),
       NULL },
 
-    { ngx_string("auth"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, pop3_auth_methods),
-      &ngx_pop3_auth_methods },
-
-    { ngx_string("pop3_auth"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, pop3_auth_methods),
-      &ngx_pop3_auth_methods },
-
-    { ngx_string("imap_auth"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, imap_auth_methods),
-      &ngx_imap_auth_methods },
-
-    { ngx_string("smtp_auth"),
-      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
-      ngx_conf_set_bitmask_slot,
-      NGX_MAIL_SRV_CONF_OFFSET,
-      offsetof(ngx_mail_core_srv_conf_t, smtp_auth_methods),
-      &ngx_smtp_auth_methods },
-
       ngx_null_command
 };
 
 
 static ngx_mail_module_t  ngx_mail_core_module_ctx = {
+    NULL,                                  /* protocol */
+
     ngx_mail_core_create_main_conf,        /* create main configuration */
     NULL,                                  /* init main configuration */
 
@@ -268,29 +134,15 @@ ngx_mail_core_create_srv_conf(ngx_conf_t
         return NULL;
     }
 
-    cscf->imap_client_buffer_size = NGX_CONF_UNSET_SIZE;
-    cscf->protocol = NGX_CONF_UNSET_UINT;
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     cscf->protocol = NULL;
+     */
+
     cscf->timeout = NGX_CONF_UNSET_MSEC;
     cscf->so_keepalive = NGX_CONF_UNSET;
 
-    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;
-    }
-
-    if (ngx_array_init(&cscf->smtp_capabilities, cf->pool, 4, sizeof(ngx_str_t))
-        != NGX_OK)
-    {
-        return NULL;
-    }
-
     return cscf;
 }
 
@@ -301,35 +153,9 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t 
     ngx_mail_core_srv_conf_t *prev = parent;
     ngx_mail_core_srv_conf_t *conf = child;
 
-    u_char      *p, *auth;
-    size_t       size, stls_only_size;
-    ngx_str_t   *c, *d;
-    ngx_uint_t   i, m;
-
-    ngx_conf_merge_size_value(conf->imap_client_buffer_size,
-                              prev->imap_client_buffer_size,
-                              (size_t) ngx_pagesize);
     ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
-    ngx_conf_merge_uint_value(conf->protocol, prev->protocol,
-                              NGX_MAIL_IMAP_PROTOCOL);
-    ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
-
 
-    ngx_conf_merge_bitmask_value(conf->pop3_auth_methods,
-                                 prev->pop3_auth_methods,
-                                 (NGX_CONF_BITMASK_SET
-                                  |NGX_MAIL_AUTH_PLAIN_ENABLED));
-
-    ngx_conf_merge_bitmask_value(conf->imap_auth_methods,
-                                 prev->imap_auth_methods,
-                                 (NGX_CONF_BITMASK_SET
-                                  |NGX_MAIL_AUTH_PLAIN_ENABLED));
-
-    ngx_conf_merge_bitmask_value(conf->smtp_auth_methods,
-                                 prev->smtp_auth_methods,
-                                 (NGX_CONF_BITMASK_SET
-                                  |NGX_MAIL_AUTH_PLAIN_ENABLED
-                                  |NGX_MAIL_AUTH_LOGIN_ENABLED));
+    ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
 
 
     ngx_conf_merge_str_value(conf->server_name, prev->server_name, "");
@@ -343,351 +169,21 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t 
         if (gethostname((char *) conf->server_name.data, NGX_MAXHOSTNAMELEN)
             == -1)
         {
-            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
-                               "gethostname() failed");
+            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
+                          "gethostname() failed");
             return NGX_CONF_ERROR;
         }
 
         conf->server_name.len = ngx_strlen(conf->server_name.data);
     }
 
-
-    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;
-
-    stls_only_size = size + sizeof("STLS" CRLF) - 1;
-
-    c = conf->pop3_capabilities.elts;
-    for (i = 0; i < conf->pop3_capabilities.nelts; i++) {
-        size += c[i].len + sizeof(CRLF) - 1;
-
-        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
-            continue;
-        }
-
-        stls_only_size += c[i].len + sizeof(CRLF) - 1;
-    }
-
-    if (conf->pop3_auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
-        size += sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1;
-
-    } else {
-        size += sizeof("SASL LOGIN PLAIN" CRLF) - 1;
-    }
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->pop3_capability.len = size;
-    conf->pop3_capability.data = p;
-
-    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
-                   sizeof("+OK Capability list follows" CRLF) - 1);
-
-    for (i = 0; i < conf->pop3_capabilities.nelts; i++) {
-        p = ngx_cpymem(p, c[i].data, c[i].len);
-        *p++ = CR; *p++ = LF;
-    }
-
-    if (conf->pop3_auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
-        p = ngx_cpymem(p, "SASL LOGIN PLAIN CRAM-MD5" CRLF,
-                       sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1);
-
-    } else {
-        p = ngx_cpymem(p, "SASL LOGIN PLAIN" CRLF,
-                       sizeof("SASL LOGIN PLAIN" CRLF) - 1);
-    }
-
-    *p++ = '.'; *p++ = CR; *p = LF;
-
-
-    size += sizeof("STLS" CRLF) - 1;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->pop3_starttls_capability.len = size;
-    conf->pop3_starttls_capability.data = p;
-
-    p = ngx_cpymem(p, conf->pop3_capability.data,
-                   conf->pop3_capability.len - (sizeof("." CRLF) - 1));
-
-    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
-    *p++ = '.'; *p++ = CR; *p = LF;
-
-
-    if (conf->pop3_auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
-        conf->pop3_auth_capability = ngx_pop3_auth_cram_md5_capability;
-
-    } else {
-        conf->pop3_auth_capability = ngx_pop3_auth_plain_capability;
-    }
-
-
-    p = ngx_palloc(cf->pool, stls_only_size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->pop3_starttls_only_capability.len = stls_only_size;
-    conf->pop3_starttls_only_capability.data = p;
-
-    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
-                   sizeof("+OK Capability list follows" CRLF) - 1);
-
-    for (i = 0; i < conf->pop3_capabilities.nelts; i++) {
-        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
-            continue;
-        }
-
-        p = ngx_cpymem(p, c[i].data, c[i].len);
-        *p++ = CR; *p++ = LF;
-    }
-
-    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
-    *p++ = '.'; *p++ = CR; *p = LF;
-
-
-    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" CRLF) - 1;
-
-    c = conf->imap_capabilities.elts;
-    for (i = 0; i < conf->imap_capabilities.nelts; i++) {
-        size += 1 + c[i].len;
-    }
-
-    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
-         m <<= 1, i++)
-    {
-        if (m & conf->imap_auth_methods) {
-            size += 1 + ngx_imap_auth_methods_names[i].len;
-        }
-    }
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
+    if (conf->protocol == NULL) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                      "unknown mail protocol for server in %s:%ui",
+                      conf->file_name, conf->line);
         return NGX_CONF_ERROR;
     }
 
-    conf->imap_capability.len = size;
-    conf->imap_capability.data = p;
-
-    p = ngx_cpymem(p, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
-
-    for (i = 0; i < conf->imap_capabilities.nelts; i++) {
-        *p++ = ' ';
-        p = ngx_cpymem(p, c[i].data, c[i].len);
-    }
-
-    auth = p;
-
-    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
-         m <<= 1, i++)
-    {
-        if (m & conf->imap_auth_methods) {
-            *p++ = ' ';
-            p = ngx_cpymem(p, ngx_imap_auth_methods_names[i].data,
-                           ngx_imap_auth_methods_names[i].len);
-        }
-    }
-
-    *p++ = CR; *p = LF;
-
-
-    size += sizeof(" STARTTLS") - 1;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->imap_starttls_capability.len = size;
-    conf->imap_starttls_capability.data = p;
-
-    p = ngx_cpymem(p, conf->imap_capability.data,
-                   conf->imap_capability.len - (sizeof(CRLF) - 1));
-    p = ngx_cpymem(p, " STARTTLS", sizeof(" STARTTLS") - 1);
-    *p++ = CR; *p = LF;
-
-
-    size = (auth - conf->imap_capability.data) + sizeof(CRLF) - 1
-            + sizeof(" STARTTLS LOGINDISABLED") - 1;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->imap_starttls_only_capability.len = size;
-    conf->imap_starttls_only_capability.data = p;
-
-    p = ngx_cpymem(p, conf->imap_capability.data,
-                   auth - conf->imap_capability.data);
-    p = ngx_cpymem(p, " STARTTLS LOGINDISABLED",
-                   sizeof(" STARTTLS LOGINDISABLED") - 1);
-    *p++ = CR; *p = LF;
-
-
-    size = sizeof("220  ESMTP ready" CRLF) - 1 + conf->server_name.len;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->smtp_greeting.len = size;
-    conf->smtp_greeting.data = p;
-
-    *p++ = '2'; *p++ = '2'; *p++ = '0'; *p++ = ' ';
-    p = ngx_cpymem(p, conf->server_name.data, conf->server_name.len);
-    ngx_memcpy(p, " ESMTP ready" CRLF, sizeof(" ESMTP ready" CRLF) - 1);
-
-
-    size = sizeof("250 " CRLF) - 1 + conf->server_name.len;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->smtp_server_name.len = size;
-    conf->smtp_server_name.data = p;
-
-    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
-    p = ngx_cpymem(p, conf->server_name.data, conf->server_name.len);
-    *p++ = CR; *p = LF;
-
-
-    if (conf->smtp_capabilities.nelts == 0) {
-        conf->smtp_capabilities = prev->smtp_capabilities;
-    }
-
-    size = sizeof("250-") - 1 + conf->server_name.len + sizeof(CRLF) - 1
-           + sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1;
-
-    c = conf->smtp_capabilities.elts;
-    for (i = 0; i < conf->smtp_capabilities.nelts; i++) {
-        size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1;
-    }
-
-    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
-         m <<= 1, i++)
-    {
-        if (m & conf->smtp_auth_methods) {
-            size += 1 + ngx_smtp_auth_methods_names[i].len;
-        }
-    }
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->smtp_capability.len = size;
-    conf->smtp_capability.data = p;
-
-    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
-    p = ngx_cpymem(p, conf->server_name.data, conf->server_name.len);
-    *p++ = CR; *p++ = LF;
-
-    for (i = 0; i < conf->smtp_capabilities.nelts; i++) {
-        *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
-        p = ngx_cpymem(p, c[i].data, c[i].len);
-        *p++ = CR; *p++ = LF;
-    }
-
-    auth = p;
-
-    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
-    *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
-
-    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
-         m <<= 1, i++)
-    {
-        if (m & conf->smtp_auth_methods) {
-            *p++ = ' ';
-            p = ngx_cpymem(p, ngx_smtp_auth_methods_names[i].data,
-                           ngx_smtp_auth_methods_names[i].len);
-        }
-    }
-
-    *p++ = CR; *p = LF;
-
-    size += sizeof("250 STARTTLS" CRLF) - 1;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->smtp_starttls_capability.len = size;
-    conf->smtp_starttls_capability.data = p;
-
-    p = ngx_cpymem(p, conf->smtp_capability.data,
-                   conf->smtp_capability.len);
-
-    p = ngx_cpymem(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
-    *p++ = CR; *p = LF;
-
-    p = conf->smtp_starttls_capability.data
-        + (auth - conf->smtp_capability.data) + 3;
-    *p = '-';
-
-    size = (auth - conf->smtp_capability.data)
-            + sizeof("250 STARTTLS" CRLF) - 1;
-
-    p = ngx_palloc(cf->pool, size);
-    if (p == NULL) {
-        return NGX_CONF_ERROR;
-    }
-
-    conf->smtp_starttls_only_capability.len = size;
-    conf->smtp_starttls_only_capability.data = p;
-
-    p = ngx_cpymem(p, conf->smtp_capability.data,
-                   auth - conf->smtp_capability.data);
-
-    ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
-
     return NGX_CONF_OK;
 }
 
@@ -704,7 +200,6 @@ ngx_mail_core_server(ngx_conf_t *cf, ngx
     ngx_mail_core_srv_conf_t   *cscf, **cscfp;
     ngx_mail_core_main_conf_t  *cmcf;
 
-
     ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
     if (ctx == NULL) {
         return NGX_CONF_ERROR;
@@ -742,6 +237,9 @@ ngx_mail_core_server(ngx_conf_t *cf, ngx
     cscf = ctx->srv_conf[ngx_mail_core_module.ctx_index];
     cscf->ctx = ctx;
 
+    cscf->file_name = cf->conf_file->file.name.data;
+    cscf->line = cf->conf_file->line;
+
     cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
 
     cscfp = ngx_array_push(&cmcf->servers);
@@ -771,10 +269,13 @@ ngx_mail_core_server(ngx_conf_t *cf, ngx
 static char *
 ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
     ngx_str_t                  *value;
     ngx_url_t                   u;
-    ngx_uint_t                  i;
+    ngx_uint_t                  i, m;
     ngx_mail_listen_t          *imls;
+    ngx_mail_module_t          *module;
     ngx_mail_core_main_conf_t  *cmcf;
 
     value = cf->args->elts;
@@ -821,6 +322,25 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx
     imls->family = AF_INET;
     imls->ctx = cf->ctx;
 
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->protocol == NULL) {
+            continue;
+        }
+
+        for (i = 0; module->protocol->port[i]; i++) {
+            if (module->protocol->port[i] == u.port) {
+                cscf->protocol = module->protocol;
+                break;
+            }
+        }
+    }
+
     if (cf->args->nelts == 2) {
         return NGX_CONF_OK;
     }
@@ -837,7 +357,40 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx
 
 
 static char *
-ngx_mail_core_capability(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_mail_core_srv_conf_t  *cscf = conf;
+
+    ngx_str_t          *value;
+    ngx_uint_t          m;
+    ngx_mail_module_t  *module;
+
+    value = cf->args->elts;
+
+    for (m = 0; ngx_modules[m]; m++) {
+        if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+            continue;
+        }
+
+        module = ngx_modules[m]->ctx;
+
+        if (module->protocol
+            && ngx_strcmp(module->protocol->name.data, value[1].data) == 0)
+        {
+            cscf->protocol = module->protocol;
+
+            return NGX_CONF_OK;
+        }
+    }
+
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "unknown protocol \"%V\"", &value[1]);
+    return NGX_CONF_ERROR;
+}
+
+
+char *
+ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     char  *p = conf;
 
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -11,12 +11,6 @@
 
 
 static void ngx_mail_init_session(ngx_connection_t *c);
-static void ngx_mail_init_protocol(ngx_event_t *rev);
-static ngx_int_t ngx_mail_decode_auth_plain(ngx_mail_session_t *s,
-    ngx_str_t *encoded);
-static void ngx_mail_do_auth(ngx_mail_session_t *s);
-static ngx_int_t ngx_mail_read_command(ngx_mail_session_t *s);
-static u_char *ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len);
 
 #if (NGX_MAIL_SSL)
 static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
@@ -24,40 +18,6 @@ static void ngx_mail_ssl_handshake_handl
 #endif
 
 
-static ngx_str_t  greetings[] = {
-   ngx_string("+OK POP3 ready" CRLF),
-   ngx_string("* OK IMAP4 ready" CRLF)
-   /* SMTP greeting */
-};
-
-static ngx_str_t  internal_server_errors[] = {
-   ngx_string("-ERR internal server error" CRLF),
-   ngx_string("* BAD internal server error" CRLF),
-   ngx_string("451 4.3.2 Internal server error" CRLF),
-};
-
-static u_char  pop3_ok[] = "+OK" CRLF;
-static u_char  pop3_next[] = "+ " CRLF;
-static u_char  pop3_username[] = "+ VXNlcm5hbWU6" CRLF;
-static u_char  pop3_password[] = "+ UGFzc3dvcmQ6" CRLF;
-static u_char  pop3_invalid_command[] = "-ERR invalid command" CRLF;
-
-static u_char  imap_star[] = "* ";
-static u_char  imap_ok[] = "OK completed" 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;
-
-static u_char  smtp_ok[] = "250 2.0.0 OK" CRLF;
-static u_char  smtp_bye[] = "221 2.0.0 Bye" CRLF;
-static u_char  smtp_next[] = "334 " CRLF;
-static u_char  smtp_username[] = "334 VXNlcm5hbWU6" CRLF;
-static u_char  smtp_password[] = "334 UGFzc3dvcmQ6" CRLF;
-static u_char  smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF;
-static u_char  smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF;
-static u_char  smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF;
-
-
 void
 ngx_mail_init_connection(ngx_connection_t *c)
 {
@@ -172,7 +132,7 @@ ngx_mail_init_connection(ngx_connection_
 
 #if (NGX_MAIL_SSL)
 
-static void
+void
 ngx_mail_starttls_handler(ngx_event_t *rev)
 {
     ngx_connection_t     *c;
@@ -222,17 +182,20 @@ ngx_mail_ssl_init_connection(ngx_ssl_t *
 static void
 ngx_mail_ssl_handshake_handler(ngx_connection_t *c)
 {
-    ngx_mail_session_t  *s;
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
 
     if (c->ssl->handshaked) {
 
         s = c->data;
 
         if (s->starttls) {
-            c->read->handler = ngx_mail_init_protocol;
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            c->read->handler = cscf->protocol->init_protocol;
             c->write->handler = ngx_mail_send;
 
-            ngx_mail_init_protocol(c->read);
+            cscf->protocol->init_protocol(c->read);
 
             return;
         }
@@ -250,18 +213,14 @@ ngx_mail_ssl_handshake_handler(ngx_conne
 static void
 ngx_mail_init_session(ngx_connection_t *c)
 {
-    u_char                    *p;
     ngx_mail_session_t        *s;
     ngx_mail_core_srv_conf_t  *cscf;
 
-    c->read->handler = ngx_mail_init_protocol;
-    c->write->handler = ngx_mail_send;
-
     s = c->data;
 
     cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
-    s->protocol = cscf->protocol;
+    s->protocol = cscf->protocol->type;
 
     s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_mail_max_module);
     if (s->ctx == NULL) {
@@ -269,60 +228,249 @@ ngx_mail_init_session(ngx_connection_t *
         return;
     }
 
-    if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
-        s->out = cscf->smtp_greeting;
+    c->write->handler = ngx_mail_send;
+
+    cscf->protocol->init_session(s, c);
+}
+
+
+ngx_int_t
+ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_mail_core_srv_conf_t *cscf)
+{
+    s->salt.data = ngx_palloc(c->pool,
+                              sizeof(" <18446744073709551616.@>" CRLF) - 1
+                              + NGX_TIME_T_LEN
+                              + cscf->server_name.len);
+    if (s->salt.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    s->salt.len = ngx_sprintf(s->salt.data, "<%ul.%T@%V>" CRLF,
+                              ngx_random(), ngx_time(), &cscf->server_name)
+                  - s->salt.data;
+
+    return NGX_OK;
+}
+
+
+#if (NGX_MAIL_SSL)
+
+ngx_int_t
+ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl) {
+        return 0;
+    }
+
+    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+        return 1;
+    }
+
+    return 0;
+}
 
-    } else {
-        s->out = greetings[s->protocol];
+#endif
+
+
+ngx_int_t
+ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n)
+{
+    u_char     *p, *last;
+    ngx_str_t  *arg, plain;
+
+    arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth plain: \"%V\"", &arg[n]);
+#endif
+
+    plain.data = ngx_palloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (plain.data == NULL){
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&plain, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    p = plain.data;
+    last = p + plain.len;
+
+    while (p < last && *p++) { /* void */ }
+
+    if (p == last) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid login in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.data = p;
+
+    while (p < last && *p) { p++; }
+
+    if (p == last) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid password in AUTH PLAIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
     }
 
-    if ((s->protocol == NGX_MAIL_POP3_PROTOCOL
-         && (cscf->pop3_auth_methods
-             & (NGX_MAIL_AUTH_APOP_ENABLED|NGX_MAIL_AUTH_CRAM_MD5_ENABLED)))
+    s->login.len = p++ - s->login.data;
+
+    s->passwd.len = last - p;
+    s->passwd.data = p;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth plain: \"%V\" \"%V\"", &s->login, &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
 
-        || (s->protocol == NGX_MAIL_IMAP_PROTOCOL
-           && (cscf->imap_auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED))
+ngx_int_t
+ngx_mail_auth_login_username(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login username: \"%V\"", &arg[0]);
+
+    s->login.data = ngx_palloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+    if (s->login.data == NULL){
+        return NGX_ERROR;
+    }
 
-        || (s->protocol == NGX_MAIL_SMTP_PROTOCOL
-           && (cscf->smtp_auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)))
-    {
-        s->salt.data = ngx_palloc(c->pool,
-                                 sizeof(" <18446744073709551616.@>" CRLF) - 1
-                                 + NGX_TIME_T_LEN
-                                 + cscf->server_name.len);
-        if (s->salt.data == NULL) {
-            ngx_mail_session_internal_server_error(s);
-            return;
-        }
+    if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH LOGIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login username: \"%V\"", &s->login);
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_password(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login password: \"%V\"", &arg[0]);
+#endif
+
+    s->passwd.data = ngx_palloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+    if (s->passwd.data == NULL){
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH LOGIN command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
 
-        s->salt.len = ngx_sprintf(s->salt.data, "<%ul.%T@%V>" CRLF,
-                                  ngx_random(), ngx_time(), &cscf->server_name)
-                     - s->salt.data;
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth login password: \"%V\"", &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *prefix, size_t len)
+{
+    u_char      *p;
+    ngx_str_t    salt;
+    ngx_uint_t   n;
+
+    p = ngx_palloc(c->pool, len + ngx_base64_encoded_length(s->salt.len) + 2);
+    if (p == NULL) {
+        return NGX_ERROR;
+    }
+
+    salt.data = ngx_cpymem(p, prefix, len);
+    s->salt.len -= 2;
+
+    ngx_encode_base64(&salt, &s->salt);
+
+    s->salt.len += 2;
+    n = len + salt.len;
+    p[n++] = CR; p[n++] = LF;
+
+    s->out.len = n;
+    s->out.data = p;
 
-        if (s->protocol == NGX_MAIL_POP3_PROTOCOL) {
-            s->out.data = ngx_palloc(c->pool,
-                                     greetings[0].len + 1 + s->salt.len);
-            if (s->out.data == NULL) {
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
+    return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    u_char     *p, *last;
+    ngx_str_t  *arg;
+
+    arg = s->args.elts;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth cram-md5: \"%V\"", &arg[0]);
 
-            p = ngx_cpymem(s->out.data,
-                           greetings[0].data, greetings[0].len - 2);
-            *p++ = ' ';
-            p = ngx_cpymem(p, s->salt.data, s->salt.len);
+    s->login.data = ngx_palloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+    if (s->login.data == NULL){
+        return NGX_ERROR;
+    }
 
-            s->out.len = p - s->out.data;
+    if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid base64 encoding in AUTH CRAM-MD5 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    p = s->login.data;
+    last = p + s->login.len;
+
+    while (p < last) {
+        if (*p++ == ' ') {
+            s->login.len = p - s->login.data - 1;
+            s->passwd.len = last - p;
+            s->passwd.data = p;
+            break;
         }
     }
 
-    ngx_add_timer(c->read, cscf->timeout);
-
-    if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
-        ngx_mail_close_connection(c);
+    if (s->passwd.len != 32) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+            "client sent invalid CRAM-MD5 hash in AUTH CRAM-MD5 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
     }
 
-    ngx_mail_send(c->write);
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth cram-md5: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
+
+    return NGX_DONE;
 }
 
 
@@ -391,1626 +539,18 @@ ngx_mail_send(ngx_event_t *wev)
 }
 
 
-static void
-ngx_mail_init_protocol(ngx_event_t *rev)
+ngx_int_t
+ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c)
 {
-    size_t                     size;
-    ngx_connection_t          *c;
-    ngx_mail_session_t        *s;
+    ssize_t                    n;
+    ngx_int_t                  rc;
+    ngx_str_t                  l;
     ngx_mail_core_srv_conf_t  *cscf;
 
-    c = rev->data;
-
-    c->log->action = "in auth state";
-
-    if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-        c->timedout = 1;
-        ngx_mail_close_connection(c);
-        return;
-    }
-
-    s = c->data;
-
-    switch (s->protocol) {
-
-    case NGX_MAIL_POP3_PROTOCOL:
-        size = 128;
-        s->mail_state = ngx_pop3_start;
-        c->read->handler = ngx_pop3_auth_state;
-        break;
-
-    case NGX_MAIL_IMAP_PROTOCOL:
-        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-        size = cscf->imap_client_buffer_size;
-        s->mail_state = ngx_imap_start;
-        c->read->handler = ngx_imap_auth_state;
-        break;
-
-    default: /* NGX_MAIL_SMTP_PROTOCOL */
-        size = 512;
-        s->mail_state = ngx_smtp_start;
-        c->read->handler = ngx_smtp_auth_state;
-        break;
-    }
-
-    if (s->buffer == NULL) {
-        if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
-            == NGX_ERROR)
-        {
-            ngx_mail_session_internal_server_error(s);
-            return;
-        }
-
-        s->buffer = ngx_create_temp_buf(c->pool, size);
-        if (s->buffer == NULL) {
-            ngx_mail_session_internal_server_error(s);
-            return;
-        }
-    }
-
-    c->read->handler(rev);
-}
-
-
-void
-ngx_pop3_auth_state(ngx_event_t *rev)
-{
-    u_char                    *p, *last, *text;
-    ssize_t                    size;
-    ngx_int_t                  rc;
-    ngx_str_t                 *arg, salt;
-    ngx_connection_t          *c;
-    ngx_mail_session_t        *s;
-    ngx_mail_core_srv_conf_t  *cscf;
-#if (NGX_MAIL_SSL)
-    ngx_mail_ssl_conf_t       *sslcf;
-#endif
-
-    c = rev->data;
-    s = c->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 auth state");
-
-    if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-        c->timedout = 1;
-        ngx_mail_close_connection(c);
-        return;
-    }
-
-    if (s->out.len) {
-        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy");
-        s->blocked = 1;
-        return;
-    }
-
-    s->blocked = 0;
-
-    rc = ngx_mail_read_command(s);
-
-    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
-        return;
-    }
-
-    text = pop3_ok;
-    size = sizeof(pop3_ok) - 1;
-
-    if (rc == NGX_OK) {
-        switch (s->mail_state) {
-
-        case ngx_pop3_start:
-
-            switch (s->command) {
-
-            case NGX_POP3_USER:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                if (s->args.nelts == 1) {
-                    s->mail_state = ngx_pop3_user;
-
-                    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_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
-
-                    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                                   "pop3 login: \"%V\"", &s->login);
-
-                    break;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_POP3_CAPA:
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
-                        size = cscf->pop3_starttls_capability.len;
-                        text = cscf->pop3_starttls_capability.data;
-                        break;
-                    }
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        size = cscf->pop3_starttls_only_capability.len;
-                        text = cscf->pop3_starttls_only_capability.data;
-                        break;
-                    }
-                }
-#endif
-
-                size = cscf->pop3_capability.len;
-                text = cscf->pop3_capability.data;
-                break;
-
-            case NGX_POP3_APOP:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-                if ((cscf->pop3_auth_methods & NGX_MAIL_AUTH_APOP_ENABLED)
-                    && 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_mail_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_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
-
-                    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                                   "pop3 apop: \"%V\" \"%V\"",
-                                   &s->login, &s->passwd);
-
-                    s->auth_method = NGX_MAIL_AUTH_APOP;
-
-                    ngx_mail_do_auth(s);
-                    return;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_POP3_AUTH:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-                if (s->args.nelts == 0) {
-                    size = cscf->pop3_auth_capability.len;
-                    text = cscf->pop3_auth_capability.data;
-                    s->state = 0;
-                    break;
-                }
-
-                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) {
-                            rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                            break;
-                        }
-
-                        s->mail_state = ngx_pop3_auth_login_username;
-
-                        size = sizeof(pop3_username) - 1;
-                        text = pop3_username;
-
-                        break;
-
-                    } else if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN",
-                                               5)
-                               == 0)
-                    {
-
-                        if (s->args.nelts == 1) {
-                            s->mail_state = ngx_pop3_auth_plain;
-
-                            size = sizeof(pop3_next) - 1;
-                            text = pop3_next;
-
-                            break;
-                        }
-
-                        if (s->args.nelts == 2) {
-
-                            /*
-                             * workaround for Eudora for Mac: it sends
-                             *    AUTH PLAIN [base64 encoded]
-                             */
-
-                            rc = ngx_mail_decode_auth_plain(s, &arg[1]);
-
-                            if (rc == NGX_OK) {
-                                ngx_mail_do_auth(s);
-                                return;
-                            }
-
-                            if (rc == NGX_ERROR) {
-                                ngx_mail_session_internal_server_error(s);
-                                return;
-                            }
-
-                            /* rc == NGX_MAIL_PARSE_INVALID_COMMAND */
-
-                            break;
-                        }
-
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-
-                } else if (arg[0].len == 8
-                           && ngx_strncasecmp(arg[0].data,
-                                              (u_char *) "CRAM-MD5", 8)
-                              == 0)
-                {
-                    if (!(cscf->pop3_auth_methods
-                          & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)
-                        || s->args.nelts != 1)
-                    {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-
-                    s->mail_state = ngx_pop3_auth_cram_md5;
-
-                    text = ngx_palloc(c->pool,
-                                      sizeof("+ " CRLF) - 1
-                                      + ngx_base64_encoded_length(s->salt.len));
-                    if (text == NULL) {
-                        ngx_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    text[0] = '+'; text[1]= ' ';
-                    salt.data = &text[2];
-                    s->salt.len -= 2;
-
-                    ngx_encode_base64(&salt, &s->salt);
-
-                    s->salt.len += 2;
-                    size = 2 + salt.len;
-                    text[size++] = CR; text[size++] = LF;
-
-                    break;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_POP3_QUIT:
-                s->quit = 1;
-                break;
-
-            case NGX_POP3_NOOP:
-                break;
-
-#if (NGX_MAIL_SSL)
-
-            case NGX_POP3_STLS:
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-                    if (sslcf->starttls) {
-                        c->read->handler = ngx_mail_starttls_handler;
-                        break;
-                    }
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-#endif
-
-            default:
-                s->mail_state = ngx_pop3_start;
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            break;
-
-        case ngx_pop3_user:
-
-            switch (s->command) {
-
-            case NGX_POP3_PASS:
-                if (s->args.nelts == 1) {
-                    arg = s->args.elts;
-                    s->passwd.len = arg[0].len;
-                    s->passwd.data = ngx_palloc(c->pool, s->passwd.len);
-                    if (s->passwd.data == NULL) {
-                        ngx_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-                    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                                   "pop3 passwd: \"%V\"", &s->passwd);
-#endif
-
-                    ngx_mail_do_auth(s);
-                    return;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_POP3_CAPA:
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-                size = cscf->pop3_capability.len;
-                text = cscf->pop3_capability.data;
-                break;
-
-            case NGX_POP3_QUIT:
-                s->quit = 1;
-                break;
-
-            case NGX_POP3_NOOP:
-                break;
-
-            default:
-                s->mail_state = ngx_pop3_start;
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            break;
-
-        /* suppress warinings */
-        case ngx_pop3_passwd:
-            break;
-
-        case ngx_pop3_auth_login_username:
-            arg = s->args.elts;
-            s->mail_state = ngx_pop3_auth_login_password;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth login username: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth login username: \"%V\"", &s->login);
-
-            size = sizeof(pop3_password) - 1;
-            text = pop3_password;
-
-            break;
-
-        case ngx_pop3_auth_login_password:
-            arg = s->args.elts;
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth login password: \"%V\"", &arg[0]);
-#endif
-
-            s->passwd.data = ngx_palloc(c->pool,
-                                        ngx_base64_decoded_length(arg[0].len));
-            if (s->passwd.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth login password: \"%V\"", &s->passwd);
-#endif
-
-            ngx_mail_do_auth(s);
-            return;
-
-        case ngx_pop3_auth_plain:
-            arg = s->args.elts;
-
-            rc = ngx_mail_decode_auth_plain(s, &arg[0]);
-
-            if (rc == NGX_OK) {
-                ngx_mail_do_auth(s);
-                return;
-            }
-
-            if (rc == NGX_ERROR) {
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            /* rc == NGX_MAIL_PARSE_INVALID_COMMAND */
-
-            break;
-
-        case ngx_pop3_auth_cram_md5:
-            arg = s->args.elts;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth cram-md5: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            p = s->login.data;
-            last = p + s->login.len;
-
-            while (p < last) {
-                if (*p++ == ' ') {
-                    s->login.len = p - s->login.data - 1;
-                    s->passwd.len = last - p;
-                    s->passwd.data = p;
-                    break;
-                }
-            }
-
-            if (s->passwd.len != 32) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid CRAM-MD5 hash "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "pop3 auth cram-md5: \"%V\" \"%V\"",
-                           &s->login, &s->passwd);
-
-            s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
-
-            ngx_mail_do_auth(s);
-            return;
-        }
-    }
-
-    if (rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
-        s->mail_state = ngx_pop3_start;
-        s->state = 0;
-        text = pop3_invalid_command;
-        size = sizeof(pop3_invalid_command) - 1;
-    }
-
-    s->args.nelts = 0;
-    s->buffer->pos = s->buffer->start;
-    s->buffer->last = s->buffer->start;
-
-    if (s->state) {
-        s->arg_start = s->buffer->start;
-    }
-
-    s->out.data = text;
-    s->out.len = size;
-
-    ngx_mail_send(c->write);
-}
-
-
-void
-ngx_imap_auth_state(ngx_event_t *rev)
-{
-    u_char                    *p, *last, *text, *dst, *src, *end;
-    ssize_t                    text_len, last_len;
-    ngx_str_t                 *arg, salt;
-    ngx_int_t                  rc;
-    ngx_uint_t                 tag, i;
-    ngx_connection_t          *c;
-    ngx_mail_session_t        *s;
-    ngx_mail_core_srv_conf_t  *cscf;
-#if (NGX_MAIL_SSL)
-    ngx_mail_ssl_conf_t       *sslcf;
-#endif
-
-    c = rev->data;
-    s = c->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth state");
-
-    if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-        c->timedout = 1;
-        ngx_mail_close_connection(c);
-        return;
-    }
-
-    if (s->out.len) {
-        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy");
-        s->blocked = 1;
-        return;
-    }
-
-    s->blocked = 0;
-
-    rc = ngx_mail_read_command(s);
-
-    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
-        return;
-    }
-
-    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_MAIL, c->log, 0, "imap auth command: %i",
-                       s->command);
-
-        if (s->backslash) {
-
-            arg = s->args.elts;
-
-            for (i = 0; i < s->args.nelts; i++) {
-                dst = arg[i].data;
-                end = dst + arg[i].len;
-
-                for (src = dst; src < end; dst++) {
-                    *dst = *src;
-                    if (*src++ == '\\') {
-                        *dst = *src++;
-                    }
-                }
-
-                arg[i].len = dst - arg[i].data;
-            }
-
-            s->backslash = 0;
-        }
-
-        switch (s->mail_state) {
-
-        case ngx_imap_start:
-
-            switch (s->command) {
-
-            case NGX_IMAP_LOGIN:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                arg = s->args.elts;
-
-                if (s->args.nelts == 2 && arg[0].len) {
-
-                    s->login.len = arg[0].len;
-                    s->login.data = ngx_palloc(c->pool, s->login.len);
-                    if (s->login.data == NULL) {
-                        ngx_mail_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_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-                    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                                   "imap login:\"%V\" passwd:\"%V\"",
-                                   &s->login, &s->passwd);
-#else
-                    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                                   "imap login:\"%V\"", &s->login);
-#endif
-
-                    ngx_mail_do_auth(s);
-                    return;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_IMAP_AUTHENTICATE:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                if (s->args.nelts != 1) {
-                    rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                    break;
-                }
-
-                arg = s->args.elts;
-
-                if (arg[0].len == 5) {
-
-                    if (ngx_strncasecmp(arg[0].data, (u_char *) "LOGIN", 5)
-                        == 0)
-                    {
-
-                        s->mail_state = ngx_imap_auth_login_username;
-
-                        last_len = sizeof(pop3_username) - 1;
-                        last = pop3_username;
-                        tag = 0;
-
-                        break;
-
-                    } else if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN",
-                                               5)
-                               == 0)
-                    {
-
-                        s->mail_state = ngx_imap_auth_plain;
-
-                        last_len = sizeof(pop3_next) - 1;
-                        last = pop3_next;
-                        tag = 0;
-
-                        break;
-                    }
-
-                } else if (arg[0].len == 8
-                           && ngx_strncasecmp(arg[0].data,
-                                              (u_char *) "CRAM-MD5", 8)
-                              == 0)
-                {
-                    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-                    if (!(cscf->imap_auth_methods
-                          & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)
-                        || s->args.nelts != 1)
-                    {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-
-                    s->mail_state = ngx_imap_auth_cram_md5;
-
-                    last = ngx_palloc(c->pool,
-                                      sizeof("+ " CRLF) - 1
-                                      + ngx_base64_encoded_length(s->salt.len));
-                    if (last == NULL) {
-                        ngx_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    last[0] = '+'; last[1]= ' ';
-                    salt.data = &last[2];
-                    s->salt.len -= 2;
-
-                    ngx_encode_base64(&salt, &s->salt);
-
-                    s->salt.len += 2;
-                    last_len = 2 + salt.len;
-                    last[last_len++] = CR; last[last_len++] = LF;
-                    tag = 0;
-
-                    break;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_IMAP_CAPABILITY:
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
-                        text_len = cscf->imap_starttls_capability.len;
-                        text = cscf->imap_starttls_capability.data;
-                        break;
-                    }
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        text_len = cscf->imap_starttls_only_capability.len;
-                        text = cscf->imap_starttls_only_capability.data;
-                        break;
-                    }
-                }
-#endif
-
-                text_len = cscf->imap_capability.len;
-                text = cscf->imap_capability.data;
-                break;
-
-            case NGX_IMAP_LOGOUT:
-                s->quit = 1;
-                text = imap_bye;
-                text_len = sizeof(imap_bye) - 1;
-                break;
-
-            case NGX_IMAP_NOOP:
-                break;
-
-#if (NGX_MAIL_SSL)
-
-            case NGX_IMAP_STARTTLS:
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-                    if (sslcf->starttls) {
-                        c->read->handler = ngx_mail_starttls_handler;
-                        break;
-                    }
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-#endif
-
-            default:
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            break;
-
-        case ngx_imap_auth_login_username:
-            arg = s->args.elts;
-            s->mail_state = ngx_imap_auth_login_password;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth login username: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth login username: \"%V\"", &s->login);
-
-            last_len = sizeof(pop3_password) - 1;
-            last = pop3_password;
-            tag = 0;
-
-            break;
-
-        case ngx_imap_auth_login_password:
-            arg = s->args.elts;
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth login password: \"%V\"", &arg[0]);
-#endif
-
-            s->passwd.data = ngx_palloc(c->pool,
-                                        ngx_base64_decoded_length(arg[0].len));
-            if (s->passwd.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth login password: \"%V\"", &s->passwd);
-#endif
-
-            ngx_mail_do_auth(s);
-            return;
-
-        case ngx_imap_auth_plain:
-            arg = s->args.elts;
-
-            rc = ngx_mail_decode_auth_plain(s, &arg[0]);
-
-            if (rc == NGX_OK) {
-                ngx_mail_do_auth(s);
-                return;
-            }
-
-            if (rc == NGX_ERROR) {
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            /* rc == NGX_MAIL_PARSE_INVALID_COMMAND */
-
-            break;
-
-        case ngx_imap_auth_cram_md5:
-            arg = s->args.elts;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth cram-md5: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            p = s->login.data;
-            last = p + s->login.len;
-
-            while (p < last) {
-                if (*p++ == ' ') {
-                    s->login.len = p - s->login.data - 1;
-                    s->passwd.len = last - p;
-                    s->passwd.data = p;
-                    break;
-                }
-            }
-
-            if (s->passwd.len != 32) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid CRAM-MD5 hash "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "imap auth cram-md5: \"%V\" \"%V\"",
-                           &s->login, &s->passwd);
-
-            s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
-
-            ngx_mail_do_auth(s);
-            return;
-        }
-
-    } else if (rc == NGX_IMAP_NEXT) {
-        last = imap_next;
-        last_len = sizeof(imap_next) - 1;
-        tag = 0;
-    }
-
-    if (rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
-        s->mail_state = ngx_imap_start;
-        s->state = 0;
-        last = imap_invalid_command;
-        last_len = sizeof(imap_invalid_command) - 1;
-    }
-
-    if (tag) {
-        if (s->tag.len == 0) {
-            s->tag.len = sizeof(imap_star) - 1;
-            s->tag.data = (u_char *) imap_star;
-        }
-
-        if (s->tagged_line.len < s->tag.len + text_len + last_len) {
-            s->tagged_line.len = s->tag.len + text_len + last_len;
-            s->tagged_line.data = ngx_palloc(c->pool, s->tagged_line.len);
-            if (s->tagged_line.data == NULL) {
-                ngx_mail_close_connection(c);
-                return;
-            }
-        }
-
-        s->out.data = s->tagged_line.data;
-        s->out.len = s->tag.len + text_len + last_len;
-
-        p = s->out.data;
-
-        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);
-
-
-    } else {
-        s->out.data = last;
-        s->out.len = last_len;
-    }
-
-    if (rc != NGX_IMAP_NEXT) {
-        s->args.nelts = 0;
-
-        if (s->state) {
-            /* preserve tag */
-            s->arg_start = s->buffer->start + s->tag.len;
-            s->buffer->pos = s->arg_start;
-            s->buffer->last = s->arg_start;
-
-        } else {
-            s->buffer->pos = s->buffer->start;
-            s->buffer->last = s->buffer->start;
-            s->tag.len = 0;
-        }
-    }
-
-    ngx_mail_send(c->write);
-}
-
-
-void
-ngx_smtp_auth_state(ngx_event_t *rev)
-{
-    u_char                    *p, *last, *text, ch;
-    ssize_t                    size;
-    ngx_int_t                  rc;
-    ngx_str_t                 *arg, salt, l;
-    ngx_uint_t                 i;
-    ngx_connection_t          *c;
-    ngx_mail_session_t        *s;
-    ngx_mail_core_srv_conf_t  *cscf;
-#if (NGX_MAIL_SSL)
-    ngx_mail_ssl_conf_t       *sslcf;
-#endif
-
-    c = rev->data;
-    s = c->data;
-
-    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp auth state");
-
-    if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-        c->timedout = 1;
-        ngx_mail_close_connection(c);
-        return;
-    }
-
-    if (s->out.len) {
-        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy");
-        s->blocked = 1;
-        return;
-    }
-
-    s->blocked = 0;
-
-    rc = ngx_mail_read_command(s);
-
-    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
-        return;
-    }
-
-    text = NULL;
-    size = 0;
-
-    if (rc == NGX_OK) {
-        switch (s->mail_state) {
-
-        case ngx_smtp_start:
-
-            switch (s->command) {
-
-            case NGX_SMTP_HELO:
-            case NGX_SMTP_EHLO:
-                cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-                if (s->args.nelts != 1) {
-                    text = smtp_invalid_argument;
-                    size = sizeof(smtp_invalid_argument) - 1;
-                    s->state = 0;
-                    break;
-                }
-
-                arg = s->args.elts;
-
-                s->smtp_helo.len = arg[0].len;
-
-                s->smtp_helo.data = ngx_palloc(c->pool, arg[0].len);
-                if (s->smtp_helo.data == NULL) {
-                    ngx_mail_session_internal_server_error(s);
-                    return;
-                }
-
-                ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len);
-
-                if (s->command == NGX_SMTP_HELO) {
-                    size = cscf->smtp_server_name.len;
-                    text = cscf->smtp_server_name.data;
-
-                } else {
-                    s->esmtp = 1;
-
-#if (NGX_MAIL_SSL)
-
-                    if (c->ssl == NULL) {
-                        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-
-                        if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
-                            size = cscf->smtp_starttls_capability.len;
-                            text = cscf->smtp_starttls_capability.data;
-                            break;
-                        }
-
-                        if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                            size = cscf->smtp_starttls_only_capability.len;
-                            text = cscf->smtp_starttls_only_capability.data;
-                            break;
-                        }
-                    }
-#endif
-
-                    size = cscf->smtp_capability.len;
-                    text = cscf->smtp_capability.data;
-                }
-
-                break;
-
-            case NGX_SMTP_AUTH:
-
-#if (NGX_MAIL_SSL)
-
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
-
-                    if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-                }
-#endif
-
-                if (s->args.nelts == 0) {
-                    text = smtp_invalid_argument;
-                    size = sizeof(smtp_invalid_argument) - 1;
-                    s->state = 0;
-                    break;
-                }
-
-                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) {
-                            rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                            break;
-                        }
-
-                        s->mail_state = ngx_smtp_auth_login_username;
-
-                        size = sizeof(smtp_username) - 1;
-                        text = smtp_username;
-
-                        break;
-
-                    } else if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN",
-                                               5)
-                               == 0)
-                    {
-                        if (s->args.nelts == 1) {
-                            s->mail_state = ngx_smtp_auth_plain;
-
-                            size = sizeof(smtp_next) - 1;
-                            text = smtp_next;
-
-                            break;
-                        }
-
-                        if (s->args.nelts == 2) {
-
-                            rc = ngx_mail_decode_auth_plain(s, &arg[1]);
-
-                            if (rc == NGX_OK) {
-                                ngx_mail_do_auth(s);
-                                return;
-                            }
-
-                            if (rc == NGX_ERROR) {
-                                ngx_mail_session_internal_server_error(s);
-                                return;
-                            }
-
-                            /* rc == NGX_MAIL_PARSE_INVALID_COMMAND */
-
-                            break;
-                        }
-
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-
-                } else if (arg[0].len == 8
-                           && ngx_strncasecmp(arg[0].data,
-                                              (u_char *) "CRAM-MD5", 8)
-                              == 0)
-                {
-                    cscf = ngx_mail_get_module_srv_conf(s,
-                                                        ngx_mail_core_module);
-
-                    if (!(cscf->smtp_auth_methods
-                          & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)
-                        || s->args.nelts != 1)
-                    {
-                        rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                        break;
-                    }
-
-                    s->mail_state = ngx_smtp_auth_cram_md5;
-
-                    text = ngx_palloc(c->pool,
-                                      sizeof("334 " CRLF) - 1
-                                      + ngx_base64_encoded_length(s->salt.len));
-                    if (text == NULL) {
-                        ngx_mail_session_internal_server_error(s);
-                        return;
-                    }
-
-                    text[0] = '3'; text[1]= '3'; text[2] = '4'; text[3]= ' ';
-                    salt.data = &text[4];
-                    s->salt.len -= 2;
-
-                    ngx_encode_base64(&salt, &s->salt);
-
-                    s->salt.len += 2;
-                    size = 4 + salt.len;
-                    text[size++] = CR; text[size++] = LF;
-
-                    break;
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-
-            case NGX_SMTP_QUIT:
-                s->quit = 1;
-                text = smtp_bye;
-                size = sizeof(smtp_bye) - 1;
-                break;
-
-            case NGX_SMTP_MAIL:
-
-                if (s->connection->log->log_level >= NGX_LOG_INFO) {
-                    l.len = s->buffer->last - s->buffer->start;
-                    l.data = s->buffer->start;
-
-                    for (i = 0; i < l.len; i++) {
-                        ch = l.data[i];
-
-                        if (ch != CR && ch != LF) {
-                            continue;
-                        }
-
-                        l.data[i] = ' ';
-                    }
-
-                    while (i) {
-                        if (l.data[i - 1] != ' ') {
-                            break;
-                        }
-
-                        i--;
-                    }
-
-                    l.len = i;
-
-                    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
-                                  "client was rejected: \"%V\"", &l);
-                }
-
-                text = smtp_auth_required;
-                size = sizeof(smtp_auth_required) - 1;
-                break;
-
-            case NGX_SMTP_NOOP:
-            case NGX_SMTP_RSET:
-                text = smtp_ok;
-                size = sizeof(smtp_ok) - 1;
-                break;
-
-#if (NGX_MAIL_SSL)
-
-            case NGX_SMTP_STARTTLS:
-                if (c->ssl == NULL) {
-                    sslcf = ngx_mail_get_module_srv_conf(s,
-                                                         ngx_mail_ssl_module);
-                    if (sslcf->starttls) {
-                        c->read->handler = ngx_mail_starttls_handler;
-
-                        /*
-                         * RFC3207 requires us to discard any knowledge
-                         * obtained from client before STARTTLS.
-                         */
-
-                        s->smtp_helo.len = 0;
-                        s->smtp_helo.data = NULL;
-
-                        text = smtp_ok;
-                        size = sizeof(smtp_ok) - 1;
-
-                        break;
-                    }
-                }
-
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-#endif
-
-            default:
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            break;
-
-        case ngx_smtp_auth_login_username:
-            arg = s->args.elts;
-            s->mail_state = ngx_smtp_auth_login_password;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth login username: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth login username: \"%V\"", &s->login);
-
-            size = sizeof(smtp_password) - 1;
-            text = smtp_password;
-
-            break;
-
-        case ngx_smtp_auth_login_password:
-            arg = s->args.elts;
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth login password: \"%V\"", &arg[0]);
-#endif
-
-            s->passwd.data = ngx_palloc(c->pool,
-                                        ngx_base64_decoded_length(arg[0].len));
-            if (s->passwd.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH LOGIN command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth login password: \"%V\"", &s->passwd);
-#endif
-
-            ngx_mail_do_auth(s);
-            return;
-
-        case ngx_smtp_auth_plain:
-            arg = s->args.elts;
-
-            rc = ngx_mail_decode_auth_plain(s, &arg[0]);
-
-            if (rc == NGX_OK) {
-                ngx_mail_do_auth(s);
-                return;
-            }
-
-            if (rc == NGX_ERROR) {
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            /* rc == NGX_MAIL_PARSE_INVALID_COMMAND */
-
-            break;
-
-        case ngx_smtp_auth_cram_md5:
-            arg = s->args.elts;
-
-            ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth cram-md5: \"%V\"", &arg[0]);
-
-            s->login.data = ngx_palloc(c->pool,
-                                       ngx_base64_decoded_length(arg[0].len));
-            if (s->login.data == NULL){
-                ngx_mail_session_internal_server_error(s);
-                return;
-            }
-
-            if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid base64 encoding "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            p = s->login.data;
-            last = p + s->login.len;
-
-            while (p < last) {
-                if (*p++ == ' ') {
-                    s->login.len = p - s->login.data - 1;
-                    s->passwd.len = last - p;
-                    s->passwd.data = p;
-                    break;
-                }
-            }
-
-            if (s->passwd.len != 32) {
-                ngx_log_error(NGX_LOG_INFO, c->log, 0,
-                              "client sent invalid CRAM-MD5 hash "
-                              "in AUTH CRAM-MD5 command");
-                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
-                break;
-            }
-
-            ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
-                           "smtp auth cram-md5: \"%V\" \"%V\"",
-                           &s->login, &s->passwd);
-
-            s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
-
-            ngx_mail_do_auth(s);
-            return;
-        }
-    }
-
-    if (rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
-        s->mail_state = ngx_smtp_start;
-        s->state = 0;
-        text = smtp_invalid_command;
-        size = sizeof(smtp_invalid_command) - 1;
-    }
-
-    s->args.nelts = 0;
-    s->buffer->pos = s->buffer->start;
-    s->buffer->last = s->buffer->start;
-
-    if (s->state) {
-        s->arg_start = s->buffer->start;
-    }
-
-    s->out.data = text;
-    s->out.len = size;
-
-    ngx_mail_send(c->write);
-}
-
-
-static ngx_int_t
-ngx_mail_decode_auth_plain(ngx_mail_session_t *s, ngx_str_t *encoded)
-{
-    u_char     *p, *last;
-    ngx_str_t   plain;
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
-                   "mail auth plain: \"%V\"", encoded);
-#endif
-
-    plain.data = ngx_palloc(s->connection->pool,
-                            ngx_base64_decoded_length(encoded->len));
-    if (plain.data == NULL){
-        return NGX_ERROR;
-    }
-
-    if (ngx_decode_base64(&plain, encoded) != NGX_OK) {
-        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
-                      "client sent invalid base64 encoding "
-                      "in AUTH PLAIN command");
-        return NGX_MAIL_PARSE_INVALID_COMMAND;
-    }
-
-    p = plain.data;
-    last = p + plain.len;
-
-    while (p < last && *p++) { /* void */ }
-
-    if (p == last) {
-        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
-                      "client sent invalid login in AUTH PLAIN command");
-        return NGX_MAIL_PARSE_INVALID_COMMAND;
-    }
-
-    s->login.data = p;
-
-    while (p < last && *p) { p++; }
-
-    if (p == last) {
-        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
-                      "client sent invalid password in AUTH PLAIN command");
-        return NGX_MAIL_PARSE_INVALID_COMMAND;
-    }
-
-    s->login.len = p++ - s->login.data;
-
-    s->passwd.len = last - p;
-    s->passwd.data = p;
-
-#if (NGX_DEBUG_MAIL_PASSWD)
-    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
-                   "mail auth plain: \"%V\" \"%V\"",
-                   &s->login, &s->passwd);
-#endif
-
-    return NGX_OK;
-}
-
-
-static void
-ngx_mail_do_auth(ngx_mail_session_t *s)
-{
-    s->args.nelts = 0;
-    s->buffer->pos = s->buffer->start;
-    s->buffer->last = s->buffer->start;
-    s->state = 0;
-
-    if (s->connection->read->timer_set) {
-        ngx_del_timer(s->connection->read);
-    }
-
-    s->login_attempt++;
-
-    ngx_mail_auth_http_init(s);
-}
-
-
-static ngx_int_t
-ngx_mail_read_command(ngx_mail_session_t *s)
-{
-    ssize_t    n;
-    ngx_int_t  rc;
-    ngx_str_t  l;
-
-    n = s->connection->recv(s->connection, s->buffer->last,
-                            s->buffer->end - s->buffer->last);
+    n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
 
     if (n == NGX_ERROR || n == 0) {
-        ngx_mail_close_connection(s->connection);
+        ngx_mail_close_connection(c);
         return NGX_ERROR;
     }
 
@@ -2019,7 +559,7 @@ ngx_mail_read_command(ngx_mail_session_t
     }
 
     if (n == NGX_AGAIN) {
-        if (ngx_handle_read_event(s->connection->read, 0) == NGX_ERROR) {
+        if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
             ngx_mail_session_internal_server_error(s);
             return NGX_ERROR;
         }
@@ -2027,19 +567,9 @@ ngx_mail_read_command(ngx_mail_session_t
         return NGX_AGAIN;
     }
 
-    switch (s->protocol) {
-    case NGX_MAIL_POP3_PROTOCOL:
-        rc = ngx_pop3_parse_command(s);
-        break;
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
-    case NGX_MAIL_IMAP_PROTOCOL:
-        rc = ngx_imap_parse_command(s);
-        break;
-
-    default: /* NGX_MAIL_SMTP_PROTOCOL */
-        rc = ngx_smtp_parse_command(s);
-        break;
-    }
+    rc = cscf->protocol->parse_command(s);
 
     if (rc == NGX_AGAIN) {
 
@@ -2050,7 +580,7 @@ ngx_mail_read_command(ngx_mail_session_t
         l.len = s->buffer->last - s->buffer->start;
         l.data = s->buffer->start;
 
-        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "client sent too long command \"%V\"", &l);
 
         s->quit = 1;
@@ -2063,7 +593,7 @@ ngx_mail_read_command(ngx_mail_session_t
     }
 
     if (rc == NGX_ERROR) {
-        ngx_mail_close_connection(s->connection);
+        ngx_mail_close_connection(c);
         return NGX_ERROR;
     }
 
@@ -2072,9 +602,31 @@ ngx_mail_read_command(ngx_mail_session_t
 
 
 void
+ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    s->args.nelts = 0;
+    s->buffer->pos = s->buffer->start;
+    s->buffer->last = s->buffer->start;
+    s->state = 0;
+
+    if (c->read->timer_set) {
+        ngx_del_timer(c->read);
+    }
+
+    s->login_attempt++;
+
+    ngx_mail_auth_http_init(s);
+}
+
+
+void
 ngx_mail_session_internal_server_error(ngx_mail_session_t *s)
 {
-    s->out = internal_server_errors[s->protocol];
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    s->out = cscf->protocol->internal_server_error;
     s->quit = 1;
 
     ngx_mail_send(s->connection->write);
@@ -2100,6 +652,10 @@ ngx_mail_close_connection(ngx_connection
 
 #endif
 
+#if (NGX_STAT_STUB)
+    ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
     c->destroyed = 1;
 
     pool = c->pool;
@@ -2110,7 +666,7 @@ ngx_mail_close_connection(ngx_connection
 }
 
 
-static u_char *
+u_char *
 ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len)
 {
     u_char              *p;
@@ -2135,7 +691,9 @@ ngx_mail_log_error(ngx_log_t *log, u_cha
         return p;
     }
 
-    p = ngx_snprintf(buf, len, ", server: %V", s->addr_text);
+    p = ngx_snprintf(buf, len, "%s, server: %V",
+                     s->starttls ? " using starttls" : "",
+                     s->addr_text);
     len -= p - buf;
     buf = p;
 
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_imap_handler.c
@@ -0,0 +1,459 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static ngx_int_t ngx_mail_imap_login(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_authenticate(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_capability(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_starttls(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+
+
+static u_char  imap_greeting[] = "* OK IMAP4 ready" CRLF;
+static u_char  imap_star[] = "* ";
+static u_char  imap_ok[] = "OK completed" CRLF;
+static u_char  imap_next[] = "+ OK" CRLF;
+static u_char  imap_plain_next[] = "+ " CRLF;
+static u_char  imap_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char  imap_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char  imap_bye[] = "* BYE" CRLF;
+static u_char  imap_invalid_command[] = "BAD invalid command" CRLF;
+
+
+void
+ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    s->out.len = sizeof(imap_greeting) - 1;
+    s->out.data = imap_greeting;
+
+    c->read->handler = ngx_mail_imap_init_protocol;
+
+    ngx_add_timer(c->read, cscf->timeout);
+
+    if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+        ngx_mail_close_connection(c);
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_imap_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+            == NGX_ERROR)
+        {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+        s->buffer = ngx_create_temp_buf(c->pool, iscf->client_buffer_size);
+        if (s->buffer == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    s->mail_state = ngx_imap_start;
+    c->read->handler = ngx_mail_imap_auth_state;
+
+    ngx_mail_imap_auth_state(rev);
+}
+
+
+void
+ngx_mail_imap_auth_state(ngx_event_t *rev)
+{
+    u_char              *p, *dst, *src, *end;
+    ngx_str_t           *arg;
+    ngx_int_t            rc;
+    ngx_uint_t           tag, i;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth state");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    tag = 1;
+    s->text.len = 0;
+    s->out.len = sizeof(imap_ok) - 1;
+    s->out.data = imap_ok;
+
+    if (rc == NGX_OK) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth command: %i",
+                       s->command);
+
+        if (s->backslash) {
+
+            arg = s->args.elts;
+
+            for (i = 0; i < s->args.nelts; i++) {
+                dst = arg[i].data;
+                end = dst + arg[i].len;
+
+                for (src = dst; src < end; dst++) {
+                    *dst = *src;
+                    if (*src++ == '\\') {
+                        *dst = *src++;
+                    }
+                }
+
+                arg[i].len = dst - arg[i].data;
+            }
+
+            s->backslash = 0;
+        }
+
+        switch (s->mail_state) {
+
+        case ngx_imap_start:
+
+            switch (s->command) {
+
+            case NGX_IMAP_LOGIN:
+                rc = ngx_mail_imap_login(s, c);
+                break;
+
+            case NGX_IMAP_AUTHENTICATE:
+                rc = ngx_mail_imap_authenticate(s, c);
+                tag = (rc != NGX_OK);
+                break;
+
+            case NGX_IMAP_CAPABILITY:
+                rc = ngx_mail_imap_capability(s, c);
+                break;
+
+            case NGX_IMAP_LOGOUT:
+                s->quit = 1;
+                s->text.len = sizeof(imap_bye) - 1;
+                s->text.data = imap_bye;
+                break;
+
+            case NGX_IMAP_NOOP:
+                break;
+
+            case NGX_IMAP_STARTTLS:
+                rc = ngx_mail_imap_starttls(s, c);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        case ngx_imap_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c);
+
+            tag = 0;
+            s->out.len = sizeof(imap_password) - 1;
+            s->out.data = imap_password;
+            s->mail_state = ngx_imap_auth_login_password;
+
+            break;
+
+        case ngx_imap_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_imap_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_imap_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+        }
+
+    } else if (rc == NGX_IMAP_NEXT) {
+        tag = 0;
+        s->out.len = sizeof(imap_next) - 1;
+        s->out.data = imap_next;
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->state = 0;
+        s->out.len = sizeof(imap_invalid_command) - 1;
+        s->out.data = imap_invalid_command;
+        s->mail_state = ngx_imap_start;
+        break;
+    }
+
+    if (tag) {
+        if (s->tag.len == 0) {
+            s->tag.len = sizeof(imap_star) - 1;
+            s->tag.data = (u_char *) imap_star;
+        }
+
+        if (s->tagged_line.len < s->tag.len + s->text.len + s->out.len) {
+            s->tagged_line.len = s->tag.len + s->text.len + s->out.len;
+            s->tagged_line.data = ngx_palloc(c->pool, s->tagged_line.len);
+            if (s->tagged_line.data == NULL) {
+                ngx_mail_close_connection(c);
+                return;
+            }
+        }
+
+        p = s->tagged_line.data;
+
+        if (s->text.len) {
+            p = ngx_cpymem(p, s->text.data, s->text.len);
+        }
+
+        p = ngx_cpymem(p, s->tag.data, s->tag.len);
+        ngx_memcpy(p, s->out.data, s->out.len);
+
+        s->out.len = s->text.len + s->tag.len + s->out.len;
+        s->out.data = s->tagged_line.data;
+    }
+
+    if (rc != NGX_IMAP_NEXT) {
+        s->args.nelts = 0;
+
+        if (s->state) {
+            /* preserve tag */
+            s->arg_start = s->buffer->start + s->tag.len;
+            s->buffer->pos = s->arg_start;
+            s->buffer->last = s->arg_start;
+
+        } else {
+            s->buffer->pos = s->buffer->start;
+            s->buffer->last = s->buffer->start;
+            s->tag.len = 0;
+        }
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+static ngx_int_t
+ngx_mail_imap_login(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
+
+    arg = s->args.elts;
+
+    if (s->args.nelts != 2 || arg[0].len == 0) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.len = arg[0].len;
+    s->login.data = ngx_palloc(c->pool, s->login.len);
+    if (s->login.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    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) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "imap login:\"%V\" passwd:\"%V\"",
+                   &s->login, &s->passwd);
+#else
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "imap login:\"%V\"", &s->login);
+#endif
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_authenticate(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        s->out.len = sizeof(imap_username) - 1;
+        s->out.data = imap_username;
+        s->mail_state = ngx_imap_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        s->out.len = sizeof(imap_plain_next) - 1;
+        s->out.data = imap_plain_next;
+        s->mail_state = ngx_imap_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+        if (!(iscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->salt.data == NULL) {
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+            s->mail_state = ngx_imap_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_capability(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_imap_srv_conf_t  *iscf;
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t       *sslcf;
+#endif
+
+    iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+#if (NGX_MAIL_SSL)
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+            s->text = iscf->starttls_capability;
+            return NGX_OK;
+        }
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+            s->text = iscf->starttls_only_capability;
+            return NGX_OK;
+        }
+    }
+#endif
+
+    s->text = iscf->capability;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_imap_module.c
@@ -0,0 +1,251 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_imap_module.h>
+
+
+static void *ngx_mail_imap_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_str_t  ngx_mail_imap_default_capabilities[] = {
+    ngx_string("IMAP4"),
+    ngx_string("IMAP4rev1"),
+    ngx_string("UIDPLUS"),
+    ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t  ngx_mail_imap_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_imap_auth_methods_names[] = {
+    ngx_string("AUTH=PLAIN"),
+    ngx_string("AUTH=LOGIN"),
+    ngx_null_string,  /* APOP */
+    ngx_string("AUTH=CRAM-MD5")
+};
+
+
+static ngx_mail_protocol_t  ngx_mail_imap_protocol = {
+    ngx_string("imap"),
+    { 143, 993, 0, 0 },
+    NGX_MAIL_IMAP_PROTOCOL,
+
+    ngx_mail_imap_init_session,
+    ngx_mail_imap_init_protocol,
+    ngx_mail_imap_parse_command,
+    ngx_mail_imap_auth_state,
+
+    ngx_string("* BAD internal server error" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_imap_commands[] = {
+
+    { ngx_string("imap_client_buffer"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, client_buffer_size),
+      NULL },
+
+    { ngx_string("imap_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("imap_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_imap_srv_conf_t, auth_methods),
+      &ngx_mail_imap_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_imap_module_ctx = {
+    &ngx_mail_imap_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_imap_create_srv_conf,         /* create server configuration */
+    ngx_mail_imap_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_imap_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_imap_module_ctx,             /* module context */
+    ngx_mail_imap_commands,                /* module directives */
+    NGX_MAIL_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_imap_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_imap_srv_conf_t  *iscf;
+
+    iscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_imap_srv_conf_t));
+    if (iscf == NULL) {
+        return NULL;
+    }
+
+    iscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+
+    if (ngx_array_init(&iscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return iscf;
+}
+
+
+static char *
+ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_imap_srv_conf_t *prev = parent;
+    ngx_mail_imap_srv_conf_t *conf = child;
+
+    u_char      *p, *auth;
+    size_t       size;
+    ngx_str_t   *c, *d;
+    ngx_uint_t   i, m;
+
+    ngx_conf_merge_size_value(conf->client_buffer_size,
+                              prev->client_buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                              prev->auth_methods,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    if (conf->capabilities.nelts == 0) {
+
+        for (d = ngx_mail_imap_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("* CAPABILITY" CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += 1 + c[i].len;
+    }
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            size += 1 + ngx_mail_imap_auth_methods_names[i].len;
+        }
+    }
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    p = ngx_cpymem(p, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        *p++ = ' ';
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+    }
+
+    auth = p;
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            *p++ = ' ';
+            p = ngx_cpymem(p, ngx_mail_imap_auth_methods_names[i].data,
+                           ngx_mail_imap_auth_methods_names[i].len);
+        }
+    }
+
+    *p++ = CR; *p = LF;
+
+
+    size += sizeof(" STARTTLS") - 1;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   conf->capability.len - (sizeof(CRLF) - 1));
+    p = ngx_cpymem(p, " STARTTLS", sizeof(" STARTTLS") - 1);
+    *p++ = CR; *p = LF;
+
+
+    size = (auth - conf->capability.data) + sizeof(CRLF) - 1
+            + sizeof(" STARTTLS LOGINDISABLED") - 1;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   auth - conf->capability.data);
+    p = ngx_cpymem(p, " STARTTLS LOGINDISABLED",
+                   sizeof(" STARTTLS LOGINDISABLED") - 1);
+    *p++ = CR; *p = LF;
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_imap_module.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    size_t       client_buffer_size;
+
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_imap_srv_conf_t;
+
+
+void ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_imap_init_protocol(ngx_event_t *rev);
+void ngx_mail_imap_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_imap_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_imap_module;
+
+
+#endif /* _NGX_MAIL_IMAP_MODULE_H_INCLUDED_ */
--- a/src/mail/ngx_mail_parse.c
+++ b/src/mail/ngx_mail_parse.c
@@ -10,7 +10,8 @@
 #include <ngx_mail.h>
 
 
-ngx_int_t ngx_pop3_parse_command(ngx_mail_session_t *s)
+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;
@@ -207,7 +208,8 @@ invalid:
 }
 
 
-ngx_int_t ngx_imap_parse_command(ngx_mail_session_t *s)
+ngx_int_t
+ngx_mail_imap_parse_command(ngx_mail_session_t *s)
 {
     u_char      ch, *p, *c;
     ngx_str_t  *arg;
@@ -613,7 +615,8 @@ invalid:
 }
 
 
-ngx_int_t ngx_smtp_parse_command(ngx_mail_session_t *s)
+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;
@@ -822,3 +825,56 @@ invalid:
 
     return NGX_MAIL_PARSE_INVALID_COMMAND;
 }
+
+
+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
+
+    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;
+            }
+
+            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 (s->args.nelts != 1) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
+            return NGX_MAIL_AUTH_CRAM_MD5;
+        }
+    }
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_handler.c
@@ -0,0 +1,501 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static ngx_int_t ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_int_t stls);
+static ngx_int_t ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+
+
+static u_char  pop3_greeting[] = "+OK POP3 ready" CRLF;
+static u_char  pop3_ok[] = "+OK" CRLF;
+static u_char  pop3_next[] = "+ " CRLF;
+static u_char  pop3_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char  pop3_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char  pop3_invalid_command[] = "-ERR invalid command" CRLF;
+
+
+void
+ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    u_char                    *p;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    if (pscf->auth_methods
+        & (NGX_MAIL_AUTH_APOP_ENABLED|NGX_MAIL_AUTH_CRAM_MD5_ENABLED))
+    {
+        if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        s->out.data = ngx_palloc(c->pool, sizeof(pop3_greeting) + s->salt.len);
+        if (s->out.data == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(s->out.data, pop3_greeting, sizeof(pop3_greeting) - 3);
+        *p++ = ' ';
+        p = ngx_cpymem(p, s->salt.data, s->salt.len);
+
+        s->out.len = p - s->out.data;
+
+    } else {
+        s->out.len = sizeof(pop3_greeting) - 1;
+        s->out.data = pop3_greeting;
+    }
+
+    c->read->handler = ngx_mail_pop3_init_protocol;
+
+    ngx_add_timer(c->read, cscf->timeout);
+
+    if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+        ngx_mail_close_connection(c);
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_pop3_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+            == NGX_ERROR)
+        {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+
+        s->buffer = ngx_create_temp_buf(c->pool, 128);
+        if (s->buffer == NULL) {
+            ngx_mail_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    s->mail_state = ngx_pop3_start;
+    c->read->handler = ngx_mail_pop3_auth_state;
+
+    ngx_mail_pop3_auth_state(rev);
+}
+
+
+void
+ngx_mail_pop3_auth_state(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 auth state");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    s->out.len = sizeof(pop3_ok) - 1;
+    s->out.data = pop3_ok;
+
+    if (rc == NGX_OK) {
+        switch (s->mail_state) {
+
+        case ngx_pop3_start:
+
+            switch (s->command) {
+
+            case NGX_POP3_USER:
+                rc = ngx_mail_pop3_user(s, c);
+                break;
+
+            case NGX_POP3_CAPA:
+                rc = ngx_mail_pop3_capa(s, c, 1);
+                break;
+
+            case NGX_POP3_APOP:
+                rc = ngx_mail_pop3_apop(s, c);
+                break;
+
+            case NGX_POP3_AUTH:
+                rc = ngx_mail_pop3_auth(s, c);
+                break;
+
+            case NGX_POP3_QUIT:
+                s->quit = 1;
+                break;
+
+            case NGX_POP3_NOOP:
+                break;
+
+            case NGX_POP3_STLS:
+                rc = ngx_mail_pop3_stls(s, c);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                s->mail_state = ngx_pop3_start;
+                break;
+            }
+
+            break;
+
+        case ngx_pop3_user:
+
+            switch (s->command) {
+
+            case NGX_POP3_PASS:
+                rc = ngx_mail_pop3_pass(s, c);
+                break;
+
+            case NGX_POP3_CAPA:
+                rc = ngx_mail_pop3_capa(s, c, 0);
+                break;
+
+            case NGX_POP3_QUIT:
+                s->quit = 1;
+                break;
+
+            case NGX_POP3_NOOP:
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                s->mail_state = ngx_pop3_start;
+                break;
+            }
+
+            break;
+
+        /* suppress warinings */
+        case ngx_pop3_passwd:
+            break;
+
+        case ngx_pop3_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c);
+
+            s->out.len = sizeof(pop3_password) - 1;
+            s->out.data = pop3_password;
+            s->mail_state = ngx_pop3_auth_login_password;
+            break;
+
+        case ngx_pop3_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_pop3_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_pop3_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+        }
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->mail_state = ngx_pop3_start;
+        s->state = 0;
+
+        s->out.len = sizeof(pop3_invalid_command) - 1;
+        s->out.data = pop3_invalid_command;
+
+        /* fall through */
+
+    case NGX_OK:
+
+        s->args.nelts = 0;
+        s->buffer->pos = s->buffer->start;
+        s->buffer->last = s->buffer->start;
+
+        if (s->state) {
+            s->arg_start = s->buffer->start;
+        }
+
+        ngx_mail_send(c->write);
+    }
+}
+
+static ngx_int_t
+ngx_mail_pop3_user(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 != 1) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    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) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 login: \"%V\"", &s->login);
+
+    s->mail_state = ngx_pop3_user;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t  *arg;
+
+    if (s->args.nelts != 1) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    arg = s->args.elts;
+    s->passwd.len = arg[0].len;
+    s->passwd.data = ngx_palloc(c->pool, s->passwd.len);
+    if (s->passwd.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 passwd: \"%V\"", &s->passwd);
+#endif
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c, ngx_int_t stls)
+{
+    ngx_mail_pop3_srv_conf_t  *pscf;
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t       *sslcf;
+#endif
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+#if (NGX_MAIL_SSL)
+
+    if (stls && c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+            s->out = pscf->starttls_capability;
+            return NGX_OK;
+        }
+
+        if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+            s->out = pscf->starttls_only_capability;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    s->out = pscf->capability;
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts != 2) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+    if (!(pscf->auth_methods & NGX_MAIL_AUTH_APOP_ENABLED)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    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) {
+        return NGX_ERROR;
+    }
+
+    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) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "pop3 apop: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_APOP;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+    if (s->args.nelts == 0) {
+        s->out = pscf->auth_capability;
+        s->state = 0;
+
+        return NGX_OK;
+    }
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        s->out.len = sizeof(pop3_username) - 1;
+        s->out.data = pop3_username;
+        s->mail_state = ngx_pop3_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        s->out.len = sizeof(pop3_next) - 1;
+        s->out.data = pop3_next;
+        s->mail_state = ngx_pop3_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        if (!(pscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+            s->mail_state = ngx_pop3_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+    }
+
+    return rc;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_module.c
@@ -0,0 +1,263 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_pop3_module.h>
+
+
+static void *ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_str_t  ngx_mail_pop3_default_capabilities[] = {
+    ngx_string("TOP"),
+    ngx_string("USER"),
+    ngx_string("UIDL"),
+    ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t  ngx_mail_pop3_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_pop3_auth_plain_capability =
+    ngx_string("+OK methods supported:" CRLF
+               "LOGIN" CRLF
+               "PLAIN" CRLF
+               "." CRLF);
+
+
+static ngx_str_t  ngx_mail_pop3_auth_cram_md5_capability =
+    ngx_string("+OK methods supported:" CRLF
+               "LOGIN" CRLF
+               "PLAIN" CRLF
+               "CRAM-MD5" CRLF
+               "." CRLF);
+
+
+static ngx_mail_protocol_t  ngx_mail_pop3_protocol = {
+    ngx_string("pop3"),
+    { 110, 995, 0, 0 },
+    NGX_MAIL_POP3_PROTOCOL,
+
+    ngx_mail_pop3_init_session,
+    ngx_mail_pop3_init_protocol,
+    ngx_mail_pop3_parse_command,
+    ngx_mail_pop3_auth_state,
+
+    ngx_string("-ERR internal server error" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_pop3_commands[] = {
+
+    { ngx_string("pop3_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_pop3_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("pop3_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_pop3_srv_conf_t, auth_methods),
+      &ngx_mail_pop3_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_pop3_module_ctx = {
+    &ngx_mail_pop3_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_pop3_create_srv_conf,         /* create server configuration */
+    ngx_mail_pop3_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_pop3_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_pop3_module_ctx,             /* module context */
+    ngx_mail_pop3_commands,                /* module directives */
+    NGX_MAIL_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_pop3_srv_conf_t  *pscf;
+
+    pscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_pop3_srv_conf_t));
+    if (pscf == NULL) {
+        return NULL;
+    }
+
+    if (ngx_array_init(&pscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return pscf;
+}
+
+
+static char *
+ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_pop3_srv_conf_t *prev = parent;
+    ngx_mail_pop3_srv_conf_t *conf = child;
+
+    u_char      *p;
+    size_t       size, stls_only_size;
+    ngx_str_t   *c, *d;
+    ngx_uint_t   i;
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                                 prev->auth_methods,
+                                 (NGX_CONF_BITMASK_SET
+                                  |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    if (conf->capabilities.nelts == 0) {
+
+        for (d = ngx_mail_pop3_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("+OK Capability list follows" CRLF) - 1
+           + sizeof("." CRLF) - 1;
+
+    stls_only_size = size + sizeof("STLS" CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += c[i].len + sizeof(CRLF) - 1;
+
+        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+            continue;
+        }
+
+        stls_only_size += c[i].len + sizeof(CRLF) - 1;
+    }
+
+    if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+        size += sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1;
+
+    } else {
+        size += sizeof("SASL LOGIN PLAIN" CRLF) - 1;
+    }
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+                   sizeof("+OK Capability list follows" CRLF) - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+        p = ngx_cpymem(p, "SASL LOGIN PLAIN CRAM-MD5" CRLF,
+                       sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1);
+
+    } else {
+        p = ngx_cpymem(p, "SASL LOGIN PLAIN" CRLF,
+                       sizeof("SASL LOGIN PLAIN" CRLF) - 1);
+    }
+
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+
+    size += sizeof("STLS" CRLF) - 1;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   conf->capability.len - (sizeof("." CRLF) - 1));
+
+    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+
+    if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+        conf->auth_capability = ngx_mail_pop3_auth_cram_md5_capability;
+
+    } else {
+        conf->auth_capability = ngx_mail_pop3_auth_plain_capability;
+    }
+
+
+    p = ngx_palloc(cf->pool, stls_only_size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = stls_only_size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+                   sizeof("+OK Capability list follows" CRLF) - 1);
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+            continue;
+        }
+
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+    *p++ = '.'; *p++ = CR; *p = LF;
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_module.h
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+#define _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+
+
+typedef struct {
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+    ngx_str_t    auth_capability;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_pop3_srv_conf_t;
+
+
+void ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_pop3_init_protocol(ngx_event_t *rev);
+void ngx_mail_pop3_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_pop3_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_pop3_module;
+
+
+#endif /* _NGX_MAIL_POP3_MODULE_H_INCLUDED_ */
--- a/src/mail/ngx_mail_proxy_module.c
+++ b/src/mail/ngx_mail_proxy_module.c
@@ -78,6 +78,8 @@ static ngx_command_t  ngx_mail_proxy_com
 
 
 static ngx_mail_module_t  ngx_mail_proxy_module_ctx = {
+    NULL,                                  /* protocol */
+
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */
 
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_handler.c
@@ -0,0 +1,548 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev);
+static ngx_int_t ngx_mail_smtp_create_buffer(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_starttls(ngx_mail_session_t *s,
+    ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_discard_command(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *err);
+static void ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s,
+    ngx_connection_t *c, char *err);
+
+
+static u_char  smtp_ok[] = "250 2.0.0 OK" CRLF;
+static u_char  smtp_bye[] = "221 2.0.0 Bye" CRLF;
+static u_char  smtp_next[] = "334 " CRLF;
+static u_char  smtp_username[] = "334 VXNlcm5hbWU6" CRLF;
+static u_char  smtp_password[] = "334 UGFzc3dvcmQ6" CRLF;
+static u_char  smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF;
+static u_char  smtp_invalid_pipelining[] =
+   "503 5.5.0 Improper use of SMTP command pipelining" CRLF;
+static u_char  smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF;
+static u_char  smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF;
+
+
+void
+ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_msec_t                 timeout;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    timeout = sscf->greeting_delay ? sscf->greeting_delay : cscf->timeout;
+    ngx_add_timer(c->read, timeout);
+
+    if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+        ngx_mail_close_connection(c);
+    }
+
+    if (sscf->greeting_delay) {
+         c->read->handler = ngx_mail_smtp_invalid_pipelining;
+         return;
+    }
+
+    c->read->handler = ngx_mail_smtp_init_protocol;
+
+    s->out = sscf->greeting;
+
+    ngx_mail_send(c->write);
+}
+
+
+static void
+ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev)
+{
+    ngx_connection_t          *c;
+    ngx_mail_session_t        *s;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    c = rev->data;
+    s = c->data;
+
+    c->log->action = "in delay pipelining state";
+
+    if (rev->timedout) {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "delay greeting");
+
+        rev->timedout = 0;
+
+        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+        c->read->handler = ngx_mail_smtp_init_protocol;
+
+        ngx_add_timer(c->read, cscf->timeout);
+
+        if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+            ngx_mail_close_connection(c);
+            return;
+        }
+
+        sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+        s->out = sscf->greeting;
+
+    } else {
+
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "invalid pipelining");
+
+        if (s->buffer == NULL) {
+            if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+                return;
+            }
+        }
+
+        if (ngx_mail_smtp_discard_command(s, c,
+                                "client was rejected before greeting: \"%V\"")
+            != NGX_OK)
+        {
+            return;
+        }
+
+        s->out.len = sizeof(smtp_invalid_pipelining) - 1;
+        s->out.data = smtp_invalid_pipelining;
+    }
+
+    ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_smtp_init_protocol(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+
+    c->log->action = "in auth state";
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    s = c->data;
+
+    if (s->buffer == NULL) {
+        if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+            return;
+        }
+    }
+
+    s->mail_state = ngx_smtp_start;
+    c->read->handler = ngx_mail_smtp_auth_state;
+
+    ngx_mail_smtp_auth_state(rev);
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_create_buffer(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) {
+        ngx_mail_session_internal_server_error(s);
+        return NGX_ERROR;
+    }
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    s->buffer = ngx_create_temp_buf(c->pool, sscf->client_buffer_size);
+    if (s->buffer == NULL) {
+        ngx_mail_session_internal_server_error(s);
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+void
+ngx_mail_smtp_auth_state(ngx_event_t *rev)
+{
+    ngx_int_t            rc;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
+
+    c = rev->data;
+    s = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp auth state");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        c->timedout = 1;
+        ngx_mail_close_connection(c);
+        return;
+    }
+
+    if (s->out.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy");
+        s->blocked = 1;
+        return;
+    }
+
+    s->blocked = 0;
+
+    rc = ngx_mail_read_command(s, c);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    s->out.len = sizeof(smtp_ok) - 1;
+    s->out.data = smtp_ok;
+
+    if (rc == NGX_OK) {
+        switch (s->mail_state) {
+
+        case ngx_smtp_start:
+
+            switch (s->command) {
+
+            case NGX_SMTP_HELO:
+            case NGX_SMTP_EHLO:
+                rc = ngx_mail_smtp_helo(s, c);
+                break;
+
+            case NGX_SMTP_AUTH:
+                rc = ngx_mail_smtp_auth(s, c);
+                break;
+
+            case NGX_SMTP_QUIT:
+                s->quit = 1;
+                s->out.len = sizeof(smtp_bye) - 1;
+                s->out.data = smtp_bye;
+                break;
+
+            case NGX_SMTP_MAIL:
+                rc = ngx_mail_smtp_mail(s, c);
+                break;
+
+            case NGX_SMTP_NOOP:
+            case NGX_SMTP_RSET:
+                break;
+
+            case NGX_SMTP_STARTTLS:
+                rc = ngx_mail_smtp_starttls(s, c);
+                break;
+
+            default:
+                rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+                break;
+            }
+
+            break;
+
+        case ngx_smtp_auth_login_username:
+            rc = ngx_mail_auth_login_username(s, c);
+
+            s->out.len = sizeof(smtp_password) - 1;
+            s->out.data = smtp_password;
+            s->mail_state = ngx_smtp_auth_login_password;
+            break;
+
+        case ngx_smtp_auth_login_password:
+            rc = ngx_mail_auth_login_password(s, c);
+            break;
+
+        case ngx_smtp_auth_plain:
+            rc = ngx_mail_auth_plain(s, c, 0);
+            break;
+
+        case ngx_smtp_auth_cram_md5:
+            rc = ngx_mail_auth_cram_md5(s, c);
+            break;
+        }
+    }
+
+    switch (rc) {
+
+    case NGX_DONE:
+        ngx_mail_auth(s, c);
+        return;
+
+    case NGX_ERROR:
+        ngx_mail_session_internal_server_error(s);
+        return;
+
+    case NGX_MAIL_PARSE_INVALID_COMMAND:
+        s->mail_state = ngx_smtp_start;
+        s->state = 0;
+
+        s->out.len = sizeof(smtp_invalid_command) - 1;
+        s->out.data = smtp_invalid_command;
+
+        /* fall through */
+
+    case NGX_OK:
+        s->args.nelts = 0;
+        s->buffer->pos = s->buffer->start;
+        s->buffer->last = s->buffer->start;
+
+        if (s->state) {
+            s->arg_start = s->buffer->start;
+        }
+
+        ngx_mail_send(c->write);
+    }
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_str_t                 *arg;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t       *sslcf;
+#endif
+
+    if (s->args.nelts != 1) {
+        s->out.len = sizeof(smtp_invalid_argument) - 1;
+        s->out.data = smtp_invalid_argument;
+        s->state = 0;
+        return NGX_OK;
+    }
+
+    arg = s->args.elts;
+
+    s->smtp_helo.len = arg[0].len;
+
+    s->smtp_helo.data = ngx_palloc(c->pool, arg[0].len);
+    if (s->smtp_helo.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len);
+
+    sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+    if (s->command == NGX_SMTP_HELO) {
+        s->out = sscf->server_name;
+
+    } else {
+        s->esmtp = 1;
+
+#if (NGX_MAIL_SSL)
+
+        if (c->ssl == NULL) {
+            sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+            if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+                s->out = sscf->starttls_capability;
+                return NGX_OK;
+            }
+
+            if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+                s->out = sscf->starttls_only_capability;
+                return NGX_OK;
+            }
+        }
+#endif
+
+        s->out = sscf->capability;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_int_t                  rc;
+    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+#if (NGX_MAIL_SSL)
+    if (ngx_mail_starttls_only(s, c)) {
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+#endif
+
+    if (s->args.nelts == 0) {
+        s->out.len = sizeof(smtp_invalid_argument) - 1;
+        s->out.data = smtp_invalid_argument;
+        s->state = 0;
+        return NGX_OK;
+    }
+
+    rc = ngx_mail_auth_parse(s, c);
+
+    switch (rc) {
+
+    case NGX_MAIL_AUTH_LOGIN:
+
+        s->out.len = sizeof(smtp_username) - 1;
+        s->out.data = smtp_username;
+        s->mail_state = ngx_smtp_auth_login_username;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_PLAIN:
+
+        s->out.len = sizeof(smtp_next) - 1;
+        s->out.data = smtp_next;
+        s->mail_state = ngx_smtp_auth_plain;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_CRAM_MD5:
+
+        sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+        if (!(sscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->salt.data == NULL) {
+            cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+            if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+                return NGX_ERROR;
+            }
+        }
+
+        if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4) == NGX_OK) {
+            s->mail_state = ngx_smtp_auth_cram_md5;
+            return NGX_OK;
+        }
+
+        return NGX_ERROR;
+    }
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+    ngx_mail_smtp_log_rejected_command(s, c, "client was rejected: \"%V\"");
+
+    s->out.len = sizeof(smtp_auth_required) - 1;
+    s->out.data = smtp_auth_required;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+    ngx_mail_ssl_conf_t  *sslcf;
+
+    if (c->ssl == NULL) {
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->starttls) {
+
+            /*
+             * RFC3207 requires us to discard any knowledge
+             * obtained from client before STARTTLS.
+             */
+
+            s->smtp_helo.len = 0;
+            s->smtp_helo.data = NULL;
+
+            c->read->handler = ngx_mail_starttls_handler;
+            return NGX_OK;
+        }
+    }
+
+#endif
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_discard_command(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *err)
+{
+    ssize_t    n;
+
+    n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+    if (n == NGX_ERROR || n == 0) {
+        ngx_mail_close_connection(c);
+        return NGX_ERROR;
+    }
+
+    if (n > 0) {
+        s->buffer->last += n;
+    }
+
+    if (n == NGX_AGAIN) {
+        if (ngx_handle_read_event(c->read, 0) == NGX_ERROR) {
+            ngx_mail_session_internal_server_error(s);
+            return NGX_ERROR;
+        }
+
+        return NGX_AGAIN;
+    }
+
+    ngx_mail_smtp_log_rejected_command(s, c, err);
+
+    s->buffer->pos = s->buffer->start;
+    s->buffer->last = s->buffer->start;
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s, ngx_connection_t *c,
+    char *err)
+{
+    u_char      ch;
+    ngx_str_t   cmd;
+    ngx_uint_t  i;
+
+    if (c->log->log_level < NGX_LOG_INFO) {
+        return;
+    }
+
+    cmd.len = s->buffer->last - s->buffer->start;
+    cmd.data = s->buffer->start;
+
+    for (i = 0; i < cmd.len; i++) {
+        ch = cmd.data[i];
+
+        if (ch != CR && ch != LF) {
+            continue;
+        }
+
+        cmd.data[i] = '_';
+    }
+
+    cmd.len = i;
+
+    ngx_log_error(NGX_LOG_INFO, c->log, 0, err, &cmd);
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_module.c
@@ -0,0 +1,285 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_event.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+static void *ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_conf_bitmask_t  ngx_mail_smtp_auth_methods[] = {
+    { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+    { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+    { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+    { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t  ngx_mail_smtp_auth_methods_names[] = {
+    ngx_string("PLAIN"),
+    ngx_string("LOGIN"),
+    ngx_null_string,  /* APOP */
+    ngx_string("CRAM-MD5")
+};
+
+
+static ngx_mail_protocol_t  ngx_mail_smtp_protocol = {
+    ngx_string("smtp"),
+    { 25, 465, 587, 0 },
+    NGX_MAIL_SMTP_PROTOCOL,
+
+    ngx_mail_smtp_init_session,
+    ngx_mail_smtp_init_protocol,
+    ngx_mail_smtp_parse_command,
+    ngx_mail_smtp_auth_state,
+
+    ngx_string("451 4.3.2 Internal server error" CRLF)
+};
+
+
+static ngx_command_t  ngx_mail_smtp_commands[] = {
+
+    { ngx_string("smtp_client_buffer"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, client_buffer_size),
+      NULL },
+
+    { ngx_string("smtp_greeting_delay"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_msec_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, greeting_delay),
+      NULL },
+
+    { ngx_string("smtp_capabilities"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_mail_capabilities,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, capabilities),
+      NULL },
+
+    { ngx_string("smtp_auth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_bitmask_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_smtp_srv_conf_t, auth_methods),
+      &ngx_mail_smtp_auth_methods },
+
+      ngx_null_command
+};
+
+
+static ngx_mail_module_t  ngx_mail_smtp_module_ctx = {
+    &ngx_mail_smtp_protocol,               /* protocol */
+
+    NULL,                                  /* create main configuration */
+    NULL,                                  /* init main configuration */
+
+    ngx_mail_smtp_create_srv_conf,         /* create server configuration */
+    ngx_mail_smtp_merge_srv_conf           /* merge server configuration */
+};
+
+
+ngx_module_t  ngx_mail_smtp_module = {
+    NGX_MODULE_V1,
+    &ngx_mail_smtp_module_ctx,             /* module context */
+    ngx_mail_smtp_commands,                /* module directives */
+    NGX_MAIL_MODULE,                       /* module type */
+    NULL,                                  /* init master */
+    NULL,                                  /* init module */
+    NULL,                                  /* init process */
+    NULL,                                  /* init thread */
+    NULL,                                  /* exit thread */
+    NULL,                                  /* exit process */
+    NULL,                                  /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf)
+{
+    ngx_mail_smtp_srv_conf_t  *sscf;
+
+    sscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_smtp_srv_conf_t));
+    if (sscf == NULL) {
+        return NULL;
+    }
+
+    sscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+    sscf->greeting_delay = NGX_CONF_UNSET_MSEC;
+
+    if (ngx_array_init(&sscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    return sscf;
+}
+
+
+static char *
+ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_mail_smtp_srv_conf_t *prev = parent;
+    ngx_mail_smtp_srv_conf_t *conf = child;
+
+    u_char                    *p, *auth;
+    size_t                     size;
+    ngx_str_t                 *c;
+    ngx_uint_t                 i, m;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    ngx_conf_merge_size_value(conf->client_buffer_size,
+                              prev->client_buffer_size,
+                              (size_t) ngx_pagesize);
+
+    ngx_conf_merge_msec_value(conf->greeting_delay,
+                              prev->greeting_delay, 0);
+
+    ngx_conf_merge_bitmask_value(conf->auth_methods,
+                              prev->auth_methods,
+                              (NGX_CONF_BITMASK_SET
+                               |NGX_MAIL_AUTH_PLAIN_ENABLED
+                               |NGX_MAIL_AUTH_LOGIN_ENABLED));
+
+
+    cscf = ngx_mail_conf_get_module_srv_conf(cf, ngx_mail_core_module);
+
+    size = sizeof("220  ESMTP ready" CRLF) - 1 + cscf->server_name.len;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->greeting.len = size;
+    conf->greeting.data = p;
+
+    *p++ = '2'; *p++ = '2'; *p++ = '0'; *p++ = ' ';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    ngx_memcpy(p, " ESMTP ready" CRLF, sizeof(" ESMTP ready" CRLF) - 1);
+
+
+    size = sizeof("250 " CRLF) - 1 + cscf->server_name.len;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->server_name.len = size;
+    conf->server_name.data = p;
+
+    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    *p++ = CR; *p = LF;
+
+
+    if (conf->capabilities.nelts == 0) {
+        conf->capabilities = prev->capabilities;
+    }
+
+    size = sizeof("250-") - 1 + cscf->server_name.len + sizeof(CRLF) - 1
+           + sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1;
+
+    c = conf->capabilities.elts;
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1;
+    }
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            size += 1 + ngx_mail_smtp_auth_methods_names[i].len;
+        }
+    }
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->capability.len = size;
+    conf->capability.data = p;
+
+    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+    p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+    *p++ = CR; *p++ = LF;
+
+    for (i = 0; i < conf->capabilities.nelts; i++) {
+        *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+        p = ngx_cpymem(p, c[i].data, c[i].len);
+        *p++ = CR; *p++ = LF;
+    }
+
+    auth = p;
+
+    *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+    *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
+
+    for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+         m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+         m <<= 1, i++)
+    {
+        if (m & conf->auth_methods) {
+            *p++ = ' ';
+            p = ngx_cpymem(p, ngx_mail_smtp_auth_methods_names[i].data,
+                           ngx_mail_smtp_auth_methods_names[i].len);
+        }
+    }
+
+    *p++ = CR; *p = LF;
+
+    size += sizeof("250 STARTTLS" CRLF) - 1;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_capability.len = size;
+    conf->starttls_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   conf->capability.len);
+
+    p = ngx_cpymem(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+    *p++ = CR; *p = LF;
+
+    p = conf->starttls_capability.data
+        + (auth - conf->capability.data) + 3;
+    *p = '-';
+
+    size = (auth - conf->capability.data)
+            + sizeof("250 STARTTLS" CRLF) - 1;
+
+    p = ngx_palloc(cf->pool, size);
+    if (p == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    conf->starttls_only_capability.len = size;
+    conf->starttls_only_capability.data = p;
+
+    p = ngx_cpymem(p, conf->capability.data,
+                   auth - conf->capability.data);
+
+    ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+
+    return NGX_CONF_OK;
+}
new file mode 100644
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_module.h
@@ -0,0 +1,44 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ */
+
+
+#ifndef _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_mail.h>
+#include <ngx_mail_smtp_module.h>
+
+
+typedef struct {
+    ngx_msec_t   greeting_delay;
+
+    size_t       client_buffer_size;
+
+    ngx_str_t    capability;
+    ngx_str_t    starttls_capability;
+    ngx_str_t    starttls_only_capability;
+
+    ngx_str_t    server_name;
+    ngx_str_t    greeting;
+
+    ngx_uint_t   auth_methods;
+
+    ngx_array_t  capabilities;
+} ngx_mail_smtp_srv_conf_t;
+
+
+void ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_smtp_init_protocol(ngx_event_t *rev);
+void ngx_mail_smtp_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_smtp_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t  ngx_mail_smtp_module;
+
+
+#endif /* _NGX_MAIL_SMTP_MODULE_H_INCLUDED_ */
--- a/src/mail/ngx_mail_ssl_module.c
+++ b/src/mail/ngx_mail_ssl_module.c
@@ -120,6 +120,8 @@ static ngx_command_t  ngx_mail_ssl_comma
 
 
 static ngx_mail_module_t  ngx_mail_ssl_module_ctx = {
+    NULL,                                  /* protocol */
+
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */