<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <div class="moz-text-plain" wrap="true" style="font-family:
      -moz-fixed; font-size: 12px;" lang="x-western">
      <pre class="moz-quote-pre" wrap=""># HG changeset patch
# User Sander Hoentjen <a class="moz-txt-link-rfc2396E" href="mailto:shoentjen@antagonist.nl"><shoentjen@antagonist.nl></a>
# Date 1586369831 -7200
#      Wed Apr 08 20:17:11 2020 +0200
# Node ID f1dffaf619688aaab90caf31781ebe27c3f79598
# Parent  0cb942c1c1aa98118076e72e0b89940e85e6291c
Added support for proxying managesieve protocol

Sieve is a widely used protocol to implement server-side filtering. Some
servers and clients that support this are listed at <a class="moz-txt-link-rfc2396E" href="http://sieve.info/"><http://sieve.info/></a>. This
commit is tested with Dovecot as the server and Roundcube as the client, but I
don't see a readon why it not would work with others. It is a nice addition to
nginx because it adds the possibility to add server side filtering to setups
that use IMAP behind an nginx proxy.

Fixes <a class="moz-txt-link-freetext" href="https://trac.nginx.org/nginx/ticket/1697">https://trac.nginx.org/nginx/ticket/1697</a>

diff -r 0cb942c1c1aa -r f1dffaf61968 auto/modules
--- a/auto/modules      Fri Mar 13 02:12:10 2020 +0300
+++ b/auto/modules      Wed Apr 08 20:17:11 2020 +0200
@@ -965,6 +965,15 @@
         . auto/module
     fi
 
