[nginx] Add support for XOAUTH2 and OAUTHBEARER authentication

Maxim Dounin mdounin at mdounin.ru
Fri May 31 23:03:21 UTC 2024


Hello!

On Tue, May 21, 2024 at 04:16:05AM +0300, Maxim Dounin wrote:

[...]

> Additionally, I tend to think that a better approach to 
> communicate with the auth server might be to parse SASL initial 
> responses as sent by clients, and provide parsed information, 
> username and the Bearer token, in the "Auth-User" and "Auth-Pass" 
> headers, similarly to what we do for other SASL mechanism, such as 
> PLAIN and CRAM-MD5.
> 
> This will certainly work for XOAUTH2, since the only information 
> is the username and the Bearer token.  In OAUTHBEARER some 
> additional information can be provided, but I very much doubt it 
> is used in practice (and, if needed, full initial response 
> can be sent separately, in a separate header).  And parsing 
> implementation seems to be easy enough.
> 
> Immediate benefits include early correctness checking (e.g., 
> "n,user=test at example.com,^A..." as used in tests and in some RFC 7628 
> examples is incorrect, it should be "n,a=test at example.com,^A..." 
> instead) and simpler auth server implementation.
> 
> I've provided proof-of-concept as a separate patch below.
> 
> What do you think about this approach?

Just for the record, below are login decoding fixes for the 
proof-of-concept patch in question, as well as test improvements 
and full merged patch to support XOAUTH2/OAUTHBEARER with parsing.

I'm going to commit this shortly unless there are objections.

[...]

> # HG changeset patch
> # User Maxim Dounin <mdounin at mdounin.ru>
> # Date 1716247681 -10800
> #      Tue May 21 02:28:01 2024 +0300
> # Node ID 377c966d4623000c0b9a5b2c0d47f9dd64b4cd9d
> # Parent  b06a347640e565012aced65e2a694a306ed2db5c
> Mail: parsing of XOAUTH2 and OAUTHBEARER.
> 
> For both mechanisms, the "Auth-User" header is set to the client identity
> obtained from the initial SASL response sent by the client, and the
> "Auth-Pass" header is set the Bearer token itself.
> 
> Additionally, only continuation responses correct for the particular
> mechanism are now accepted after errors ("AQ==" for OAUTHBEARER, empty
> line for XOAUTH2).
> 
> To be merged with the previous patch.

[...]

> +ngx_int_t
> +ngx_mail_auth_oauthbearer(ngx_mail_session_t *s, ngx_connection_t *c,
> +    ngx_uint_t n)
> +{
> +    u_char     *p, *last, *prev;
> +    ngx_str_t  *arg, oauth;
> +
> +    arg = s->args.elts;
> +
> +    if (s->auth_err.len) {
> +        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0,
> +                       "mail auth oauthbearer cancel");
> +
> +        if (s->args.nelts == 1
> +            && ngx_strncmp(arg[0].data, (u_char *) "AQ==", 4) == 0)
>          {
>              s->out = s->auth_err;
>              s->quit = s->auth_quit;
> @@ -782,16 +885,14 @@ ngx_mail_auth_oauth(ngx_mail_session_t *
>              return NGX_OK;
>          }
>  
> -    invalid:
> -
>          s->quit = s->auth_quit;
>          ngx_str_null(&s->auth_err);
>  
>          return NGX_MAIL_PARSE_INVALID_COMMAND;
>      }
>  
> -    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
> -                   "mail auth oauth: \"%V\" type %ui", &arg[n], auth_method);
> +    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
> +                   "mail auth oauthbearer: \"%V\"", &arg[n]);
>  
>      oauth.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
>      if (oauth.data == NULL) {
> @@ -801,19 +902,113 @@ ngx_mail_auth_oauth(ngx_mail_session_t *
>      if (ngx_decode_base64(&oauth, &arg[n]) != NGX_OK) {
>          ngx_log_error(NGX_LOG_INFO, c->log, 0,
>                        "client sent invalid base64 encoding in "
> -                      "AUTH XOAUTH2/OAUTHBEARER command");
> +                      "AUTH OAUTHBEARER command");
> +        return NGX_MAIL_PARSE_INVALID_COMMAND;
> +    }
> +
> +    /*
> +     * RFC 7628
> +     * "n,a=user at example.com,^A...^Aauth=Bearer <token>^A^A"
> +     */
> +
> +    p = oauth.data;
> +    last = p + oauth.len;
> +
> +    s->login.len = 0;
> +    prev = NULL;
> +
> +    while (p < last) {
> +        if (*p == ',') {
> +            if (prev
> +                && (size_t) (p - prev) > sizeof("a=") - 1
> +                && ngx_strncasecmp(prev, (u_char *) "a=", sizeof("a=") - 1)
> +                   == 0)
> +            {
> +                s->login.len = p - prev - (sizeof("a=") - 1);
> +                s->login.data = prev + sizeof("a=") - 1;
> +                break;
> +            }
> +
> +            p++;
> +            prev = p;
> +            continue;
> +        }
> +
> +        if (*p == '\1') {
> +            break;
> +        }
> +
> +        p++;
> +    }
> +
> +    if (s->login.len == 0) {
> +        ngx_log_error(NGX_LOG_INFO, c->log, 0,
> +                      "client sent invalid login in AUTH OAUTHBEARER command");
>          return NGX_MAIL_PARSE_INVALID_COMMAND;
>      }
>  
> -    s->passwd.len = oauth.len;
> -    s->passwd.data = oauth.data;
> +    s->passwd.len = 0;
> +    prev = NULL;
>  
> -    ngx_str_null(&s->login);
> +    while (p < last) {
> +        if (*p == '\1') {
> +            if (prev
> +                && (size_t) (p - prev) > sizeof("auth=Bearer ") - 1
> +                && ngx_strncasecmp(prev, (u_char *) "auth=Bearer ",
> +                                   sizeof("auth=Bearer ") - 1)
> +                   == 0)
> +            {
> +                s->passwd.len = p - prev - (sizeof("auth=Bearer ") - 1);
> +                s->passwd.data = prev + sizeof("auth=Bearer ") - 1;
> +                break;
> +            }
> +
> +            p++;
> +            prev = p;
> +            continue;
> +        }
> +
> +        p++;
> +    }
> +
> +    if (s->passwd.len == 0) {
> +        ngx_log_error(NGX_LOG_INFO, c->log, 0,
> +                      "client sent invalid token in AUTH OAUTHBEARER command");
> +        return NGX_MAIL_PARSE_INVALID_COMMAND;
> +    }
>  
> -    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
> -                   "mail auth oauth: \"%V\"", &s->passwd);
> +    /* decode =2C =3D in login */
> +
> +    p = s->login.data;
> +    last = s->login.data + s->login.len;
> +
> +    while (p < last) {
> +        if (*p == '=') {
> +            if (p[1] == '2' && (p[2] == 'C' || p[2] == 'c')) {
> +                *p = ',';
> +
> +            } else if (p[1] == '3' && (p[2] == 'D' || p[2] == 'd')) {
> +                *p = '=';
>  
> -    s->auth_method = auth_method;
> +            } else {
> +                ngx_log_error(NGX_LOG_INFO, c->log, 0,
> +                              "client sent invalid login in "
> +                              "AUTH OAUTHBEARER command");
> +                return NGX_MAIL_PARSE_INVALID_COMMAND;
> +            }
> +
> +            p += 3;
> +            continue;
> +        }
> +
> +        p++;
> +    }
> +
> +    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
> +                   "mail auth oauthbearer: \"%V\" \"%V\"",
> +                   &s->login, &s->passwd);
> +
> +    s->auth_method = NGX_MAIL_AUTH_OAUTHBEARER;
>  
>      return NGX_DONE;
>  }

