[nginx] Add support for XOAUTH2 and OAUTHBEARER authentication
Maxim Dounin
mdounin at mdounin.ru
Tue May 21 01:16:05 UTC 2024
Hello!
On Mon, May 13, 2024 at 03:41:45PM +1000, Robert Mueller wrote:
> # 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
[...]
Thanks for the patch.
Some problem as identified during review, in no particular order:
- SASL errors are returned incorrectly for POP3 (the code uses
SMTP-style "334 " challenge prefix instead of "+ " to be used in
POP3, see RFC 5034).
- The code maintains auth http context while auth sleep handler,
which is not really needed: it is only used for a flag ("SASL
response is being sent") and a pointer to non-SASL error response.
- Logic in auth_http looks overcomplicated, just "use
Auth-Error-SASL if its available and returning error" should be
enough, much like we do for the Auth-Error-Code header in SMTP.
- Without the Auth-Wait header, but with Auth-Error-SASL, the code
will use ngx_add_timer(ev, 0). While this works, this is
suboptimal.
- Without the Auth-Wait header, but with Auth-Error-SASL, the
connection won't be closed when the dummy response is received.
This makes it impossible to correctly return OAUTHBEARER error
without allowing yet another authentication attempt.
- Various issues with dummy response waiting:
- No timeouts are set while waiting for the dummy response
("AQ==" or an empty line), a DoS vector.
- The dummy response waiting code does not support pipelining.
- The dummy response waiting code does not recognize bare LF as
a line terminator.
- The dummy response waiting code closes connection on any
unexpected input from the client without any diagnostic.
- Formally, SASL abort might require different response codes.
Notably, in IMAP server must respond to "*" with "BAD", not
"NO" (note that examples in RFC 7628 are broken).
Overall, an approach taken to implement dummy response waiting
seems to correlate with the number of various issues introduced.
A better approach might be to implement dummy response waiting in
protocol-specific code, such as in the
ngx_mail_imap_parse_command() function.
- If disabled, XOAUTH2 and OAUTHBEARER mechanisms are still
allowed in the form with initial response. Likely copied from
the EXTERNAL mechanism, which contains the same bug (patch below).
- The s->login field needs to be cleared by the mechanism
(similarly, likely copied from the EXTERNAL mechanism which
doesn't clear s->passwd, patch below).
- Since s->login is not provided for the mechanisms, auth_http
probably needs to check if it is returned by the auth script,
similarly to s->passwd.
Below are patches to address identified issues in the EXTERNAL
mechanism and a reworked XOAUTH2/OAUTHBEARER patch, which tries to
address issues mentioned above. In particular, waiting for the
dummy SASL response is implemented by returning to the auth state
without clearing the state, so mechanisms can simply continue
handling additional SASL responses.
Please take a look.
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?
Patches for the code:
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716247666 -10800
# Tue May 21 02:27:46 2024 +0300
# Node ID e6aefee77e6c667412977b6af3fdb87aff73e514
# Parent 46ecad404a296042c0088e699f275a92758e5ab9
Mail: fixed EXTERNAL to be accepted only if enabled.
As originally implemented in 6774:bcb107bb89cd, it wasn't possible to
disable the EXTERNAL authentication method: it was always accepted
(but not advertised unless enabled). It is, however, believed that
it is better to reject attempts to use the disabled method, hence in
6869:b2915d99ee8d an attempt was made to address this. This attempt
was insufficient though: it was still possible to use the method as long
as initial SASL response was used.
With this patch both challenge-response and initial response forms are
disabled. Additionally, initial response handling for the PLAIN
authentication is removed from ngx_mail_auth_parse(), for consistency
and to don't provoke such bugs.
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
@@ -388,6 +388,10 @@ ngx_mail_imap_authenticate(ngx_mail_sess
case NGX_MAIL_AUTH_PLAIN:
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_plain(s, c, 1);
+ }
+
ngx_str_set(&s->out, imap_plain_next);
s->mail_state = ngx_imap_auth_plain;
@@ -420,6 +424,10 @@ ngx_mail_imap_authenticate(ngx_mail_sess
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_external(s, c, 1);
+ }
+
ngx_str_set(&s->out, imap_username);
s->mail_state = ngx_imap_auth_external;
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
@@ -934,13 +934,11 @@ ngx_mail_auth_parse(ngx_mail_session_t *
if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN", 5) == 0) {
- if (s->args.nelts == 1) {
+ if (s->args.nelts == 1 || s->args.nelts == 2) {
return NGX_MAIL_AUTH_PLAIN;
}
- if (s->args.nelts == 2) {
- return ngx_mail_auth_plain(s, c, 1);
- }
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
}
return NGX_MAIL_PARSE_INVALID_COMMAND;
@@ -959,13 +957,11 @@ ngx_mail_auth_parse(ngx_mail_session_t *
if (ngx_strncasecmp(arg[0].data, (u_char *) "EXTERNAL", 8) == 0) {
- if (s->args.nelts == 1) {
+ if (s->args.nelts == 1 || s->args.nelts == 2) {
return NGX_MAIL_AUTH_EXTERNAL;
}
- if (s->args.nelts == 2) {
- return ngx_mail_auth_external(s, c, 1);
- }
+ 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
@@ -517,6 +517,10 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s
case NGX_MAIL_AUTH_PLAIN:
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_plain(s, c, 1);
+ }
+
ngx_str_set(&s->out, pop3_next);
s->mail_state = ngx_pop3_auth_plain;
@@ -541,6 +545,10 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_external(s, c, 1);
+ }
+
ngx_str_set(&s->out, pop3_username);
s->mail_state = ngx_pop3_auth_external;
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
@@ -701,6 +701,10 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s
case NGX_MAIL_AUTH_PLAIN:
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_plain(s, c, 1);
+ }
+
ngx_str_set(&s->out, smtp_next);
s->mail_state = ngx_smtp_auth_plain;
@@ -733,6 +737,10 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s
return NGX_MAIL_PARSE_INVALID_COMMAND;
}
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_external(s, c, 1);
+ }
+
ngx_str_set(&s->out, smtp_username);
s->mail_state = ngx_smtp_auth_external;
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716247668 -10800
# Tue May 21 02:27:48 2024 +0300
# Node ID 6dbc239d5e2bdbf69baa7d4cbdfa3871cfe67bbf
# Parent e6aefee77e6c667412977b6af3fdb87aff73e514
Mail: fixed EXTERNAL auth to clear s->passwd.
The s->passwd field might be set after previous (failed) authentication
in the same session, and since EXTERNAL authentication did not touch it,
it was sent to the auth server.
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
@@ -744,6 +744,8 @@ ngx_mail_auth_external(ngx_mail_session_
s->login.len = external.len;
s->login.data = external.data;
+ ngx_str_null(&s->passwd);
+
ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
"mail auth external: \"%V\"", &s->login);
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716247671 -10800
# Tue May 21 02:27:51 2024 +0300
# Node ID 5692eed9d5f4d7b53e3d2eb377a3249466a23000
# Parent 6dbc239d5e2bdbf69baa7d4cbdfa3871cfe67bbf
Mail: added some parsing debug logging.
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
@@ -30,6 +30,9 @@ ngx_mail_pop3_parse_command(ngx_mail_ses
state = s->state;
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "pop3 parse: %d", state);
+
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
@@ -248,6 +251,9 @@ ngx_mail_imap_parse_command(ngx_mail_ses
state = s->state;
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "imap parse: %d", state);
+
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
@@ -692,6 +698,9 @@ ngx_mail_smtp_parse_command(ngx_mail_ses
state = s->state;
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "smtp parse: %d", state);
+
for (p = s->buffer->pos; p < s->buffer->last; p++) {
ch = *p;
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716247679 -10800
# Tue May 21 02:27:59 2024 +0300
# Node ID b06a347640e565012aced65e2a694a306ed2db5c
# Parent 5692eed9d5f4d7b53e3d2eb377a3249466a23000
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,
full initial SASL response as sent by the client is passed to the auth
server in the "Auth-Pass" header, base64-decoded.
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
clients 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,8 @@ 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_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
--- 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;
}
@@ -772,6 +822,17 @@ ngx_mail_auth_http_process_headers(ngx_m
return;
}
+ if (s->login.data == NULL
+ && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V did not send username",
+ ctx->peer.name);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t));
if (peer == NULL) {
ngx_destroy_pool(ctx->pool);
@@ -858,9 +919,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 +937,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 +949,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,70 @@ ngx_mail_auth_external(ngx_mail_session_
}
+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;
+
+ if (s->auth_err.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth oauth cancel");
+
+ if (s->args.nelts != 1) {
+ goto invalid;
+ }
+
+ if (arg[0].len == 0
+ || 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;
+ }
+
+ 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);
+
+ 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_str_null(&s->login);
+
+ 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)
{
@@ -919,13 +983,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_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) {
@@ -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_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ }
+
+ 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_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ }
+
+ 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_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;
}
}
@@ -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_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ }
+
+ 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_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ }
+
+ 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_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;
}
}
@@ -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_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ }
+
+ 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_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ }
+
+ 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) {
# 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.
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
@@ -412,8 +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_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_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
@@ -822,17 +822,6 @@ ngx_mail_auth_http_process_headers(ngx_m
return;
}
- if (s->login.data == NULL
- && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
- {
- ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
- "auth http server %V did not send username",
- ctx->peer.name);
- ngx_destroy_pool(ctx->pool);
- ngx_mail_session_internal_server_error(s);
- return;
- }
-
peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t));
if (peer == NULL) {
ngx_destroy_pool(ctx->pool);
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
@@ -756,23 +756,126 @@ ngx_mail_auth_external(ngx_mail_session_
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_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 oauth cancel");
+ "mail auth xoauth2 cancel");
- if (s->args.nelts != 1) {
- goto invalid;
+ 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;
}
- if (arg[0].len == 0
- || ngx_strncmp(arg[0].data, (u_char *) "AQ==", 4) == 0)
+ 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, *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;
}
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
@@ -222,11 +222,11 @@ ngx_mail_imap_auth_state(ngx_event_t *re
break;
case ngx_imap_auth_xoauth2:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+ rc = ngx_mail_auth_xoauth2(s, c, 0);
break;
case ngx_imap_auth_oauthbearer:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+ rc = ngx_mail_auth_oauthbearer(s, c, 0);
break;
}
@@ -449,7 +449,7 @@ ngx_mail_imap_authenticate(ngx_mail_sess
if (s->args.nelts == 2) {
s->mail_state = ngx_imap_auth_xoauth2;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ return ngx_mail_auth_xoauth2(s, c, 1);
}
ngx_str_set(&s->out, imap_plain_next);
@@ -465,7 +465,7 @@ ngx_mail_imap_authenticate(ngx_mail_sess
if (s->args.nelts == 2) {
s->mail_state = ngx_imap_auth_oauthbearer;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ return ngx_mail_auth_oauthbearer(s, c, 1);
}
ngx_str_set(&s->out, imap_plain_next);
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
@@ -262,11 +262,11 @@ ngx_mail_pop3_auth_state(ngx_event_t *re
break;
case ngx_pop3_auth_xoauth2:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+ rc = ngx_mail_auth_xoauth2(s, c, 0);
break;
case ngx_pop3_auth_oauthbearer:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+ rc = ngx_mail_auth_oauthbearer(s, c, 0);
break;
}
}
@@ -570,7 +570,7 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s
if (s->args.nelts == 2) {
s->mail_state = ngx_pop3_auth_xoauth2;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ return ngx_mail_auth_xoauth2(s, c, 1);
}
ngx_str_set(&s->out, pop3_next);
@@ -586,7 +586,7 @@ ngx_mail_pop3_auth(ngx_mail_session_t *s
if (s->args.nelts == 2) {
s->mail_state = ngx_pop3_auth_oauthbearer;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ return ngx_mail_auth_oauthbearer(s, c, 1);
}
ngx_str_set(&s->out, pop3_next);
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
@@ -550,11 +550,11 @@ ngx_mail_smtp_auth_state(ngx_event_t *re
break;
case ngx_smtp_auth_xoauth2:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_XOAUTH2);
+ rc = ngx_mail_auth_xoauth2(s, c, 0);
break;
case ngx_smtp_auth_oauthbearer:
- rc = ngx_mail_auth_oauth(s, c, 0, NGX_MAIL_AUTH_OAUTHBEARER);
+ rc = ngx_mail_auth_oauthbearer(s, c, 0);
break;
}
}
@@ -762,7 +762,7 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s
if (s->args.nelts == 2) {
s->mail_state = ngx_smtp_auth_xoauth2;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_XOAUTH2);
+ return ngx_mail_auth_xoauth2(s, c, 1);
}
ngx_str_set(&s->out, smtp_next);
@@ -778,7 +778,7 @@ ngx_mail_smtp_auth(ngx_mail_session_t *s
if (s->args.nelts == 2) {
s->mail_state = ngx_smtp_auth_oauthbearer;
- return ngx_mail_auth_oauth(s, c, 1, NGX_MAIL_AUTH_OAUTHBEARER);
+ return ngx_mail_auth_oauthbearer(s, c, 1);
}
ngx_str_set(&s->out, smtp_next);
Patches for tests:
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716247768 -10800
# Tue May 21 02:29:28 2024 +0300
# Node ID 7c476ab61fc4836df946ae65c2d83f9cf3b2e51e
# Parent 79753dd514e60b47375e36c54739ea434e04a5b6
Tests: added test that EXTERNAL mail auth clears old password.
diff --git a/mail_imap.t b/mail_imap.t
--- a/mail_imap.t
+++ b/mail_imap.t
@@ -93,7 +93,7 @@ http {
EOF
$t->run_daemon(\&Test::Nginx::IMAP::imap_test_daemon);
-$t->run()->plan(29);
+$t->run()->plan(30);
$t->waitforsocket('127.0.0.1:' . port(8144));
@@ -184,6 +184,23 @@ my $s = Test::Nginx::IMAP->new();
$s->send('1 AUTHENTICATE EXTERNAL ' . encode_base64('test at example.com', ''));
$s->ok('auth external with username');
+# auth external after failed plain
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.27.1');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+
+$s->send('1 AUTHENTICATE PLAIN '
+ . encode_base64("\0test\@example.com\0bad", ''));
+$s->read();
+
+$s->send('1 AUTHENTICATE EXTERNAL ' . encode_base64('test at example.com', ''));
+$s->ok('auth external after plain');
+
+}
+
# quoted strings
$s = Test::Nginx::IMAP->new();
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1716249442 -10800
# Tue May 21 02:57:22 2024 +0300
# Node ID c88a117c95eec661bbb8d672f77827ce237d6948
# Parent 7c476ab61fc4836df946ae65c2d83f9cf3b2e51e
Tests: added tests for OAUTHBEARER and XOAUTH2 auth methods.
Based on a patch by Rob Mueller.
diff --git a/mail_oauth.t b/mail_oauth.t
new file mode 100644
--- /dev/null
+++ b/mail_oauth.t
@@ -0,0 +1,331 @@
+#!/usr/bin/perl
+
+# (C) Maxim Dounin
+
+# Tests for mail module, XOAUTH2 and OAUTHBEARER authentication.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use MIME::Base64;
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::IMAP;
+use Test::Nginx::POP3;
+use Test::Nginx::SMTP;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+local $SIG{PIPE} = 'IGNORE';
+
+my $t = Test::Nginx->new()->has(qw/mail imap pop3 smtp http map rewrite/)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+mail {
+ proxy_pass_error_message on;
+ proxy_timeout 15s;
+ timeout 2s;
+ auth_http http://127.0.0.1:8080/mail/auth;
+
+ server {
+ listen 127.0.0.1:8143;
+ protocol imap;
+ imap_auth plain oauthbearer xoauth2;
+ }
+ server {
+ listen 127.0.0.1:8110;
+ protocol pop3;
+ pop3_auth plain oauthbearer xoauth2;
+ }
+ server {
+ listen 127.0.0.1:8025;
+ protocol smtp;
+ smtp_auth plain oauthbearer xoauth2;
+ }
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ map $http_auth_protocol $proxy_port {
+ imap %%PORT_8144%%;
+ pop3 %%PORT_8111%%;
+ smtp %%PORT_8026%%;
+ }
+
+ map $http_auth_pass $reply {
+ ~secretok OK;
+ default auth-failed;
+ }
+ map $http_auth_pass $passw {
+ ~secretok secret;
+ default "";
+ }
+ map $http_auth_pass $sasl {
+ ~saslfail "eyJzY2hlbWVzIjoiQmVhcmVyIiwic3RhdHVzIjoiNDAwIn0=";
+ default "";
+ }
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location = /mail/auth {
+ 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;
+ return 204;
+ }
+ }
+}
+
+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->run()->plan(47);
+
+$t->waitforsocket('127.0.0.1:' . port(8144));
+$t->waitforsocket('127.0.0.1:' . port(8111));
+$t->waitforsocket('127.0.0.1:' . port(8026));
+
+###############################################################################
+
+# AUTHBEARER SASL mechanism
+# https://datatracker.ietf.org/doc/html/rfc7628
+
+# XOAUTH2 SASL mechanism
+# https://developers.google.com/gmail/imap/xoauth2-protocol
+
+my $s;
+my $token = encode_base64(
+ "n,a=test\@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(
+ "n,a=test\@example.com,\001auth=Bearer bad\001\001", '');
+
+my $token_xoauth2 = encode_base64(
+ "user=test\@example.com\001auth=Bearer secretok\001\001", '');
+my $token_xoauth2_saslfail = encode_base64(
+ "user=test\@example.com\001auth=Bearer saslfail\001\001", '');
+my $token_xoauth2_bad = encode_base64(
+ "user=test\@example.com\001auth=Bearer bad\001\001", '');
+
+# IMAP
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token);
+$s->ok('imap oauthbearer success');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE OAUTHBEARER');
+$s->check(qr/\+ /, 'imap oauthbearer challenge');
+$s->send($token);
+$s->ok('imap oauthbearer success after challenge');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_bad);
+$s->check(qr/^1 NO auth-failed/, 'imap oauthbearer non-sasl error');
+
+sleep(3);
+
+my @ready = $s->can_read(0);
+is(scalar @ready, 1, "imap ready for reading");
+ok($s->eof(), "imap session closed");
+
+# fail, sasl failure method
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+my $start = time;
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap oauthbearer sasl failure');
+my $wait_time = time - $start;
+ok($wait_time >= 1, 'imap oauthbearer error delayed');
+$s->send('AQ==');
+$s->check(qr/^1 NO auth-failed/,
+ 'imap oauthbearer auth failure after dummy response');
+
+# fail, sasl failure method, invalid client response
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap oauthbearer sasl failure');
+$s->send('foo');
+$s->check(qr/^1 BAD /, 'imap oauthbearer invalid command after invalid line');
+
+# fail, sasl failure method, multiple attempts, then success
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap oauthbearer sasl failure');
+$s->send('AQ==');
+$s->check(qr/^1 NO auth-failed/,
+ 'imap oauthbearer auth failure after dummy response');
+
+$s->send('1 AUTHENTICATE OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap oauthbearer sasl failure next');
+$s->send('foo');
+$s->check(qr/^1 BAD/, 'imap oauthbearer invalid command after invalid line');
+
+$s->send('1 AUTHENTICATE OAUTHBEARER');
+$s->check(qr/\+ /, 'imap oauthbearer challenge after fail');
+$s->send($token);
+$s->ok('imap oauthbearer success after fail');
+
+# IMAP XOAUTH2
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE XOAUTH2 ' . $token_xoauth2);
+$s->ok('imap xoauth2 success');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE XOAUTH2');
+$s->check(qr/^\+ /, 'imap xoauth2 challenge');
+$s->send($token_xoauth2);
+$s->ok('imap xoauth2 success after challenge');
+
+$s = Test::Nginx::IMAP->new();
+$s->read();
+$s->send('1 AUTHENTICATE XOAUTH2 ' . $token_xoauth2_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap xoauth2 with bad token');
+$s->send('');
+$s->check(qr/^1 NO auth-failed/, 'imap xoauth2 auth failure after empty line');
+
+$s->send('1 AUTHENTICATE XOAUTH2 ' . $token_xoauth2_saslfail);
+$s->check(qr/^\+ eyJz/, 'imap xoauth2 with bad token next');
+$s->send('foo');
+$s->check(qr/^1 BAD/, 'imap xoauth2 invalid command after invalid line');
+
+$s->send('1 AUTHENTICATE XOAUTH2 ' . $token_xoauth2);
+$s->ok('imap xoauth2 success after fail');
+
+# POP3
+
+$s = Test::Nginx::POP3->new();
+$s->read();
+$s->send('AUTH OAUTHBEARER ' . $token);
+$s->ok('pop3 oauthbearer success');
+
+$s = Test::Nginx::POP3->new();
+$s->read();
+$s->send('AUTH OAUTHBEARER');
+$s->check(qr/^\+ /, 'pop3 oauthbearer challenge');
+$s->send($token);
+$s->ok('pop3 oauthbearer success after challenge');
+
+$s = Test::Nginx::POP3->new();
+$s->read();
+$s->send('AUTH OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'pop3 oauthbearer sasl failure');
+$s->send('AQ==');
+$s->check(qr/^-ERR /, 'pop3 oauthbearer auth failure after dummy response');
+
+$s->send('AUTH OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^\+ eyJz/, 'pop3 oauthbearer sasl failure next');
+$s->send('');
+$s->check(qr/^-ERR /, 'pop3 oauthbearer invalid command after invalid line');
+
+$s->send('AUTH OAUTHBEARER ' . $token);
+$s->ok('pop3 oauthbearer success after fail');
+
+# POP3 XOAUTH2
+
+$s = Test::Nginx::POP3->new();
+$s->read();
+$s->send('AUTH XOAUTH2 ' . $token_xoauth2);
+$s->ok('pop3 xoauth2 success');
+
+$s = Test::Nginx::POP3->new();
+$s->read();
+$s->send('AUTH XOAUTH2');
+$s->check(qr/^\+ /, 'pop3 xoauth2 challenge');
+$s->send($token_xoauth2);
+$s->ok('pop3 xoauth2 success after challenge');
+
+# SMTP
+
+$s = Test::Nginx::SMTP->new();
+$s->read();
+$s->send('EHLO example.com');
+$s->read();
+$s->send('AUTH OAUTHBEARER ' . $token);
+$s->authok('smtp oauthbearer success');
+
+$s = Test::Nginx::SMTP->new();
+$s->read();
+$s->send('EHLO example.com');
+$s->read();
+$s->send('AUTH OAUTHBEARER');
+$s->check(qr/^334 /, 'smtp oauthbearer challenge');
+$s->send($token);
+$s->authok('smtp oauthbearer success after challenge');
+
+$s = Test::Nginx::SMTP->new();
+$s->read();
+$s->send('EHLO example.com');
+$s->read();
+$s->send('AUTH OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^334 eyJz/, 'smtp oauthbearer sasl failure');
+$s->send('AQ==');
+$s->check(qr/^535 /, 'smtp oauthbearer auth failure after dummy response');
+
+$s->send('AUTH OAUTHBEARER ' . $token_saslfail);
+$s->check(qr/^334 eyJz/, 'smtp oauthbearer sasl failure next');
+$s->send('foo');
+$s->check(qr/^500 /, 'smtp oauthbearer invalid command after invalid line');
+
+$s->send('AUTH OAUTHBEARER ' . $token);
+$s->authok('smtp oauthbearer success after fail');
+
+# SMTP XOAUTH2
+
+$s = Test::Nginx::SMTP->new();
+$s->read();
+$s->send('EHLO example.com');
+$s->read();
+$s->send('AUTH XOAUTH2 ' . $token_xoauth2);
+$s->authok('smtp xoauth2 success');
+
+$s = Test::Nginx::SMTP->new();
+$s->read();
+$s->send('EHLO example.com');
+$s->read();
+$s->send('AUTH XOAUTH2');
+$s->check(qr/^334 /, 'smtp xoauth2 challenge');
+$s->send($token_xoauth2);
+$s->authok('smtp xoauth2 success after challenge');
+
+###############################################################################
--
Maxim Dounin
http://mdounin.ru/
More information about the nginx-devel
mailing list