[nginx] Add support for XOAUTH2 and OAUTHBEARER authentication

Robert Mueller robm at fastmailteam.com
Mon May 13 05:41:45 UTC 2024


# HG changeset patch
# User Rob Mueller <robm at fastmailteam.com>
Add support for XOAUTH2 and OAUTHBEARER authentication

This patch adds support for RFC 7628 OAUTHBEARER style authentication
to the nginx mail proxy module for IMAP, POP and SMTP. To help with
legacy libraries, it also implements the pre-RFC XOAUTH2 protocol as well.

It adds ngx_*_auth_{oauthbearer,xoauth2} states, constants, handlers, etc.
The bearer token provided by the client is passed to the backend auth
server in the `Auth-Pass` header.

The auth server may return an additional optional response
header `Auth-Error-Sasl`. It's expected in the auth failure
case that the backend auth server will generate a base64
encoded JSON object that conforms to the error reporting in
https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2 in this
header.

If present, the value in this header is prefixed with a `+ ` (IMAP) or
`334 ` (SMTP or POP) and returned as the SASL response. We then wait for
a valid line from the client (which we ignore) and then we exit the SASL
mode and return back to standard protocol parsing. If we don't receive
a valid line, we terminate the connection. There's an example of this
looks like in https://datatracker.ietf.org/doc/html/rfc7628#section-4.3
---
 src/mail/ngx_mail.h                  |  30 ++--
 src/mail/ngx_mail_auth_http_module.c | 201 ++++++++++++++++++++++++++-
 src/mail/ngx_mail_handler.c          |  33 +++++
 src/mail/ngx_mail_imap_handler.c     |  30 ++++
 src/mail/ngx_mail_imap_module.c      |   8 +-
 src/mail/ngx_mail_parse.c            |  32 +++++
 src/mail/ngx_mail_pop3_handler.c     |  30 ++++
 src/mail/ngx_mail_pop3_module.c      |  12 +-
 src/mail/ngx_mail_smtp_handler.c     |  30 ++++
 src/mail/ngx_mail_smtp_module.c      |   8 +-
 10 files changed, 390 insertions(+), 24 deletions(-)

diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
index e0c62b7ab..ed95a5417 100644
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -140,7 +140,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;
 
 
@@ -151,6 +153,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
@@ -164,6 +168,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,
@@ -211,7 +217,7 @@ 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;
 
     ngx_str_t               login;
@@ -301,15 +307,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
@@ -397,6 +407,8 @@ ngx_int_t ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s,
 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_oauth(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n, ngx_uint_t auth_method);
 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
index 27f64b92e..543d1c4eb 100644
--- 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;
 
@@ -66,6 +67,7 @@ static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx);
 static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_read_sasl_end_handler(ngx_event_t *rev);
 static void ngx_mail_auth_sleep_handler(ngx_event_t *rev);
 static ngx_int_t ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
     ngx_mail_auth_http_ctx_t *ctx);
@@ -152,6 +154,8 @@ static ngx_str_t   ngx_mail_auth_http_method[] = {
     ngx_string("apop"),
     ngx_string("cram-md5"),
     ngx_string("external"),
+    ngx_string("xoauth2"),
+    ngx_string("oauthbearer"),
     ngx_string("none")
 };
 
@@ -677,6 +681,29 @@ ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
                 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)
+            {
+                ctx->errsasl.len = ctx->header_end - ctx->header_start;
+
+                ctx->errsasl.data = ngx_pnalloc(s->connection->pool,
+                                                ctx->errsasl.len);
+                if (ctx->errsasl.data == NULL) {
+                    ngx_close_connection(ctx->peer.connection);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(ctx->errsasl.data, ctx->header_start,
+                           ctx->errsasl.len);
+
+                continue;
+            }
+
             /* ignore other headers */
 
             continue;
@@ -716,13 +743,67 @@ ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
                     p = ngx_cpymem(p, ctx->errmsg.data, ctx->errmsg.len);
                     *p++ = CR; *p = LF;
                 }