Login decoding is wrong here, here is a fix:

diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -865,7 +865,7 @@ ngx_int_t
 ngx_mail_auth_oauthbearer(ngx_mail_session_t *s, ngx_connection_t *c,
     ngx_uint_t n)
 {
-    u_char     *p, *last, *prev;
+    u_char     *p, *d, *last, *prev;
     ngx_str_t  *arg, oauth;
 
     arg = s->args.elts;
@@ -980,15 +980,22 @@ ngx_mail_auth_oauthbearer(ngx_mail_sessi
     /* decode =2C =3D in login */
 
     p = s->login.data;
+    d = s->login.data;
     last = s->login.data + s->login.len;
 
     while (p < last) {
         if (*p == '=') {
+
+            /*
+             * login is always followed by other data,
+             * so p[1] and p[2] can be checked directly
+             */
+
             if (p[1] == '2' && (p[2] == 'C' || p[2] == 'c')) {
-                *p = ',';
+                *d++ = ',';
 
             } else if (p[1] == '3' && (p[2] == 'D' || p[2] == 'd')) {
-                *p = '=';
+                *d++ = '=';
 
             } else {
                 ngx_log_error(NGX_LOG_INFO, c->log, 0,
@@ -1001,9 +1008,11 @@ ngx_mail_auth_oauthbearer(ngx_mail_sessi
             continue;
         }
 
-        p++;
+        *d++ = *p++;
     }
 
+    s->login.len = d - s->login.data;
+
     ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
                    "mail auth oauthbearer: \"%V\" \"%V\"",
                    &s->login, &s->passwd);

And patch for tests:

diff --git a/mail_oauth.t b/mail_oauth.t
--- a/mail_oauth.t
+++ b/mail_oauth.t
@@ -71,17 +71,18 @@ http {
 	smtp %%PORT_8026%%;
     }
 
-    map $http_auth_pass $reply {
-	~secretok OK;
+    map $http_auth_user:$http_auth_pass $reply {
+	test at example.com:secretok OK;
+	test=, at example.com:secretok OK;
 	default auth-failed;
     }
+
     map $http_auth_pass $passw {
-	~secretok secret;
-	default "";
+	secretok secret;
     }
+
     map $http_auth_pass $sasl {
-	~saslfail "eyJzY2hlbWVzIjoiQmVhcmVyIiwic3RhdHVzIjoiNDAwIn0=";
-	default "";
+	saslfail "eyJzY2hlbWVzIjoiQmVhcmVyIiwic3RhdHVzIjoiNDAwIn0=";
     }
 
     server {
@@ -92,7 +93,6 @@ http {
             add_header Auth-Status $reply;
             add_header Auth-Server 127.0.0.1;
             add_header Auth-Port $proxy_port;
-            add_header Auth-User test at example.com;
             add_header Auth-Pass $passw;
             add_header Auth-Wait 1;
             add_header Auth-Error-SASL $sasl;
@@ -106,7 +106,7 @@ EOF
 $t->run_daemon(\&Test::Nginx::IMAP::imap_test_daemon);
 $t->run_daemon(\&Test::Nginx::POP3::pop3_test_daemon);
 $t->run_daemon(\&Test::Nginx::SMTP::smtp_test_daemon);
-$t->try_run('no oauth support')->plan(47);
+$t->try_run('no oauth support')->plan(48);
 
 $t->waitforsocket('127.0.0.1:' . port(8144));
 $t->waitforsocket('127.0.0.1:' . port(8111));
@@ -123,6 +123,8 @@ EOF
 my $s;
 my $token = encode_base64(
 	"n,a=test\@example.com,\001auth=Bearer secretok\001\001", '');
+my $token_escaped = encode_base64(
+	"n,a=test=3D=2C\@example.com,\001auth=Bearer secretok\001\001", '');
 my $token_saslfail = encode_base64(
 	"n,a=test\@example.com,\001auth=Bearer saslfail\001\001", '');
 my $token_bad = encode_base64(
@@ -144,6 +146,11 @@ my $token_xoauth2_bad = encode_base64(
 
 $s = Test::Nginx::IMAP->new();
 $s->read();
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_escaped);
+$s->ok('imap oauthbearer escaped login');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
 $s->send('1 AUTHENTICATE OAUTHBEARER');
 $s->check(qr/\+ /, 'imap oauthbearer challenge');
 $s->send($token);


Full XOAUTH2/OAUTHBEARER patch with parsing:

# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1717195412 -10800
#      Sat Jun 01 01:43:32 2024 +0300
# Node ID a87a01815c540faf782e96078ddd303adef2175d
# Parent  e73875d3d33e07948136a5eec2d313d6ffdfbe72
Mail: added support for XOAUTH2 and OAUTHBEARER authentication.

This patch adds support for the OAUTHBEARER SASL mechanism as defined
by RFC 7628, as well as pre-RFC XOAUTH2 SASL mechanism.  For both
mechanisms, the "Auth-User" header is set to the client identity
obtained from the initial SASL response sent by the client, and the
"Auth-Pass" header is set to the Bearer token itself.

The auth server may return the "Auth-Error-SASL" header, which is
passed to the client as an additional SASL challenge.  It is expected
to contain mechanism-specific error details, base64-encoded.  After
the client responds (with an empty SASL response for XAUTH2, or with
"AQ==" dummy response for OAUTHBEARER), the error message from the
"Auth-Status" header is sent.

Based on a patch by Rob Mueller.

diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -141,7 +141,9 @@ typedef enum {
     ngx_pop3_auth_login_password,
     ngx_pop3_auth_plain,
     ngx_pop3_auth_cram_md5,
-    ngx_pop3_auth_external
+    ngx_pop3_auth_external,
+    ngx_pop3_auth_xoauth2,
+    ngx_pop3_auth_oauthbearer
 } ngx_pop3_state_e;
 
 
@@ -152,6 +154,8 @@ typedef enum {
     ngx_imap_auth_plain,
     ngx_imap_auth_cram_md5,
     ngx_imap_auth_external,
+    ngx_imap_auth_xoauth2,
+    ngx_imap_auth_oauthbearer,
     ngx_imap_login,
     ngx_imap_user,
     ngx_imap_passwd
@@ -165,6 +169,8 @@ typedef enum {
     ngx_smtp_auth_plain,
     ngx_smtp_auth_cram_md5,
     ngx_smtp_auth_external,
+    ngx_smtp_auth_xoauth2,
+    ngx_smtp_auth_oauthbearer,
     ngx_smtp_helo,
     ngx_smtp_helo_xclient,
     ngx_smtp_helo_auth,
@@ -212,8 +218,9 @@ typedef struct {
     unsigned                no_sync_literal:1;
     unsigned                starttls:1;
     unsigned                esmtp:1;
-    unsigned                auth_method:3;
+    unsigned                auth_method:4;
     unsigned                auth_wait:1;
+    unsigned                auth_quit:1;
 
     ngx_str_t               login;
     ngx_str_t               passwd;
@@ -229,6 +236,8 @@ typedef struct {
     ngx_str_t               smtp_from;
     ngx_str_t               smtp_to;
 
+    ngx_str_t               auth_err;
+
     ngx_str_t               cmd;
 
     ngx_uint_t              command;
@@ -303,15 +312,19 @@ typedef struct {
 #define NGX_MAIL_AUTH_APOP              3
 #define NGX_MAIL_AUTH_CRAM_MD5          4
 #define NGX_MAIL_AUTH_EXTERNAL          5
-#define NGX_MAIL_AUTH_NONE              6
+#define NGX_MAIL_AUTH_XOAUTH2           6
+#define NGX_MAIL_AUTH_OAUTHBEARER       7
+#define NGX_MAIL_AUTH_NONE              8
 
 
-#define NGX_MAIL_AUTH_PLAIN_ENABLED     0x0002
-#define NGX_MAIL_AUTH_LOGIN_ENABLED     0x0004
-#define NGX_MAIL_AUTH_APOP_ENABLED      0x0008
-#define NGX_MAIL_AUTH_CRAM_MD5_ENABLED  0x0010
-#define NGX_MAIL_AUTH_EXTERNAL_ENABLED  0x0020
-#define NGX_MAIL_AUTH_NONE_ENABLED      0x0040
+#define NGX_MAIL_AUTH_PLAIN_ENABLED        0x0002
+#define NGX_MAIL_AUTH_LOGIN_ENABLED        0x0004
+#define NGX_MAIL_AUTH_APOP_ENABLED         0x0008
+#define NGX_MAIL_AUTH_CRAM_MD5_ENABLED     0x0010
+#define NGX_MAIL_AUTH_EXTERNAL_ENABLED     0x0020
+#define NGX_MAIL_AUTH_XOAUTH2_ENABLED      0x0040
+#define NGX_MAIL_AUTH_OAUTHBEARER_ENABLED  0x0080
+#define NGX_MAIL_AUTH_NONE_ENABLED         0x0100
 
 
 #define NGX_MAIL_PARSE_INVALID_COMMAND  20
@@ -399,6 +412,10 @@ ngx_int_t ngx_mail_auth_cram_md5_salt(ng
 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);
+ngx_int_t ngx_mail_auth_xoauth2(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n);
+ngx_int_t ngx_mail_auth_oauthbearer(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n);
 ngx_int_t ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c);
 
 void ngx_mail_send(ngx_event_t *wev);
diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c
+++ b/src/mail/ngx_mail_auth_http_module.c
@@ -53,6 +53,7 @@ struct ngx_mail_auth_http_ctx_s {
     ngx_str_t                       err;
     ngx_str_t                       errmsg;
     ngx_str_t                       errcode;
+    ngx_str_t                       errsasl;
 
     time_t                          sleep;
 
@@ -67,6 +68,7 @@ static void ngx_mail_auth_http_ignore_st
 static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx);
 static void ngx_mail_auth_sleep_handler(ngx_event_t *rev);
+static void ngx_mail_auth_send_error(ngx_mail_session_t *s);
 static ngx_int_t ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx);
 static void ngx_mail_auth_http_block_read(ngx_event_t *rev);
@@ -152,6 +154,8 @@ static ngx_str_t   ngx_mail_auth_http_me
     ngx_string("apop"),
     ngx_string("cram-md5"),
     ngx_string("external"),
+    ngx_string("xoauth2"),
+    ngx_string("oauthbearer"),
     ngx_string("none")
 };
 
@@ -677,6 +681,51 @@ ngx_mail_auth_http_process_headers(ngx_m
                 continue;
             }
 
+            if (len == sizeof("Auth-Error-SASL") - 1
+                && ngx_strncasecmp(ctx->header_name_start,
+                                   (u_char *) "Auth-Error-SASL",
+                                   sizeof("Auth-Error-SASL") - 1)
+                   == 0)
+            {
+                if (s->auth_method != NGX_MAIL_AUTH_XOAUTH2
+                    && s->auth_method != NGX_MAIL_AUTH_OAUTHBEARER)
+                {
+                    continue;
+                }
+
+                len = ctx->header_end - ctx->header_start;
+
+                if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
+                    size = len + sizeof("334 " CRLF) - 1;
+
+                } else {
+                    size = len + sizeof("+ " CRLF) - 1;
+                }
+
+                p = ngx_pnalloc(s->connection->pool, size);
+                if (p == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ctx->errsasl.len = size;
+                ctx->errsasl.data = p;
+
+                if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
+                    *p++ = '3'; *p++ = '3'; *p++ = '4'; *p++ = ' ';
+
+                } else {
+                    *p++ = '+'; *p++ = ' ';
+                }
+
+                p = ngx_cpymem(p, ctx->header_start, len);
+                *p++ = CR; *p = LF;
+
+                continue;
+            }
+
             /* ignore other headers */
 
             continue;
@@ -717,14 +766,15 @@ ngx_mail_auth_http_process_headers(ngx_m
                     *p++ = CR; *p = LF;
                 }
 
-                s->out = ctx->err;
+                s->out = ctx->errsasl;
+                s->auth_err = ctx->err;
                 timer = ctx->sleep;
 
                 ngx_destroy_pool(ctx->pool);
 
                 if (timer == 0) {
-                    s->quit = 1;
-                    ngx_mail_send(s->connection->write);
+                    s->auth_quit = 1;
+                    ngx_mail_auth_send_error(s);
                     return;
                 }
 
@@ -858,9 +908,8 @@ ngx_mail_auth_http_process_headers(ngx_m
 static void
 ngx_mail_auth_sleep_handler(ngx_event_t *rev)
 {
-    ngx_connection_t          *c;
-    ngx_mail_session_t        *s;
-    ngx_mail_core_srv_conf_t  *cscf;
+    ngx_connection_t    *c;
+    ngx_mail_session_t  *s;
 
     ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail auth sleep handler");
 
@@ -877,33 +926,7 @@ ngx_mail_auth_sleep_handler(ngx_event_t 
             return;
         }
 
-        cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
-        rev->handler = cscf->protocol->auth_state;
-
-        s->mail_state = 0;
-        s->auth_method = NGX_MAIL_AUTH_PLAIN;
-        s->tag.len = 0;
-
-        c->log->action = "in auth state";
-
-        ngx_mail_send(c->write);
-
-        if (c->destroyed) {
-            return;
-        }
-
-        ngx_add_timer(rev, cscf->timeout);
-
-        if (rev->ready) {
-            rev->handler(rev);
-            return;
-        }
-
-        if (ngx_handle_read_event(rev, 0) != NGX_OK) {
-            ngx_mail_close_connection(c);
-        }
-
+        ngx_mail_auth_send_error(s);
         return;
     }
 
@@ -915,6 +938,57 @@ ngx_mail_auth_sleep_handler(ngx_event_t 
 }
 
 
+static void
+ngx_mail_auth_send_error(ngx_mail_session_t *s)
+{
+    ngx_event_t               *rev;
+    ngx_connection_t          *c;
+    ngx_mail_core_srv_conf_t  *cscf;
+
+    c = s->connection;
+    rev = c->read;
+
+    cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+    rev->handler = cscf->protocol->auth_state;
+
+    s->auth_method = NGX_MAIL_AUTH_PLAIN;
+
+    c->log->action = "in auth state";
+
+    if (s->out.len == 0) {
+        s->out = s->auth_err;
+        s->quit = s->auth_quit;
+        ngx_str_null(&s->auth_err);
+
+        s->state = 0;
+        s->mail_state = 0;
+        s->tag.len = 0;
+
+    } else {
+        s->auth_err.len -= s->tag.len;
+        s->auth_err.data += s->tag.len;
+    }
+
+    ngx_mail_send(c->write);
+
+    if (c->destroyed) {
+        return;
+    }
+
+    ngx_add_timer(rev, cscf->timeout);
+
+    if (rev->ready) {
+        rev->handler(rev);
+        return;
+    }
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        ngx_mail_close_connection(c);
+    }
+}
+
+
 static ngx_int_t
 ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx)
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -755,6 +755,274 @@ ngx_mail_auth_external(ngx_mail_session_
 }
 
 
+ngx_int_t
+ngx_mail_auth_xoauth2(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n)
+{
+    u_char     *p, *last;
+    ngx_str_t  *arg, oauth;
+
+    arg = s->args.elts;
+
+    if (s->auth_err.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                       "mail auth xoauth2 cancel");
+
+        if (s->args.nelts == 1 && arg[0].len == 0) {
+            s->out = s->auth_err;
+            s->quit = s->auth_quit;
+            s->state = 0;
+            s->mail_state = 0;
+            ngx_str_null(&s->auth_err);
+            return NGX_OK;
+        }
+
+        s->quit = s->auth_quit;
+        ngx_str_null(&s->auth_err);
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth xoauth2: \"%V\"", &arg[n]);
+
+    oauth.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (oauth.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&oauth, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid base64 encoding in "
+                      "AUTH XOAUTH2 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    /*
+     * https://developers.google.com/gmail/imap/xoauth2-protocol
+     * "user=" {User} "^Aauth=Bearer " {token} "^A^A"
+     */
+
+    p = oauth.data;
+    last = p + oauth.len;
+
+    while (p < last) {
+        if (*p++ == '\1') {
+            s->login.len = p - oauth.data - 1;
+            s->login.data = oauth.data;
+            s->passwd.len = last - p;
+            s->passwd.data = p;
+            break;
+        }
+    }
+
+    if (s->login.len < sizeof("user=") - 1
+        || ngx_strncasecmp(s->login.data, (u_char *) "user=",
+                           sizeof("user=") - 1)
+           != 0)
+    {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid login in AUTH XOAUTH2 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->login.len -= sizeof("user=") - 1;
+    s->login.data += sizeof("user=") - 1;
+
+    if (s->passwd.len < sizeof("auth=Bearer ") - 1
+        || ngx_strncasecmp(s->passwd.data, (u_char *) "auth=Bearer ",
+                           sizeof("auth=Bearer ") - 1)
+           != 0)
+    {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid token in AUTH XOAUTH2 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->passwd.len -= sizeof("auth=Bearer ") - 1;
+    s->passwd.data += sizeof("auth=Bearer ") - 1;
+
+    if (s->passwd.len < 2
+        || s->passwd.data[s->passwd.len - 2] != '\1'
+        || s->passwd.data[s->passwd.len - 1] != '\1')
+    {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid token in AUTH XOAUTH2 command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->passwd.len -= 2;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth xoauth2: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_XOAUTH2;
+
+    return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_oauthbearer(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n)
+{
+    u_char     *p, *d, *last, *prev;
+    ngx_str_t  *arg, oauth;
+
+    arg = s->args.elts;
+
+    if (s->auth_err.len) {
+        ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                       "mail auth oauthbearer cancel");
+
+        if (s->args.nelts == 1
+            && ngx_strncmp(arg[0].data, (u_char *) "AQ==", 4) == 0)
+        {
+            s->out = s->auth_err;
+            s->quit = s->auth_quit;
+            s->state = 0;
+            s->mail_state = 0;
+            ngx_str_null(&s->auth_err);
+            return NGX_OK;
+        }
+
+        s->quit = s->auth_quit;
+        ngx_str_null(&s->auth_err);
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth oauthbearer: \"%V\"", &arg[n]);
+
+    oauth.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+    if (oauth.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_decode_base64(&oauth, &arg[n]) != NGX_OK) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid base64 encoding in "
+                      "AUTH OAUTHBEARER command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    /*
+     * RFC 7628
+     * "n,a=user at example.com,^A...^Aauth=Bearer <token>^A^A"
+     */
+
+    p = oauth.data;
+    last = p + oauth.len;
+
+    s->login.len = 0;
+    prev = NULL;
+
+    while (p < last) {
+        if (*p == ',') {
+            if (prev
+                && (size_t) (p - prev) > sizeof("a=") - 1
+                && ngx_strncasecmp(prev, (u_char *) "a=", sizeof("a=") - 1)
+                   == 0)
+            {
+                s->login.len = p - prev - (sizeof("a=") - 1);
+                s->login.data = prev + sizeof("a=") - 1;
+                break;
+            }
+
+            p++;
+            prev = p;
+            continue;
+        }
+
+        if (*p == '\1') {
+            break;
+        }
+
+        p++;
+    }
+
+    if (s->login.len == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid login in AUTH OAUTHBEARER command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->passwd.len = 0;
+    prev = NULL;
+
+    while (p < last) {
+        if (*p == '\1') {
+            if (prev
+                && (size_t) (p - prev) > sizeof("auth=Bearer ") - 1
+                && ngx_strncasecmp(prev, (u_char *) "auth=Bearer ",
+                                   sizeof("auth=Bearer ") - 1)
+                   == 0)
+            {
+                s->passwd.len = p - prev - (sizeof("auth=Bearer ") - 1);
+                s->passwd.data = prev + sizeof("auth=Bearer ") - 1;
+                break;
+            }
+
+            p++;
+            prev = p;
+            continue;
+        }
+
+        p++;
+    }
+
+    if (s->passwd.len == 0) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "client sent invalid token in AUTH OAUTHBEARER command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    /* decode =2C =3D in login */
+
+    p = s->login.data;
+    d = s->login.data;
+    last = s->login.data + s->login.len;
+
+    while (p < last) {
+        if (*p == '=') {
+
+            /*
+             * login is always followed by other data,
+             * so p[1] and p[2] can be checked directly
+             */
+
+            if (p[1] == '2' && (p[2] == 'C' || p[2] == 'c')) {
+                *d++ = ',';
+
+            } else if (p[1] == '3' && (p[2] == 'D' || p[2] == 'd')) {
+                *d++ = '=';
+
+            } else {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client sent invalid login in "
+                              "AUTH OAUTHBEARER command");
+                return NGX_MAIL_PARSE_INVALID_COMMAND;
+            }
+
+            p += 3;
+            continue;
+        }
+
+        *d++ = *p++;
+    }
+
+    s->login.len = d - s->login.data;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth oauthbearer: \"%V\" \"%V\"",
+                   &s->login, &s->passwd);
+
+    s->auth_method = NGX_MAIL_AUTH_OAUTHBEARER;
+
+    return NGX_DONE;
+}
+
+
 void
 ngx_mail_send(ngx_event_t *wev)
 {
@@ -919,13 +1187,17 @@ ngx_mail_auth(ngx_mail_session_t *s, ngx
 {
     s->args.nelts = 0;
 
-    if (s->buffer->pos == s->buffer->last) {
-        s->buffer->pos = s->buffer->start;
-        s->buffer->last = s->buffer->start;
+    if (s->state) {
+        /* preserve tag */
+        s->arg_start = s->buffer->pos;
+
+    } else {
+        if (s->buffer->pos == s->buffer->last) {
+            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);
     }
diff --git a/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c
--- a/src/mail/ngx_mail_imap_handler.c
+++ b/src/mail/ngx_mail_imap_handler.c
@@ -220,6 +220,14 @@ ngx_mail_imap_auth_state(ngx_event_t *re
         case ngx_imap_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_imap_auth_xoauth2:
+            rc = ngx_mail_auth_xoauth2(s, c, 0);
+            break;
+
+        case ngx_imap_auth_oauthbearer:
+            rc = ngx_mail_auth_oauthbearer(s, c, 0);
+            break;
         }
 
     } else if (rc == NGX_IMAP_NEXT) {
@@ -432,6 +440,38 @@ ngx_mail_imap_authenticate(ngx_mail_sess
         s->mail_state = ngx_imap_auth_external;
 
         return NGX_OK;
+
+    case NGX_MAIL_AUTH_XOAUTH2:
+
+        if (!(iscf->auth_methods & NGX_MAIL_AUTH_XOAUTH2_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_imap_auth_xoauth2;
+            return ngx_mail_auth_xoauth2(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, imap_plain_next);
+        s->mail_state = ngx_imap_auth_xoauth2;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_OAUTHBEARER:
+
+        if (!(iscf->auth_methods & NGX_MAIL_AUTH_OAUTHBEARER_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_imap_auth_oauthbearer;
+            return ngx_mail_auth_oauthbearer(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, imap_plain_next);
+        s->mail_state = ngx_imap_auth_oauthbearer;
+
+        return NGX_OK;
     }
 
     return rc;
diff --git a/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c
--- a/src/mail/ngx_mail_imap_module.c
+++ b/src/mail/ngx_mail_imap_module.c
@@ -30,6 +30,8 @@ static ngx_conf_bitmask_t  ngx_mail_imap
     { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
     { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
     { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_string("xoauth2"), NGX_MAIL_AUTH_XOAUTH2_ENABLED },
+    { ngx_string("oauthbearer"), NGX_MAIL_AUTH_OAUTHBEARER_ENABLED },
     { ngx_null_string, 0 }
 };
 
@@ -40,6 +42,8 @@ static ngx_str_t  ngx_mail_imap_auth_met
     ngx_null_string,  /* APOP */
     ngx_string("AUTH=CRAM-MD5"),
     ngx_string("AUTH=EXTERNAL"),
+    ngx_string("AUTH=XOAUTH2"),
+    ngx_string("AUTH=OAUTHBEARER"),
     ngx_null_string   /* NONE */
 };
 
@@ -182,7 +186,7 @@ ngx_mail_imap_merge_srv_conf(ngx_conf_t 
     }
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (m & conf->auth_methods) {
@@ -208,7 +212,7 @@ ngx_mail_imap_merge_srv_conf(ngx_conf_t 
     auth = p;
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (m & conf->auth_methods) {
diff --git a/src/mail/ngx_mail_parse.c b/src/mail/ngx_mail_parse.c
--- a/src/mail/ngx_mail_parse.c
+++ b/src/mail/ngx_mail_parse.c
@@ -953,6 +953,20 @@ ngx_mail_auth_parse(ngx_mail_session_t *
         return NGX_MAIL_PARSE_INVALID_COMMAND;
     }
 
+    if (arg[0].len == 7) {
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "XOAUTH2", 7) == 0) {
+
+            if (s->args.nelts == 1 || s->args.nelts == 2) {
+                return NGX_MAIL_AUTH_XOAUTH2;
+            }
+
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
     if (arg[0].len == 8) {
 
         if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
@@ -976,5 +990,19 @@ ngx_mail_auth_parse(ngx_mail_session_t *
         return NGX_MAIL_PARSE_INVALID_COMMAND;
     }
 
+    if (arg[0].len == 11) {
+
+        if (ngx_strncasecmp(arg[0].data, (u_char *) "OAUTHBEARER", 11) == 0) {
+
+            if (s->args.nelts == 1 || s->args.nelts == 2) {
+                return NGX_MAIL_AUTH_OAUTHBEARER;
+            }
+
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
     return NGX_MAIL_PARSE_INVALID_COMMAND;
 }
diff --git a/src/mail/ngx_mail_pop3_handler.c b/src/mail/ngx_mail_pop3_handler.c
--- a/src/mail/ngx_mail_pop3_handler.c
+++ b/src/mail/ngx_mail_pop3_handler.c
@@ -260,6 +260,14 @@ ngx_mail_pop3_auth_state(ngx_event_t *re
         case ngx_pop3_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_pop3_auth_xoauth2:
+            rc = ngx_mail_auth_xoauth2(s, c, 0);
+            break;
+
+        case ngx_pop3_auth_oauthbearer:
+            rc = ngx_mail_auth_oauthbearer(s, c, 0);
+            break;
         }
     }
 
@@ -553,6 +561,38 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s
         s->mail_state = ngx_pop3_auth_external;
 
         return NGX_OK;
+
+    case NGX_MAIL_AUTH_XOAUTH2:
+
+        if (!(pscf->auth_methods & NGX_MAIL_AUTH_XOAUTH2_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_pop3_auth_xoauth2;
+            return ngx_mail_auth_xoauth2(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, pop3_next);
+        s->mail_state = ngx_pop3_auth_xoauth2;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_OAUTHBEARER:
+
+        if (!(pscf->auth_methods & NGX_MAIL_AUTH_OAUTHBEARER_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_pop3_auth_oauthbearer;
+            return ngx_mail_auth_oauthbearer(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, pop3_next);
+        s->mail_state = ngx_pop3_auth_oauthbearer;
+
+        return NGX_OK;
     }
 
     return rc;
diff --git a/src/mail/ngx_mail_pop3_module.c b/src/mail/ngx_mail_pop3_module.c
--- a/src/mail/ngx_mail_pop3_module.c
+++ b/src/mail/ngx_mail_pop3_module.c
@@ -30,6 +30,8 @@ static ngx_conf_bitmask_t  ngx_mail_pop3
     { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
     { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
     { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_string("xoauth2"), NGX_MAIL_AUTH_XOAUTH2_ENABLED },
+    { ngx_string("oauthbearer"), NGX_MAIL_AUTH_OAUTHBEARER_ENABLED },
     { ngx_null_string, 0 }
 };
 
@@ -40,6 +42,8 @@ static ngx_str_t  ngx_mail_pop3_auth_met
     ngx_null_string,  /* APOP */
     ngx_string("CRAM-MD5"),
     ngx_string("EXTERNAL"),
+    ngx_string("XOAUTH2"),
+    ngx_string("OAUTHBEARER"),
     ngx_null_string   /* NONE */
 };
 
@@ -183,7 +187,7 @@ ngx_mail_pop3_merge_srv_conf(ngx_conf_t 
     size += sizeof("SASL") - 1 + sizeof(CRLF) - 1;
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
@@ -214,7 +218,7 @@ ngx_mail_pop3_merge_srv_conf(ngx_conf_t 
     p = ngx_cpymem(p, "SASL", sizeof("SASL") - 1);
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
@@ -254,7 +258,7 @@ ngx_mail_pop3_merge_srv_conf(ngx_conf_t 
            + sizeof("." CRLF) - 1;
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
@@ -279,7 +283,7 @@ ngx_mail_pop3_merge_srv_conf(ngx_conf_t 
                    sizeof("+OK methods supported:" CRLF) - 1);
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (ngx_mail_pop3_auth_methods_names[i].len == 0) {
diff --git a/src/mail/ngx_mail_smtp_handler.c b/src/mail/ngx_mail_smtp_handler.c
--- a/src/mail/ngx_mail_smtp_handler.c
+++ b/src/mail/ngx_mail_smtp_handler.c
@@ -548,6 +548,14 @@ ngx_mail_smtp_auth_state(ngx_event_t *re
         case ngx_smtp_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_smtp_auth_xoauth2:
+            rc = ngx_mail_auth_xoauth2(s, c, 0);
+            break;
+
+        case ngx_smtp_auth_oauthbearer:
+            rc = ngx_mail_auth_oauthbearer(s, c, 0);
+            break;
         }
     }
 
@@ -745,6 +753,38 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s
         s->mail_state = ngx_smtp_auth_external;
 
         return NGX_OK;
+
+    case NGX_MAIL_AUTH_XOAUTH2:
+
+        if (!(sscf->auth_methods & NGX_MAIL_AUTH_XOAUTH2_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_smtp_auth_xoauth2;
+            return ngx_mail_auth_xoauth2(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, smtp_next);
+        s->mail_state = ngx_smtp_auth_xoauth2;
+
+        return NGX_OK;
+
+    case NGX_MAIL_AUTH_OAUTHBEARER:
+
+        if (!(sscf->auth_methods & NGX_MAIL_AUTH_OAUTHBEARER_ENABLED)) {
+            return NGX_MAIL_PARSE_INVALID_COMMAND;
+        }
+
+        if (s->args.nelts == 2) {
+            s->mail_state = ngx_smtp_auth_oauthbearer;
+            return ngx_mail_auth_oauthbearer(s, c, 1);
+        }
+
+        ngx_str_set(&s->out, smtp_next);
+        s->mail_state = ngx_smtp_auth_oauthbearer;
+
+        return NGX_OK;
     }
 
     return rc;
diff --git a/src/mail/ngx_mail_smtp_module.c b/src/mail/ngx_mail_smtp_module.c
--- a/src/mail/ngx_mail_smtp_module.c
+++ b/src/mail/ngx_mail_smtp_module.c
@@ -22,6 +22,8 @@ static ngx_conf_bitmask_t  ngx_mail_smtp
     { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
     { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
     { ngx_string("external"), NGX_MAIL_AUTH_EXTERNAL_ENABLED },
+    { ngx_string("xoauth2"), NGX_MAIL_AUTH_XOAUTH2_ENABLED },
+    { ngx_string("oauthbearer"), NGX_MAIL_AUTH_OAUTHBEARER_ENABLED },
     { ngx_string("none"), NGX_MAIL_AUTH_NONE_ENABLED },
     { ngx_null_string, 0 }
 };
@@ -33,6 +35,8 @@ static ngx_str_t  ngx_mail_smtp_auth_met
     ngx_null_string,  /* APOP */
     ngx_string("CRAM-MD5"),
     ngx_string("EXTERNAL"),
+    ngx_string("XOAUTH2"),
+    ngx_string("OAUTHBEARER"),
     ngx_null_string   /* NONE */
 };
 
@@ -210,7 +214,7 @@ ngx_mail_smtp_merge_srv_conf(ngx_conf_t 
     auth_enabled = 0;
 
     for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-         m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+         m < NGX_MAIL_AUTH_NONE_ENABLED;
          m <<= 1, i++)
     {
         if (m & conf->auth_methods) {
@@ -253,7 +257,7 @@ ngx_mail_smtp_merge_srv_conf(ngx_conf_t 
         *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
 
         for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
-             m <= NGX_MAIL_AUTH_EXTERNAL_ENABLED;
+             m < NGX_MAIL_AUTH_NONE_ENABLED;
              m <<= 1, i++)
         {
             if (m & conf->auth_methods) {


-- 
Maxim Dounin
http://mdounin.ru/



More information about the nginx-devel mailing list