+    if [ $MAIL_SIEVE = YES ]; then
+        ngx_module_name=ngx_mail_sieve_module
+        ngx_module_deps=src/mail/ngx_mail_sieve_module.h
+        ngx_module_srcs="src/mail/ngx_mail_sieve_module.c \
+                         src/mail/ngx_mail_sieve_handler.c<a class="moz-txt-link-rfc2396E" href="mailto:++.auto/module+fi+if[$MAIL_SMTP=YES];thenngx_module_name=ngx_mail_smtp_modulengx_module_deps=src/mail/ngx_mail_smtp_module.hdiff-r0cb942c1c1aa-rf1dffaf61968auto/options---a/auto/optionsFriMar1302:12:102020+0300+++b/auto/optionsWedApr0820:17:112020+0200@@-112,6+112,7@@MAIL_SSL=NOMAIL_POP3=YESMAIL_IMAP=YES+MAIL_SIEVE=YESMAIL_SMTP=YESSTREAM=NO@@-305,6+306,8@@;;--without-mail_pop3_module)MAIL_POP3=NO;;--without-mail_imap_module)MAIL_IMAP=NO;;+--without-mail_sieve_module)+MAIL_SIEVE=NO;;--without-mail_smtp_module)MAIL_SMTP=NO;;--with-stream)STREAM=YES;;@@-517,11+520,12@@--without-httpdisableHTTPserver--without-http-cachedisableHTTPcache---with-mailenablePOP3/IMAP4/SMTPproxymodule---with-mail=dynamicenabledynamicPOP3/IMAP4/SMTPproxymodule+--with-mailenablePOP3/IMAP4/SIEVE/SMTPproxymodule+--with-mail=dynamicenabledynamicPOP3/IMAP4/SIEVE/SMTPproxymodule--with-mail_ssl_moduleenablengx_mail_ssl_module--without-mail_pop3_moduledisablengx_mail_pop3_module--without-mail_imap_moduledisablengx_mail_imap_module+--without-mail_sieve_moduledisablengx_mail_sieve_module--without-mail_smtp_moduledisablengx_mail_smtp_module--with-streamenableTCP/UDPproxymodulediff-r0cb942c1c1aa-rf1dffaf61968contrib/vim/syntax/nginx.vim---a/contrib/vim/syntax/nginx.vimFriMar1302:12:102020+0300+++b/contrib/vim/syntax/nginx.vimWedApr0820:17:112020+0200@@-571,6+571,9@@synkeywordngxDirectivecontainedsession_log_formatsynkeywordngxDirectivecontainedsession_log_zonesynkeywordngxDirectivecontainedset_real_ip_from+synkeywordngxDirectivecontainedsieve_auth+synkeywordngxDirectivecontainedsieve_capabilities+synkeywordngxDirectivecontainedsieve_client_buffersynkeywordngxDirectivecontainedslicesynkeywordngxDirectivecontainedsmtp_authsynkeywordngxDirectivecontainedsmtp_capabilitiesdiff-r0cb942c1c1aa-rf1dffaf61968src/mail/ngx_mail.h---a/src/mail/ngx_mail.hFriMar1302:12:102020+0300+++b/src/mail/ngx_mail.hWedApr0820:17:112020+0200@@-102,6+102,7@@#defineNGX_MAIL_POP3_PROTOCOL0#defineNGX_MAIL_IMAP_PROTOCOL1#defineNGX_MAIL_SMTP_PROTOCOL2+#defineNGX_MAIL_SIEVE_PROTOCOL3typedefstructngx_mail_protocol_sngx_mail_protocol_t;@@-154,6+155,21@@typedefenum{+ngx_sieve_start=0,+ngx_sieve_starttls,+ngx_sieve_auth_login_username,+ngx_sieve_auth_login_password,+ngx_sieve_auth_plain,+ngx_sieve_auth_cram_md5,+ngx_sieve_auth_external,+ngx_sieve_login,+ngx_sieve_login_capabilities,+ngx_sieve_user,+ngx_sieve_passwd+}ngx_sieve_state_e;+++typedefenum{ngx_smtp_start=0,ngx_smtp_auth_login_username,ngx_smtp_auth_login_password,@@-227,7+243,7@@ngx_uint_tlogin_attempt;-/*usedtoparsePOP3/IMAP/SMTPcommand*/+/*usedtoparsePOP3/IMAP/SIEVE/SMTPcommand*/ngx_uint_tstate;u_char*cmd_start;@@-271,6+287,14@@#defineNGX_IMAP_AUTHENTICATE7+#defineNGX_SIEVE_LOGOUT1+#defineNGX_SIEVE_CAPABILITY2+#defineNGX_SIEVE_NOOP3+#defineNGX_SIEVE_STARTTLS4+#defineNGX_SIEVE_NEXT5+#defineNGX_SIEVE_AUTHENTICATE6++#defineNGX_SMTP_HELO1#defineNGX_SMTP_EHLO2#defineNGX_SMTP_AUTH3@@-383,7+407,7@@ngx_int_tngx_mail_auth_login_password(ngx_mail_session_t*s,ngx_connection_t*c);ngx_int_tngx_mail_auth_cram_md5_salt(ngx_mail_session_t*s,-ngx_connection_t*c,char*prefix,size_tlen);+ngx_connection_t*c,char*prefix,size_tlen,ngx_uint_tquote);ngx_int_tngx_mail_auth_cram_md5(ngx_mail_session_t*s,ngx_connection_t*c);ngx_int_tngx_mail_auth_external(ngx_mail_session_t*s,ngx_connection_t*c,ngx_uint_tn);diff-r0cb942c1c1aa-rf1dffaf61968src/mail/ngx_mail_auth_http_module.c---a/src/mail/ngx_mail_auth_http_module.cFriMar1302:12:102020+0300+++b/src/mail/ngx_mail_auth_http_module.cWedApr0820:17:112020+0200@@-533,6+533,11@@+sizeof(CRLF)-1;break;+caseNGX_MAIL_SIEVE_PROTOCOL:+size=sizeof(">"
+
+        . auto/module
+    fi
+
     if [ $MAIL_SMTP = YES ]; then
         ngx_module_name=ngx_mail_smtp_module
         ngx_module_deps=src/mail/ngx_mail_smtp_module.h