+            }
 
-                s->out = ctx->err;
-                timer = ctx->sleep;
+            if (ctx->errsasl.len) {
 
-                ngx_destroy_pool(ctx->pool);
+                if (s->auth_method == NGX_MAIL_AUTH_XOAUTH2
+                    || s->auth_method == NGX_MAIL_AUTH_OAUTHBEARER)
+                {
+                    if (!ctx->err.len) {
+                        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                                      "auth http server %V returned SASL "
+                                      "error to auth success request",
+                                      ctx->peer.name);
+                        ngx_destroy_pool(ctx->pool);
+                        ngx_mail_session_internal_server_error(s);
+                        return;
+                    }
 
-                if (timer == 0) {
+                    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+                                  "client login sasl failed: \"%V\"", &ctx->errsasl);
+
+                    if (s->protocol == NGX_MAIL_IMAP_PROTOCOL) {
+                        size = ctx->errsasl.len + sizeof("+ " CRLF) - 1;
+                    } else {
+                        size = ctx->errsasl.len + sizeof("334 " CRLF) - 1;
+                    }
+
+                    p = ngx_pnalloc(s->connection->pool, size);
+                    if (p == NULL) {
+                        ngx_destroy_pool(ctx->pool);
+                        ngx_mail_session_internal_server_error(s);
+                        return;
+                    }
+
+                    if (s->protocol == NGX_MAIL_IMAP_PROTOCOL) {
+                        *p++ = '+'; *p++ = ' ';
+                    } else {
+                        *p++ = '3'; *p++ = '3'; *p++ = '4'; *p++ = ' ';
+                    }
+                    p = ngx_cpymem(p, ctx->errsasl.data, ctx->errsasl.len);
+                    *p++ = CR; *p++ = LF;
+
+                    ctx->errsasl.data = p - size;
+                    ctx->errsasl.len = size;
+
+                } else {
+                    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                                  "auth http server %V returned SASL "
+                                  "error to non-SASL auth request",
+                                  ctx->peer.name);
+                    ngx_destroy_pool(ctx->pool);
+                    ngx_mail_session_internal_server_error(s);
+                    return;
+                }
+            }
+
+            if (ctx->err.len || ctx->errsasl.len) {
+                s->out = ctx->errsasl.len ? ctx->errsasl : ctx->err;
+                timer = ctx->sleep;
+
+                if (timer == 0 && !ctx->errsasl.len) {
+                    ngx_destroy_pool(ctx->pool);
                     s->quit = 1;
                     ngx_mail_send(s->connection->write);
                     return;
@@ -738,9 +819,8 @@ ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
             if (s->auth_wait) {
                 timer = ctx->sleep;
 
-                ngx_destroy_pool(ctx->pool);
-
                 if (timer == 0) {
+                    ngx_destroy_pool(ctx->pool);
                     ngx_mail_auth_http_init(s);
                     return;
                 }
@@ -854,29 +934,47 @@ ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
     }
 }
 
-
 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_mail_auth_http_ctx_t  *ctx;
 
     ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail auth sleep handler");
 
     c = rev->data;
     s = c->data;
 
+    ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
     if (rev->timedout) {
 
         rev->timedout = 0;
 
         if (s->auth_wait) {
             s->auth_wait = 0;
+            ngx_destroy_pool(ctx->pool);
             ngx_mail_auth_http_init(s);
             return;
         }
 
+        if (ctx->errsasl.len) {
+            ngx_mail_send(c->write);
+
+            if (c->destroyed) {
+                return;
+            }
+
+            s->buffer->pos = s->buffer->start;
+            s->buffer->last = s->buffer->start;
+            s->connection->read->handler = ngx_mail_read_sasl_end_handler;
+            return;
+        }
+
+        ngx_destroy_pool(ctx->pool);
+
         cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
         rev->handler = cscf->protocol->auth_state;
@@ -908,11 +1006,100 @@ ngx_mail_auth_sleep_handler(ngx_event_t *rev)
 
     if (rev->active) {
         if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+            ngx_destroy_pool(ctx->pool);
             ngx_mail_close_connection(c);
         }
     }
 }
 
