@@ -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,68 @@ 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 raw_len , 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_len = strlen (user ) + strlen (access_token ) + 20 ;
913
+ raw = xmallocz (raw_len + 1 );
914
+ snprintf (raw , raw_len + 1 , "n,a=%s,\001auth=Bearer %s\001\001" , user , access_token );
915
+
916
+ /* Base64 encode */
917
+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
918
+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
919
+ free (raw );
920
+
921
+ if (b64_len < 0 ) {
922
+ free (b64 );
923
+ return NULL ;
924
+ }
925
+ return b64 ;
926
+ }
927
+
928
+ static char * xoauth2_base64 (const char * user , const char * access_token )
929
+ {
930
+ int raw_len , b64_len ;
931
+ char * raw , * b64 ;
932
+
933
+ /*
934
+ * Compose the XOAUTH2 string
935
+ * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
936
+ * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
937
+ */
938
+ raw_len = strlen (user ) + strlen (access_token ) + 20 ;
939
+ raw = xmallocz (raw_len + 1 );
940
+ snprintf (raw , raw_len + 1 , "user=%s\001auth=Bearer %s\001\001" , user , access_token );
941
+
942
+ /* Base64 encode */
943
+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
944
+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
945
+ free (raw );
946
+
947
+ if (b64_len < 0 ) {
948
+ free (b64 );
949
+ return NULL ;
950
+ }
951
+ return b64 ;
952
+ }
953
+
888
954
static int auth_cram_md5 (struct imap_store * ctx , const char * prompt )
889
955
{
890
956
int ret ;
@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
903
969
return 0 ;
904
970
}
905
971
972
+ static int auth_oauthbearer (struct imap_store * ctx , const char * prompt UNUSED )
973
+ {
974
+ int ret ;
975
+ char * b64 ;
976
+
977
+ b64 = oauthbearer_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
978
+ if (!b64 )
979
+ return error ("OAUTHBEARER: base64 encoding failed" );
980
+
981
+ /* Send the base64-encoded response */
982
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
983
+ if (ret != (int )strlen (b64 )) {
984
+ free (b64 );
985
+ return error ("IMAP error: sending OAUTHBEARER response failed" );
986
+ }
987
+
988
+ free (b64 );
989
+ return 0 ;
990
+ }
991
+
992
+ static int auth_xoauth2 (struct imap_store * ctx , const char * prompt UNUSED )
993
+ {
994
+ int ret ;
995
+ char * b64 ;
996
+
997
+ b64 = xoauth2_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
998
+ if (!b64 )
999
+ return error ("XOAUTH2: base64 encoding failed" );
1000
+
1001
+ /* Send the base64-encoded response */
1002
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
1003
+ if (ret != (int )strlen (b64 )) {
1004
+ free (b64 );
1005
+ return error ("IMAP error: sending XOAUTH2 response failed" );
1006
+ }
1007
+
1008
+ free (b64 );
1009
+ return 0 ;
1010
+ }
1011
+
906
1012
#else
907
1013
908
1014
#define auth_cram_md5 NULL
1015
+ #define auth_oauthbearer NULL
1016
+ #define auth_xoauth2 NULL
909
1017
910
1018
#endif
911
1019
@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
1118
1226
if (!strcmp (srvc -> auth_method , "CRAM-MD5" )) {
1119
1227
if (try_auth_method (srvc , ctx , imap , "CRAM-MD5" , AUTH_CRAM_MD5 , auth_cram_md5 ))
1120
1228
goto bail ;
1229
+ } else if (!strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1230
+ if (try_auth_method (srvc , ctx , imap , "OAUTHBEARER" , AUTH_OAUTHBEARER , auth_oauthbearer ))
1231
+ goto bail ;
1232
+ } else if (!strcmp (srvc -> auth_method , "XOAUTH2" )) {
1233
+ if (try_auth_method (srvc , ctx , imap , "XOAUTH2" , AUTH_XOAUTH2 , auth_xoauth2 ))
1234
+ goto bail ;
1121
1235
} else {
1122
1236
fprintf (stderr , "Unknown authentication method:%s\n" , srvc -> host );
1123
1237
goto bail ;
@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1419
1533
1420
1534
server_fill_credential (srvc , cred );
1421
1535
curl_easy_setopt (curl , CURLOPT_USERNAME , srvc -> user );
1422
- curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1536
+
1537
+ /*
1538
+ * Use CURLOPT_PASSWORD irrespective of whether there is
1539
+ * an auth method specified or not, unless it's OAuth2.0,
1540
+ * where we use CURLOPT_XOAUTH2_BEARER.
1541
+ */
1542
+ if (!srvc -> auth_method ||
1543
+ (strcmp (srvc -> auth_method , "XOAUTH2" ) &&
1544
+ strcmp (srvc -> auth_method , "OAUTHBEARER" )))
1545
+ curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1423
1546
1424
1547
strbuf_addstr (& path , srvc -> use_ssl ? "imaps://" : "imap://" );
1425
1548
strbuf_addstr (& path , srvc -> host );
@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1437
1560
curl_easy_setopt (curl , CURLOPT_PORT , srvc -> port );
1438
1561
1439
1562
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 );
1563
+ if (!strcmp (srvc -> auth_method , "XOAUTH2" ) ||
1564
+ !strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1565
+
1566
+ /*
1567
+ * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
1568
+ * upon debugging, it has been found that it is capable of detecting
1569
+ * the best option out of OAUTHBEARER and XOAUTH2.
1570
+ */
1571
+ curl_easy_setopt (curl , CURLOPT_XOAUTH2_BEARER , srvc -> pass );
1572
+ } else {
1573
+ struct strbuf auth = STRBUF_INIT ;
1574
+ strbuf_addstr (& auth , "AUTH=" );
1575
+ strbuf_addstr (& auth , srvc -> auth_method );
1576
+ curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1577
+ strbuf_release (& auth );
1578
+ }
1445
1579
}
1446
1580
1447
1581
if (!srvc -> use_ssl )
0 commit comments