diff -r 0cb942c1c1aa -r f1dffaf61968 auto/options
--- a/auto/options      Fri Mar 13 02:12:10 2020 +0300
+++ b/auto/options      Wed Apr 08 20:17:11 2020 +0200
@@ -112,6 +112,7 @@
 MAIL_SSL=NO
 MAIL_POP3=YES
 MAIL_IMAP=YES
+MAIL_SIEVE=YES
 MAIL_SMTP=YES
 
 STREAM=NO
@@ -305,6 +306,8 @@
         ;;
         --without-mail_pop3_module)      MAIL_POP3=NO               ;;
         --without-mail_imap_module)      MAIL_IMAP=NO               ;;
+        --without-mail_sieve_module)
+                                         MAIL_SIEVE=NO        ;;
         --without-mail_smtp_module)      MAIL_SMTP=NO               ;;
 
         --with-stream)                   STREAM=YES                 ;;
@@ -517,11 +520,12 @@
   --without-http                     disable HTTP server
   --without-http-cache               disable HTTP cache
 
-  --with-mail                        enable POP3/IMAP4/SMTP proxy module
-  --with-mail=dynamic                enable dynamic POP3/IMAP4/SMTP proxy module
+  --with-mail                        enable POP3/IMAP4/SIEVE/SMTP proxy module
+  --with-mail=dynamic                enable dynamic POP3/IMAP4/SIEVE/SMTP proxy module
   --with-mail_ssl_module             enable ngx_mail_ssl_module
   --without-mail_pop3_module         disable ngx_mail_pop3_module
   --without-mail_imap_module         disable ngx_mail_imap_module
+  --without-mail_sieve_module        disable ngx_mail_sieve_module
   --without-mail_smtp_module         disable ngx_mail_smtp_module
 
   --with-stream                      enable TCP/UDP proxy module
diff -r 0cb942c1c1aa -r f1dffaf61968 contrib/vim/syntax/nginx.vim
--- a/contrib/vim/syntax/nginx.vim      Fri Mar 13 02:12:10 2020 +0300
+++ b/contrib/vim/syntax/nginx.vim      Wed Apr 08 20:17:11 2020 +0200
@@ -571,6 +571,9 @@
 syn keyword ngxDirective contained session_log_format
 syn keyword ngxDirective contained session_log_zone
 syn keyword ngxDirective contained set_real_ip_from
+syn keyword ngxDirective contained sieve_auth
+syn keyword ngxDirective contained sieve_capabilities
+syn keyword ngxDirective contained sieve_client_buffer
 syn keyword ngxDirective contained slice
 syn keyword ngxDirective contained smtp_auth
 syn keyword ngxDirective contained smtp_capabilities
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h       Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail.h       Wed Apr 08 20:17:11 2020 +0200
@@ -102,6 +102,7 @@
 #define NGX_MAIL_POP3_PROTOCOL  0
 #define NGX_MAIL_IMAP_PROTOCOL  1
 #define NGX_MAIL_SMTP_PROTOCOL  2
+#define NGX_MAIL_SIEVE_PROTOCOL  3
 
 
 typedef struct ngx_mail_protocol_s  ngx_mail_protocol_t;
