summaryrefslogtreecommitdiff
path: root/imap-send.c
diff options
context:
space:
mode:
authorAditya Garg <gargaditya08@live.com>2025-06-20 12:10:27 +0530
committerJunio C Hamano <gitster@pobox.com>2025-06-20 08:11:16 -0700
commit103d7b12b7adeee88a95e642ffd105a25335bfef (patch)
treecbfd3a90bc9b7a0a2739d70bcfecefa558fef457 /imap-send.c
parentb9e766604df2c50b2f721479bb405409db3344d1 (diff)
imap-send: add support for OAuth2.0 authentication
OAuth2.0 is a new way of authentication supported by various email providers these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas XOAUTH2 is Google's proprietary mechanism (See [3]). [1]: https://datatracker.ietf.org/doc/html/rfc5801 [2]: https://datatracker.ietf.org/doc/html/rfc7628 [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response Signed-off-by: Aditya Garg <gargaditya08@live.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'imap-send.c')
-rw-r--r--imap-send.c144
1 files changed, 137 insertions, 7 deletions
diff --git a/imap-send.c b/imap-send.c
index f55399cd9e..6c33318102 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -139,7 +139,9 @@ enum CAPABILITY {
LITERALPLUS,
NAMESPACE,
STARTTLS,
- AUTH_CRAM_MD5
+ AUTH_CRAM_MD5,
+ AUTH_OAUTHBEARER,
+ AUTH_XOAUTH2,
};
static const char *cap_list[] = {
@@ -149,6 +151,8 @@ static const char *cap_list[] = {
"NAMESPACE",
"STARTTLS",
"AUTH=CRAM-MD5",
+ "AUTH=OAUTHBEARER",
+ "AUTH=XOAUTH2",
};
#define RESP_OK 0
@@ -885,6 +889,64 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
return (char *)response_64;
}
+static char *oauthbearer_base64(const char *user, const char *access_token)
+{
+ int b64_len;
+ char *raw, *b64;
+
+ /*
+ * Compose the OAUTHBEARER string
+ *
+ * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
+ *
+ * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
+ * * gs2-cb-flag `n` -> client does not support CB
+ * * gs2-authzid `a=" {User} "`
+ *
+ * The second part are key value pairs containing host, port and auth as
+ * described in RFC7628.
+ *
+ * https://datatracker.ietf.org/doc/html/rfc5801
+ * https://datatracker.ietf.org/doc/html/rfc7628
+ */
+ raw = xstrfmt("n,a=%s,\001auth=Bearer %s\001\001", user, access_token);
+
+ /* Base64 encode */
+ b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
+ b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
+ free(raw);
+
+ if (b64_len < 0) {
+ free(b64);
+ return NULL;
+ }
+ return b64;
+}
+
+static char *xoauth2_base64(const char *user, const char *access_token)
+{
+ int b64_len;
+ char *raw, *b64;
+
+ /*
+ * Compose the XOAUTH2 string
+ * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
+ * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
+ */
+ raw = xstrfmt("user=%s\001auth=Bearer %s\001\001", user, access_token);
+
+ /* Base64 encode */
+ b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
+ b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
+ free(raw);
+
+ if (b64_len < 0) {
+ free(b64);
+ return NULL;
+ }
+ return b64;
+}
+
static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
{
int ret;
@@ -903,9 +965,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
return 0;
}
+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)
+{
+ int ret;
+ char *b64;
+
+ b64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);
+ if (!b64)
+ return error("OAUTHBEARER: base64 encoding failed");
+
+ /* Send the base64-encoded response */
+ ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
+ if (ret != (int)strlen(b64)) {
+ free(b64);
+ return error("IMAP error: sending OAUTHBEARER response failed");
+ }
+
+ free(b64);
+ return 0;
+}
+
+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)
+{
+ int ret;
+ char *b64;
+
+ b64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);
+ if (!b64)
+ return error("XOAUTH2: base64 encoding failed");
+
+ /* Send the base64-encoded response */
+ ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
+ if (ret != (int)strlen(b64)) {
+ free(b64);
+ return error("IMAP error: sending XOAUTH2 response failed");
+ }
+
+ free(b64);
+ return 0;
+}
+
#else
#define auth_cram_md5 NULL
+#define auth_oauthbearer NULL
+#define auth_xoauth2 NULL
#endif
@@ -1118,6 +1222,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
if (try_auth_method(srvc, ctx, imap, "CRAM-MD5", AUTH_CRAM_MD5, auth_cram_md5))
goto bail;
+ } else if (!strcmp(srvc->auth_method, "OAUTHBEARER")) {
+ if (try_auth_method(srvc, ctx, imap, "OAUTHBEARER", AUTH_OAUTHBEARER, auth_oauthbearer))
+ goto bail;
+ } else if (!strcmp(srvc->auth_method, "XOAUTH2")) {
+ if (try_auth_method(srvc, ctx, imap, "XOAUTH2", AUTH_XOAUTH2, auth_xoauth2))
+ goto bail;
} else {
fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
goto bail;
@@ -1419,7 +1529,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
server_fill_credential(srvc, cred);
curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);
- curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
+
+ /*
+ * Use CURLOPT_PASSWORD irrespective of whether there is
+ * an auth method specified or not, unless it's OAuth2.0,
+ * where we use CURLOPT_XOAUTH2_BEARER.
+ */
+ if (!srvc->auth_method ||
+ (strcmp(srvc->auth_method, "XOAUTH2") &&
+ strcmp(srvc->auth_method, "OAUTHBEARER")))
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
strbuf_addstr(&path, srvc->use_ssl ? "imaps://" : "imap://");
strbuf_addstr(&path, srvc->host);
@@ -1437,11 +1556,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
curl_easy_setopt(curl, CURLOPT_PORT, srvc->port);
if (srvc->auth_method) {
- struct strbuf auth = STRBUF_INIT;
- strbuf_addstr(&auth, "AUTH=");
- strbuf_addstr(&auth, srvc->auth_method);
- curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
- strbuf_release(&auth);
+ if (!strcmp(srvc->auth_method, "XOAUTH2") ||
+ !strcmp(srvc->auth_method, "OAUTHBEARER")) {
+
+ /*
+ * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
+ * upon debugging, it has been found that it is capable of detecting
+ * the best option out of OAUTHBEARER and XOAUTH2.
+ */
+ curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);
+ } else {
+ struct strbuf auth = STRBUF_INIT;
+ strbuf_addstr(&auth, "AUTH=");
+ strbuf_addstr(&auth, srvc->auth_method);
+ curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
+ strbuf_release(&auth);
+ }
}
if (!srvc->use_ssl)