+static void
+ngx_mail_read_sasl_end_handler(ngx_event_t *rev)
+{
+    ssize_t                    n, len;
+    ngx_connection_t           *c;
+    ngx_mail_session_t         *s;
+    u_char                     *p;
+    ngx_mail_auth_http_ctx_t   *ctx;
+
+    c = rev->data;
+    s = c->data;
+
+    ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+    n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+    if (n == NGX_ERROR || n == 0) {
+        goto error;
+    }
+
+    if (n > 0) {
+        s->buffer->last += n;
+    }
+
+    if (n == NGX_AGAIN) {
+        if (s->buffer->pos == s->buffer->last) {
+            goto error;
+        }
+    }
+
+    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+        goto error;
+    }
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        if (*p == LF && p > s->buffer->start && *(p-1) == CR) {
+            /* Three valid client lines. "*" on it's own is a cancel, see
+             * rfc3501 (imap), rfc4954 (smtp), rfc5034 (pop3), "AQ=="
+             * is client dummy response, see rfc7628, or empty line,
+             * see xoauth2 */
+            len = p - s->buffer->start - 1;
+            if (len == 0) {
+                goto done;
+            }
+            if (len == 1
+                && s->buffer->start[0] == '*')
+            {
+                goto done;
+            }
+            if (len == 4
+                && s->buffer->start[0] == 'A'
+                && s->buffer->start[1] == 'Q'
+                && s->buffer->start[2] == '='
+                && s->buffer->start[3] == '=')
+            {
+                goto done;
+            }
+
+            goto error;
+        }
+    }
+
+    s->buffer->pos = p;
+    return;
+
+done:
+    s->buffer->pos = s->buffer->start;
+    s->buffer->last = s->buffer->start;
+
+    ctx->errsasl.len = 0;
+    rev->timedout = 1;
+
+    s->out = ctx->err;
+    ngx_mail_send(c->write);
+
+    if (c->destroyed) {
+        return;
+    }
+
+    s->connection->read->handler = ngx_mail_auth_sleep_handler;
+    return;
+
+error:
+    ngx_destroy_pool(ctx->pool);
+    ngx_mail_close_connection(c);
+    return;
+}
+
 
 static ngx_int_t
 ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
index 1167df3fb..e3add131b 100644
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -752,6 +752,39 @@ ngx_mail_auth_external(ngx_mail_session_t *s, ngx_connection_t *c,
     return NGX_DONE;
 }
 
+ngx_int_t
+ngx_mail_auth_oauth(ngx_mail_session_t *s, ngx_connection_t *c,
+    ngx_uint_t n, ngx_uint_t auth_method)
+{
+    ngx_str_t  *arg, oauth;
+
+    arg = s->args.elts;
+
+    ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth oauth: \"%V\" type %ui", &arg[n], auth_method);
+
+    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/OAUTHBEARER command");
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
+    s->passwd.len = oauth.len;
+    s->passwd.data = oauth.data;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+                   "mail auth oauth: \"%V\"", &s->passwd);
+
+    s->auth_method = auth_method;
+
+    return NGX_DONE;
+}
+
 
 void
 ngx_mail_send(ngx_event_t *wev)
diff --git a/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c
index 291e87a4d..93e9e35fe 100644
--- a/src/mail/ngx_mail_imap_handler.c
+++ b/src/mail/ngx_mail_imap_handler.c
@@ -219,6 +219,14 @@ ngx_mail_imap_auth_state(ngx_event_t *rev)
         case ngx_imap_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_imap_auth_xoauth2:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+            break;