@@ -154,6 +155,21 @@
 
 
 typedef enum {
+    ngx_sieve_start = 0,
+    ngx_sieve_starttls,
+    ngx_sieve_auth_login_username,
+    ngx_sieve_auth_login_password,
+    ngx_sieve_auth_plain,
+    ngx_sieve_auth_cram_md5,
+    ngx_sieve_auth_external,
+    ngx_sieve_login,
+    ngx_sieve_login_capabilities,
+    ngx_sieve_user,
+    ngx_sieve_passwd
+} ngx_sieve_state_e;
+
+
+typedef enum {
     ngx_smtp_start = 0,
     ngx_smtp_auth_login_username,
     ngx_smtp_auth_login_password,
@@ -227,7 +243,7 @@
 
     ngx_uint_t              login_attempt;
 
-    /* used to parse POP3/IMAP/SMTP command */
+    /* used to parse POP3/IMAP/SIEVE/SMTP command */
 
     ngx_uint_t              state;
     u_char                 *cmd_start;
@@ -271,6 +287,14 @@
 #define NGX_IMAP_AUTHENTICATE  7
 
 
+#define NGX_SIEVE_LOGOUT        1
+#define NGX_SIEVE_CAPABILITY    2
+#define NGX_SIEVE_NOOP          3
+#define NGX_SIEVE_STARTTLS      4
+#define NGX_SIEVE_NEXT          5
+#define NGX_SIEVE_AUTHENTICATE  6
+
+
 #define NGX_SMTP_HELO          1
 #define NGX_SMTP_EHLO          2
 #define NGX_SMTP_AUTH          3
@@ -383,7 +407,7 @@
 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_connection_t *c, char *prefix, size_t len, ngx_uint_t quote);
 ngx_int_t ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c);
 ngx_int_t ngx_mail_auth_external(ngx_mail_session_t *s, ngx_connection_t *c,
     ngx_uint_t n);
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c      Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_auth_http_module.c      Wed Apr 08 20:17:11 2020 +0200
@@ -533,6 +533,11 @@
                            + sizeof(CRLF) - 1;
                     break;
 
+                case NGX_MAIL_SIEVE_PROTOCOL:
+                    size = sizeof("</a>BYE \"\"") - 1 + len
+                           + sizeof(CRLF) - 1;
+                    break;
+
                 default: /* NGX_MAIL_SMTP_PROTOCOL */
                     ctx->err = ctx->errmsg;
                     continue;
@@ -559,11 +564,27 @@
                     *p++ = 'N'; *p++ = 'O'; *p++ = ' ';
                     break;
 
+                case NGX_MAIL_SIEVE_PROTOCOL:
+                    p = ngx_cpymem(p, s->tag.data, s->tag.len);
+                    *p++ = 'B'; *p++ = 'Y'; *p++ = 'E'; *p++ = ' '; *p++ = '"';
+                    break;
+
                 default: /* NGX_MAIL_SMTP_PROTOCOL */
                     break;
                 }
 
                 p = ngx_cpymem(p, ctx->header_start, len);
+
+                switch (s->protocol) {
+
+                case NGX_MAIL_SIEVE_PROTOCOL:
+                    *p++ = '"';
+                    break;
+
+                default:
+                    break;
+                }
+
                 *p++ = CR; *p++ = LF;
 
                 ctx->err.len = p - ctx->err.data;
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c       Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_handler.c       Wed Apr 08 20:17:11 2020 +0200
@@ -520,28 +520,39 @@
 
 ngx_int_t
 ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s, ngx_connection_t *c,
-    char *prefix, size_t len)
+    char *prefix, size_t len, ngx_uint_t quote)
 {
     u_char      *p;
     ngx_str_t    salt;
-    ngx_uint_t   n;
+    ngx_uint_t   n = 0;
 
+    if (quote) {
+        len += 2;
+    }
     p = ngx_pnalloc(c->pool, len + ngx_base64_encoded_length(s->salt.len) + 2);
     if (p == NULL) {
         return NGX_ERROR;
     }
 
-    salt.data = ngx_cpymem(p, prefix, len);
+    if (quote) {
+        len -= 2;
+        salt.data = ngx_cpymem(p, "\"", 1);
+       n++;
+    }
+    salt.data = ngx_cpymem(p+ n, prefix, len);
     s->salt.len -= 2;
 
     ngx_encode_base64(&salt, &s->salt);
 
     s->salt.len += 2;
-    n = len + salt.len;
+    n += len + salt.len;
+    if (quote) {
+        p[n++] = '"';
+    }
     p[n++] = CR; p[n++] = LF;
 
     s->out.len = n;
-    s->out.data = p;
+    s->out.data = p--;
 
     return NGX_OK;
 }
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_imap_handler.c
--- a/src/mail/ngx_mail_imap_handler.c  Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_imap_handler.c  Wed Apr 08 20:17:11 2020 +0200
@@ -397,7 +397,7 @@
             }
         }
 
