@@ -139,7 +139,9 @@ enum CAPABILITY {
139
139
LITERALPLUS ,
140
140
NAMESPACE ,
141
141
STARTTLS ,
142
- AUTH_CRAM_MD5
142
+ AUTH_CRAM_MD5 ,
143
+ AUTH_OAUTHBEARER ,
144
+ AUTH_XOAUTH2 ,
143
145
};
144
146
145
147
static const char * cap_list [] = {
@@ -149,6 +151,8 @@ static const char *cap_list[] = {
149
151
"NAMESPACE" ,
150
152
"STARTTLS" ,
151
153
"AUTH=CRAM-MD5" ,
154
+ "AUTH=OAUTHBEARER" ,
155
+ "AUTH=XOAUTH2" ,
152
156
};
153
157
154
158
#define RESP_OK 0
@@ -885,6 +889,64 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
885
889
return (char * )response_64 ;
886
890
}
887
891
892
+ static char * oauthbearer_base64 (const char * user , const char * access_token )
893
+ {
894
+ int b64_len ;
895
+ char * raw , * b64 ;
896
+
897
+ /*
898
+ * Compose the OAUTHBEARER string
899
+ *
900
+ * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
901
+ *
902
+ * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
903
+ * * gs2-cb-flag `n` -> client does not support CB
904
+ * * gs2-authzid `a=" {User} "`
905
+ *
906
+ * The second part are key value pairs containing host, port and auth as
907
+ * described in RFC7628.
908
+ *
909
+ * https://datatracker.ietf.org/doc/html/rfc5801
910
+ * https://datatracker.ietf.org/doc/html/rfc7628
911
+ */
912
+ raw = xstrfmt ("n,a=%s,\001auth=Bearer %s\001\001" , user , access_token );
913
+
914
+ /* Base64 encode */
915
+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
916
+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
917
+ free (raw );
918
+
919
+ if (b64_len < 0 ) {
920
+ free (b64 );
921
+ return NULL ;
922
+ }
923
+ return b64 ;
924
+ }
925
+
926
+ static char * xoauth2_base64 (const char * user , const char * access_token )
927
+ {
928
+ int b64_len ;
929
+ char * raw , * b64 ;
930
+
931
+ /*
932
+ * Compose the XOAUTH2 string
933
+ * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
934
+ * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
935
+ */
936
+ raw = xstrfmt ("user=%s\001auth=Bearer %s\001\001" , user , access_token );
937
+
938
+ /* Base64 encode */
939
+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
940
+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
941
+ free (raw );
942
+
943
+ if (b64_len < 0 ) {
944
+ free (b64 );
945
+ return NULL ;
946
+ }
947
+ return b64 ;
948
+ }
949
+
888
950
static int auth_cram_md5 (struct imap_store * ctx , const char * prompt )
889
951
{
890
952
int ret ;
@@ -903,9 +965,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
903
965
return 0 ;
904
966
}
905
967
968
+ static int auth_oauthbearer (struct imap_store * ctx , const char * prompt UNUSED )
969
+ {
970
+ int ret ;
971
+ char * b64 ;
972
+
973
+ b64 = oauthbearer_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
974
+ if (!b64 )
975
+ return error ("OAUTHBEARER: base64 encoding failed" );
976
+
977
+ /* Send the base64-encoded response */
978
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
979
+ if (ret != (int )strlen (b64 )) {
980
+ free (b64 );
981
+ return error ("IMAP error: sending OAUTHBEARER response failed" );
982
+ }
983
+
984
+ free (b64 );
985
+ return 0 ;
986
+ }
987
+
988
+ static int auth_xoauth2 (struct imap_store * ctx , const char * prompt UNUSED )
989
+ {
990
+ int ret ;
991
+ char * b64 ;
992
+
993
+ b64 = xoauth2_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
994
+ if (!b64 )
995
+ return error ("XOAUTH2: base64 encoding failed" );
996
+
997
+ /* Send the base64-encoded response */
998
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
999
+ if (ret != (int )strlen (b64 )) {
1000
+ free (b64 );
1001
+ return error ("IMAP error: sending XOAUTH2 response failed" );
1002
+ }
1003
+
1004
+ free (b64 );
1005
+ return 0 ;
1006
+ }
1007
+
906
1008
#else
907
1009
908
1010
#define auth_cram_md5 NULL
1011
+ #define auth_oauthbearer NULL
1012
+ #define auth_xoauth2 NULL
909
1013
910
1014
#endif
911
1015
@@ -1118,6 +1222,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
1118
1222
if (!strcmp (srvc -> auth_method , "CRAM-MD5" )) {
1119
1223
if (try_auth_method (srvc , ctx , imap , "CRAM-MD5" , AUTH_CRAM_MD5 , auth_cram_md5 ))
1120
1224
goto bail ;
1225
+ } else if (!strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1226
+ if (try_auth_method (srvc , ctx , imap , "OAUTHBEARER" , AUTH_OAUTHBEARER , auth_oauthbearer ))
1227
+ goto bail ;
1228
+ } else if (!strcmp (srvc -> auth_method , "XOAUTH2" )) {
1229
+ if (try_auth_method (srvc , ctx , imap , "XOAUTH2" , AUTH_XOAUTH2 , auth_xoauth2 ))
1230
+ goto bail ;
1121
1231
} else {
1122
1232
fprintf (stderr , "Unknown authentication method:%s\n" , srvc -> host );
1123
1233
goto bail ;
@@ -1419,7 +1529,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1419
1529
1420
1530
server_fill_credential (srvc , cred );
1421
1531
curl_easy_setopt (curl , CURLOPT_USERNAME , srvc -> user );
1422
- curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1532
+
1533
+ /*
1534
+ * Use CURLOPT_PASSWORD irrespective of whether there is
1535
+ * an auth method specified or not, unless it's OAuth2.0,
1536
+ * where we use CURLOPT_XOAUTH2_BEARER.
1537
+ */
1538
+ if (!srvc -> auth_method ||
1539
+ (strcmp (srvc -> auth_method , "XOAUTH2" ) &&
1540
+ strcmp (srvc -> auth_method , "OAUTHBEARER" )))
1541
+ curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1423
1542
1424
1543
strbuf_addstr (& path , srvc -> use_ssl ? "imaps://" : "imap://" );
1425
1544
strbuf_addstr (& path , srvc -> host );
@@ -1437,11 +1556,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1437
1556
curl_easy_setopt (curl , CURLOPT_PORT , srvc -> port );
1438
1557
1439
1558
if (srvc -> auth_method ) {
1440
- struct strbuf auth = STRBUF_INIT ;
1441
- strbuf_addstr (& auth , "AUTH=" );
1442
- strbuf_addstr (& auth , srvc -> auth_method );
1443
- curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1444
- strbuf_release (& auth );
1559
+ if (!strcmp (srvc -> auth_method , "XOAUTH2" ) ||
1560
+ !strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1561
+
1562
+ /*
1563
+ * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
1564
+ * upon debugging, it has been found that it is capable of detecting
1565
+ * the best option out of OAUTHBEARER and XOAUTH2.
1566
+ */
1567
+ curl_easy_setopt (curl , CURLOPT_XOAUTH2_BEARER , srvc -> pass );
1568
+ } else {
1569
+ struct strbuf auth = STRBUF_INIT ;
1570
+ strbuf_addstr (& auth , "AUTH=" );
1571
+ strbuf_addstr (& auth , srvc -> auth_method );
1572
+ curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1573
+ strbuf_release (& auth );
1574
+ }
1445
1575
}
1446
1576
1447
1577
if (!srvc -> use_ssl )
0 commit comments