+
+        case ngx_imap_auth_oauthbearer:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+            break;
         }
 
     } else if (rc == NGX_IMAP_NEXT) {
@@ -416,6 +424,28 @@ ngx_mail_imap_authenticate(ngx_mail_session_t *s, ngx_connection_t *c)
         ngx_str_set(&s->out, imap_username);
         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;
+        }
+
+        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;
+        }
+
+        ngx_str_set(&s->out, imap_plain_next);
+        s->mail_state = ngx_imap_auth_oauthbearer;
+
         return NGX_OK;
     }
 
diff --git a/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c
index 02c684cd4..eff5e2c49 100644
--- 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_auth_methods[] = {
     { 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_methods_names[] = {
     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 *cf, void *parent, void *child)
     }
 
     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 *cf, void *parent, void *child)
     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
index 4db1f18d3..3ad9032e2 100644
--- a/src/mail/ngx_mail_parse.c
+++ b/src/mail/ngx_mail_parse.c
@@ -946,6 +946,22 @@ ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
         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) {
+                return NGX_MAIL_AUTH_XOAUTH2;
+            }
+
+            if (s->args.nelts == 2) {
+                return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+            }
+        }
+
+        return NGX_MAIL_PARSE_INVALID_COMMAND;
+    }
+
     if (arg[0].len == 8) {
 
         if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
@@ -971,5 +987,21 @@ ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
         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) {
+                return NGX_MAIL_AUTH_OAUTHBEARER;
+            }
+
+            if (s->args.nelts == 2) {
+                return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+            }
+        }
+
+        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
index 226e7419b..8865aa637 100644
--- a/src/mail/ngx_mail_pop3_handler.c
+++ b/src/mail/ngx_mail_pop3_handler.c
@@ -259,6 +259,14 @@ ngx_mail_pop3_auth_state(ngx_event_t *rev)
         case ngx_pop3_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_pop3_auth_xoauth2:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+            break;
+
+        case ngx_pop3_auth_oauthbearer:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+            break;
         }
     }
 
@@ -537,6 +545,28 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c)
         ngx_str_set(&s->out, pop3_username);
         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;
+        }
+
+        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;
+        }
+
+        ngx_str_set(&s->out, pop3_next);
+        s->mail_state = ngx_pop3_auth_oauthbearer;
+
         return NGX_OK;
     }
 
diff --git a/src/mail/ngx_mail_pop3_module.c b/src/mail/ngx_mail_pop3_module.c
index a257b5a70..14b14fb24 100644
--- 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_auth_methods[] = {
     { 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_methods_names[] = {
     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 *cf, void *parent, void *child)
     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 *cf, void *parent, void *child)
     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 *cf, void *parent, void *child)
            + 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 *cf, void *parent, void *child)
                    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
index e68ceedfd..885876fcb 100644
--- a/src/mail/ngx_mail_smtp_handler.c
+++ b/src/mail/ngx_mail_smtp_handler.c
@@ -547,6 +547,14 @@ ngx_mail_smtp_auth_state(ngx_event_t *rev)
         case ngx_smtp_auth_external:
             rc = ngx_mail_auth_external(s, c, 0);
             break;
+
+        case ngx_smtp_auth_xoauth2:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+            break;
+
+        case ngx_smtp_auth_oauthbearer:
+            rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+            break;
         }
     }
 
@@ -729,6 +737,28 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c)
         ngx_str_set(&s->out, smtp_username);
         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;
+        }
+
+        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;
+        }
+
+        ngx_str_set(&s->out, smtp_next);
+        s->mail_state = ngx_smtp_auth_oauthbearer;
+
         return NGX_OK;
     }
 
diff --git a/src/mail/ngx_mail_smtp_module.c b/src/mail/ngx_mail_smtp_module.c
index 0e05fdc03..2f4a1f960 100644
--- 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_auth_methods[] = {
     { 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_methods_names[] = {
     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 *cf, void *parent, void *child)
     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 *cf, void *parent, void *child)
         *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) {




More information about the nginx-devel mailing list