-        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2, 0) == NGX_OK) {
             s->mail_state = ngx_imap_auth_cram_md5;
             return NGX_OK;
         }
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_parse.c
--- a/src/mail/ngx_mail_parse.c Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_parse.c Wed Apr 08 20:17:11 2020 +0200
@@ -11,6 +11,7 @@
 #include <ngx_mail.h>
 #include <ngx_mail_pop3_module.h>
 #include <ngx_mail_imap_module.h>
+#include <ngx_mail_sieve_module.h>
 #include <ngx_mail_smtp_module.h>
 
 
@@ -620,6 +621,364 @@
 
 
 ngx_int_t
+ngx_mail_sieve_parse_command(ngx_mail_session_t *s)
+{
+    u_char      ch, *p, *c;
+    ngx_str_t  *arg;
+    enum {
+        sw_start = 0,
+        sw_spaces_before_argument,
+        sw_argument,
+        sw_backslash,
+        sw_literal,
+        sw_no_sync_literal_argument,
+        sw_start_literal_argument,
+        sw_literal_argument,
+        sw_end_literal_argument,
+        sw_almost_done
+    } state;
+
+    state = s->state;
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* SIEVE command */
+        case sw_start:
+            if (ch == ' ' || ch == CR || ch == LF) {
+
+                c = s->buffer->start;
+
+                switch (p - c) {
+
+                case 4:
+                    if ((c[0] == 'N' || c[0] == 'n')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'O'|| c[2] == 'o')
+                        && (c[3] == 'P'|| c[3] == 'p'))
+                    {
+                        s->command = NGX_SIEVE_NOOP;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 6:
+                    if ((c[0] == 'L'|| c[0] == 'l')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'G'|| c[2] == 'g')
+                        && (c[3] == 'O'|| c[3] == 'o')
+                        && (c[4] == 'U'|| c[4] == 'u')
+                        && (c[5] == 'T'|| c[5] == 't'))
+                    {
+                        s->command = NGX_SIEVE_LOGOUT;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+#if (NGX_MAIL_SSL)
+                case 8:
+                    if ((c[0] == 'S'|| c[0] == 's')
+                        && (c[1] == 'T'|| c[1] == 't')
+                        && (c[2] == 'A'|| c[2] == 'a')
+                        && (c[3] == 'R'|| c[3] == 'r')
+                        && (c[4] == 'T'|| c[4] == 't')
+                        && (c[5] == 'T'|| c[5] == 't')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'S'|| c[7] == 's'))
+                    {
+                        s->command = NGX_SIEVE_STARTTLS;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+#endif
+
+                case 10:
+                    if ((c[0] == 'C'|| c[0] == 'c')
+                        && (c[1] == 'A'|| c[1] == 'a')
+                        && (c[2] == 'P'|| c[2] == 'p')
+                        && (c[3] == 'A'|| c[3] == 'a')
+                        && (c[4] == 'B'|| c[4] == 'b')
+                        && (c[5] == 'I'|| c[5] == 'i')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'I'|| c[7] == 'i')
+                        && (c[8] == 'T'|| c[8] == 't')
+                        && (c[9] == 'Y'|| c[9] == 'y'))
+                    {
+                        s->command = NGX_SIEVE_CAPABILITY;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 12:
+                    if ((c[0] == 'A'|| c[0] == 'a')
+                        && (c[1] == 'U'|| c[1] == 'u')
+                        && (c[2] == 'T'|| c[2] == 't')
+                        && (c[3] == 'H'|| c[3] == 'h')
+                        && (c[4] == 'E'|| c[4] == 'e')
+                        && (c[5] == 'N'|| c[5] == 'n')
+                        && (c[6] == 'T'|| c[6] == 't')
+                        && (c[7] == 'I'|| c[7] == 'i')
+                        && (c[8] == 'C'|| c[8] == 'c')
+                        && (c[9] == 'A'|| c[9] == 'a')
+                        && (c[10] == 'T'|| c[10] == 't')
+                        && (c[11] == 'E'|| c[11] == 'e'))
+                    {
+                        s->command = NGX_SIEVE_AUTHENTICATE;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                default:
+                    goto invalid;
+                }
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
+            }
+
+            break;
+
+        case sw_spaces_before_argument:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                s->arg_end = p;
+                break;
+            case LF:
+                s->arg_end = p;
+                goto done;
+            case '"':
+                if (s->args.nelts <= 2) {
+                    s->quoted = 1;
+                    s->arg_start = p + 1;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
+            default:
+                if (s->args.nelts <= 2) {
+                    s->arg_start = p;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
+        case sw_argument:
+            if (ch == ' ' && s->quoted) {
+                break;
+            }
+
+            switch (ch) {
+            case '"':
+                if (!s->quoted) {
+                    break;
+                }
+                s->quoted = 0;
+                /* fall through */
+            case ' ':
+            case CR:
+            case LF:
+                arg = ngx_array_push(&s->args);
+                if (arg == NULL) {
+                    return NGX_ERROR;
+                }
+                arg->len = p - s->arg_start;
+                arg->data = s->arg_start;
+                s->arg_start = NULL;
+
+                switch (ch) {
+                case '"':
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            case '\\':
+                if (s->quoted) {
+                    s->backslash = 1;
+                    state = sw_backslash;
+                }
+                break;
+            }
+            break;
+
+        case sw_backslash:
+            switch (ch) {
+            case CR:
+            case LF:
+                goto invalid;
+            default:
+                state = sw_argument;
+            }
+            break;
+
+        case sw_literal:
+            if (ch >= '0' && ch <= '9') {
+                s->literal_len = s->literal_len * 10 + (ch - '0');
+                break;
+            }
+            if (ch == '}') {
+                state = sw_start_literal_argument;
+                break;
+            }
+            if (ch == '+') {
+                state = sw_no_sync_literal_argument;
+                break;
+            }
+            goto invalid;
+
+        case sw_no_sync_literal_argument:
+            if (ch == '}') {
+                s->no_sync_literal = 1;
+                state = sw_start_literal_argument;
+                break;
+            }
+            goto invalid;
+
+        case sw_start_literal_argument:
+            switch (ch) {
+            case CR:
+                break;
+            case LF:
+                s->buffer->pos = p + 1;
+                s->arg_start = p + 1;
+                if (s->no_sync_literal == 0) {
+                    s->state = sw_literal_argument;
+                    return NGX_SIEVE_NEXT;
+                }
+                state = sw_literal_argument;
+                s->no_sync_literal = 0;
+                break;
+            default:
+                goto invalid;
+            }
+            break;
+
+        case sw_literal_argument:
+            if (s->literal_len && --s->literal_len) {
+                break;
+            }
+
+            arg = ngx_array_push(&s->args);
+            if (arg == NULL) {
+                return NGX_ERROR;
+            }
+            arg->len = p + 1 - s->arg_start;
+            arg->data = s->arg_start;
+            s->arg_start = NULL;
+            state = sw_end_literal_argument;
+
+            break;
+
+        case sw_end_literal_argument:
+            switch (ch) {
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_spaces_before_argument;
+                break;
+            }
+            break;
+
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+        }
+    }
+
+    s->buffer->pos = p;
+    s->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    s->buffer->pos = p + 1;
+
+    if (s->arg_start) {
+        arg = ngx_array_push(&s->args);
+        if (arg == NULL) {
+            return NGX_ERROR;
+        }
+        arg->len = s->arg_end - s->arg_start;
+        arg->data = s->arg_start;
+
+        s->arg_start = NULL;
+        s->cmd_start = NULL;
+        s->quoted = 0;
+        s->no_sync_literal = 0;
+        s->literal_len = 0;
+    }
+
+    s->state = (s->command != NGX_SIEVE_AUTHENTICATE) ? sw_start : sw_spaces_before_argument;
+
+    return NGX_OK;
+
+invalid:
+
+    s->state = sw_start;
+    s->quoted = 0;
+    s->no_sync_literal = 0;
+    s->literal_len = 0;
+
+    return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
 ngx_mail_smtp_parse_command(ngx_mail_session_t *s)
 {
     u_char      ch, *p, *c, c0, c1, c2, c3;
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_pop3_handler.c
--- a/src/mail/ngx_mail_pop3_handler.c  Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_pop3_handler.c  Wed Apr 08 20:17:11 2020 +0200
@@ -492,7 +492,7 @@
             return NGX_MAIL_PARSE_INVALID_COMMAND;
         }
 
-        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+        if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2, 0) == NGX_OK) {
             s->mail_state = ngx_pop3_auth_cram_md5;
             return NGX_OK;
         }
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c  Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_proxy_module.c  Wed Apr 08 20:17:11 2020 +0200
@@ -24,6 +24,7 @@
 static void ngx_mail_proxy_block_read(ngx_event_t *rev);
 static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev);
 static void ngx_mail_proxy_imap_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_sieve_handler(ngx_event_t *rev);
 static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev);
 static void ngx_mail_proxy_dummy_handler(ngx_event_t *ev);
 static ngx_int_t ngx_mail_proxy_read_response(ngx_mail_session_t *s,
@@ -173,6 +174,11 @@
         s->mail_state = ngx_imap_start;
         break;
 
+    case NGX_MAIL_SIEVE_PROTOCOL:
+        p->upstream.connection->read->handler = ngx_mail_proxy_sieve_handler;
+        s->mail_state = ngx_sieve_start;
+        break;
+
     default: /* NGX_MAIL_SMTP_PROTOCOL */
         p->upstream.connection->read->handler = ngx_mail_proxy_smtp_handler;
         s->mail_state = ngx_smtp_start;
@@ -446,6 +452,144 @@
 
 
 static void
+ngx_mail_proxy_sieve_handler(ngx_event_t *rev)
+{
+    u_char                 *p;
+    ngx_int_t               rc;
+    ngx_str_t               line;
+    ngx_str_t               userpass;
+    ngx_str_t               b64_userpass;
+    ngx_connection_t       *c;
+    ngx_mail_session_t     *s;
+    ngx_mail_proxy_conf_t  *pcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                   "mail proxy sieve auth handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        c->timedout = 1;
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_mail_proxy_upstream_error(s);
+        return;
+    }
+
+    switch (s->mail_state) {
+
+    case ngx_sieve_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send login");
+
+        s->connection->log->action = "sending LOGIN command to upstream";
+
+        line.len = s->tag.len + sizeof("AUTHENTICATE ") - 1
+                   + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+       userpass.len = s->login.len + s->passwd.len + 2;
+       userpass.data = ngx_pnalloc(c->pool, userpass.len);
+        if (userpass.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+       b64_userpass.len = ngx_base64_encoded_length(userpass.len) + 2;
+       b64_userpass.data = ngx_pnalloc(c->pool, b64_userpass.len);
+        if (b64_userpass.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+       p = userpass.data;
+       *p++ = '\0';
+        p = ngx_cpymem(p, s->login.data, s->login.len);
+        *p++ = '\0';
+        p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
+
+       ngx_encode_base64(&b64_userpass, &userpass);
+       line.len = ngx_sprintf(line.data, "AUTHENTICATE \"PLAIN\" \"%V\"" CRLF,
+                               &b64_userpass)
+                   - line.data;
+
+        s->mail_state = ngx_sieve_login;
+        break;
+
+    case ngx_sieve_login:
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+                       "mail proxy send capabilities");
+
+        s->connection->log->action = "sending CAPABILITY command to upstream";
+
+        line.len = s->tag.len + sizeof("AUTHENTICATE ") - 1
+                   + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_pnalloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_mail_proxy_internal_server_error(s);
+            return;
+        }
+
+       line.len = ngx_sprintf(line.data, "CAPABILITY" CRLF)
+                   - line.data;
+
+        s->mail_state = ngx_sieve_login_capabilities;
+        break;
+
+    case ngx_sieve_login_capabilities:
+       s->connection->read->handler = ngx_mail_proxy_handler;
+        s->connection->write->handler = ngx_mail_proxy_handler;
+        rev->handler = ngx_mail_proxy_handler;
+        c->write->handler = ngx_mail_proxy_handler;
+
+        pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+        ngx_add_timer(s->connection->read, pcf->timeout);
+        ngx_del_timer(c->read);
+
+        c->log->action = NULL;
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+        ngx_mail_proxy_handler(s->connection->write);
+
+        return;
+
+    default:
+#if (NGX_SUPPRESS_WARN)
+        ngx_str_null(&line);
+#endif
+        break;
+    }
+
+    if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        ngx_mail_proxy_internal_server_error(s);
+        return;
+    }
+
+    s->proxy->buffer->pos = s->proxy->buffer->start;
+    s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
 ngx_mail_proxy_smtp_handler(ngx_event_t *rev)
 {
     u_char                    *p;
@@ -793,6 +937,42 @@
 
         break;
 
+    case NGX_MAIL_SIEVE_PROTOCOL:
+        if (p[0] == '"') {
+            m = b->last - (sizeof(CRLF "OK" CRLF) - 1);
+
+            while (m > p) {
+                if (m[0] == CR && m[1] == LF) {
+                    break;
+                }
+                m--;
+            }
+
+            if (m <= p || m[0] == '"') {
+                return NGX_AGAIN;
+            }
+            p = m + 2;
+        }
+
+        switch (state) {
+
+        case ngx_sieve_start:
+        case ngx_sieve_login:
+            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "1 upstream sent: \"%s\"", p);
+            if (p[0] == 'O' && p[1] == 'K') {
+                return NGX_OK;
+            }
+            break;
+        case ngx_sieve_login_capabilities:
+            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "2 upstream sent: \"%s\"", p);
+            if (p[0] == 'O' && p[1] == 'K') {
+                return NGX_OK;
+            }
+            break;
+        }
+
+        break;
+
     default: /* NGX_MAIL_SMTP_PROTOCOL */
 
         if (p[3] == '-') {
diff -r 0cb942c1c1aa -r f1dffaf61968 src/mail/ngx_mail_smtp_handler.c
--- a/src/mail/ngx_mail_smtp_handler.c  Fri Mar 13 02:12:10 2020 +0300
+++ b/src/mail/ngx_mail_smtp_handler.c  Wed Apr 08 20:17:11 2020 +0200
@@ -693,7 +693,7 @@
             }
         }
 
-        if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4) == NGX_OK) {
+        if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4, 0) == NGX_OK) {
             s->mail_state = ngx_smtp_auth_cram_md5;
             return NGX_OK;
         }
diff -r 0cb942c1c1aa -r f1dffaf61968 src/misc/ngx_cpp_test_module.cpp
--- a/src/misc/ngx_cpp_test_module.cpp  Fri Mar 13 02:12:10 2020 +0300
+++ b/src/misc/ngx_cpp_test_module.cpp  Wed Apr 08 20:17:11 2020 +0200
@@ -13,6 +13,7 @@
   #include <ngx_mail.h>
   #include <ngx_mail_pop3_module.h>
   #include <ngx_mail_imap_module.h>
+  #include <ngx_mail_sieve_module.h>
   #include <ngx_mail_smtp_module.h>
 }
 

</pre>
    </div>
  </body>
</html>