@@ -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,108 @@ 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
+
954
+ static int auth_oauthbearer (struct imap_store * ctx , const char * prompt UNUSED )
955
+ {
956
+ int ret ;
957
+ char * b64 ;
958
+
959
+ b64 = oauthbearer_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
960
+ if (!b64 )
961
+ return error ("OAUTHBEARER: base64 encoding failed" );
962
+
963
+ /* Send the base64-encoded response */
964
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
965
+ if (ret != (int )strlen (b64 )) {
966
+ free (b64 );
967
+ return error ("IMAP error: sending OAUTHBEARER response failed" );
968
+ }
969
+
970
+ free (b64 );
971
+ return 0 ;
972
+ }
973
+
974
+ static int auth_xoauth2 (struct imap_store * ctx , const char * prompt UNUSED )
975
+ {
976
+ int ret ;
977
+ char * b64 ;
978
+
979
+ b64 = xoauth2_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
980
+ if (!b64 )
981
+ return error ("XOAUTH2: base64 encoding failed" );
982
+
983
+ /* Send the base64-encoded response */
984
+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
985
+ if (ret != (int )strlen (b64 )) {
986
+ free (b64 );
987
+ return error ("IMAP error: sending XOAUTH2 response failed" );
988
+ }
989
+
990
+ free (b64 );
991
+ return 0 ;
992
+ }
993
+
888
994
#else
889
995
890
996
static char * cram (const char * challenge_64 UNUSED ,
@@ -895,6 +1001,9 @@ static char *cram(const char *challenge_64 UNUSED,
895
1001
"you have to build git-imap-send with OpenSSL library." );
896
1002
}
897
1003
1004
+ #define auth_oauthbearer NULL
1005
+ #define auth_xoauth2 NULL
1006
+
898
1007
#endif
899
1008
900
1009
static int auth_cram_md5 (struct imap_store * ctx , const char * prompt )
@@ -1104,6 +1213,50 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
1104
1213
fprintf (stderr , "IMAP error: AUTHENTICATE CRAM-MD5 failed\n" );
1105
1214
goto bail ;
1106
1215
}
1216
+ } else if (!strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1217
+ if (!CAP (AUTH_OAUTHBEARER )) {
1218
+ fprintf (stderr , "You specified "
1219
+ "OAUTHBEARER as authentication method, "
1220
+ "but %s doesn't support it.\n" , srvc -> host );
1221
+ goto bail ;
1222
+ }
1223
+
1224
+ #ifdef NO_OPENSSL
1225
+ fprintf (stderr , "You are trying to use OAUTHBEARER authentication mechanism "
1226
+ "with OpenSSL library, but its support has not been compiled in." );
1227
+ goto bail ;
1228
+ #endif
1229
+
1230
+ /* OAUTHBEARER */
1231
+
1232
+ memset (& cb , 0 , sizeof (cb ));
1233
+ cb .cont = auth_oauthbearer ;
1234
+ if (imap_exec (ctx , & cb , "AUTHENTICATE OAUTHBEARER" ) != RESP_OK ) {
1235
+ fprintf (stderr , "IMAP error: AUTHENTICATE OAUTHBEARER failed\n" );
1236
+ goto bail ;
1237
+ }
1238
+ } else if (!strcmp (srvc -> auth_method , "XOAUTH2" )) {
1239
+ if (!CAP (AUTH_XOAUTH2 )) {
1240
+ fprintf (stderr , "You specified "
1241
+ "XOAUTH2 as authentication method, "
1242
+ "but %s doesn't support it.\n" , srvc -> host );
1243
+ goto bail ;
1244
+ }
1245
+
1246
+ #ifdef NO_OPENSSL
1247
+ fprintf (stderr , "You are trying to use XOAUTH2 authentication mechanism "
1248
+ "with OpenSSL library, but its support has not been compiled in." );
1249
+ goto bail ;
1250
+ #endif
1251
+
1252
+ /* XOAUTH2 */
1253
+
1254
+ memset (& cb , 0 , sizeof (cb ));
1255
+ cb .cont = auth_xoauth2 ;
1256
+ if (imap_exec (ctx , & cb , "AUTHENTICATE XOAUTH2" ) != RESP_OK ) {
1257
+ fprintf (stderr , "IMAP error: AUTHENTICATE XOAUTH2 failed\n" );
1258
+ goto bail ;
1259
+ }
1107
1260
} else {
1108
1261
fprintf (stderr , "Unknown authentication method:%s\n" , srvc -> host );
1109
1262
goto bail ;
@@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1405
1558
1406
1559
server_fill_credential (srvc , cred );
1407
1560
curl_easy_setopt (curl , CURLOPT_USERNAME , srvc -> user );
1408
- curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1561
+
1562
+ if (!srvc -> auth_method ||
1563
+ (strcmp (srvc -> auth_method , "XOAUTH2" ) &&
1564
+ strcmp (srvc -> auth_method , "OAUTHBEARER" )))
1565
+ curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1409
1566
1410
1567
strbuf_addstr (& path , srvc -> use_ssl ? "imaps://" : "imap://" );
1411
1568
strbuf_addstr (& path , srvc -> host );
@@ -1423,11 +1580,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1423
1580
curl_easy_setopt (curl , CURLOPT_PORT , srvc -> port );
1424
1581
1425
1582
if (srvc -> auth_method ) {
1426
- struct strbuf auth = STRBUF_INIT ;
1427
- strbuf_addstr (& auth , "AUTH=" );
1428
- strbuf_addstr (& auth , srvc -> auth_method );
1429
- curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1430
- strbuf_release (& auth );
1583
+ if (!strcmp (srvc -> auth_method , "XOAUTH2" ) ||
1584
+ !strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1585
+
1586
+ /*
1587
+ * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
1588
+ * upon debugging, it has been found that it is capable of detecting
1589
+ * the best option out of OAUTHBEARER and XOAUTH2.
1590
+ */
1591
+ curl_easy_setopt (curl , CURLOPT_XOAUTH2_BEARER , srvc -> pass );
1592
+ } else {
1593
+ struct strbuf auth = STRBUF_INIT ;
1594
+ strbuf_addstr (& auth , "AUTH=" );
1595
+ strbuf_addstr (& auth , srvc -> auth_method );
1596
+ curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1597
+ strbuf_release (& auth );
1598
+ }
1431
1599
}
1432
1600
1433
1601
if (!srvc -> use_ssl )
0 commit comments