@@ -139,7 +139,9 @@ enum CAPABILITY {
139139 LITERALPLUS ,
140140 NAMESPACE ,
141141 STARTTLS ,
142- AUTH_CRAM_MD5
142+ AUTH_CRAM_MD5 ,
143+ AUTH_OAUTHBEARER ,
144+ AUTH_XOAUTH2
143145};
144146
145147static const char * cap_list [] = {
@@ -149,6 +151,8 @@ static const char *cap_list[] = {
149151 "NAMESPACE" ,
150152 "STARTTLS" ,
151153 "AUTH=CRAM-MD5" ,
154+ "AUTH=OAUTHBEARER" ,
155+ "AUTH=XOAUTH2" ,
152156};
153157
154158#define RESP_OK 0
@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
885889 return (char * )response_64 ;
886890}
887891
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+ /* Compose the OAUTHBEARER string
898+ *
899+ * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
900+ *
901+ * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
902+ * * gs2-cb-flag `n` -> client does not support CB
903+ * * gs2-authzid `a=" {User} "`
904+ *
905+ * The second part are key value pairs containing host, port and auth as
906+ * described in RFC7628.
907+ *
908+ * https://datatracker.ietf.org/doc/html/rfc5801
909+ * https://datatracker.ietf.org/doc/html/rfc7628
910+ */
911+ raw_len = strlen (user ) + strlen (access_token ) + 20 ;
912+ raw = xmallocz (raw_len + 1 );
913+ snprintf (raw , raw_len + 1 , "n,a=%s,\001auth=Bearer %s\001\001" , user , access_token );
914+
915+ /* Base64 encode */
916+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
917+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
918+ free (raw );
919+
920+ if (b64_len < 0 ) {
921+ free (b64 );
922+ return NULL ;
923+ }
924+ return b64 ;
925+ }
926+
927+ static char * xoauth2_base64 (const char * user , const char * access_token )
928+ {
929+ int raw_len , b64_len ;
930+ char * raw , * b64 ;
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_len = strlen (user ) + strlen (access_token ) + 20 ;
937+ raw = xmallocz (raw_len + 1 );
938+ snprintf (raw , raw_len + 1 , "user=%s\001auth=Bearer %s\001\001" , user , access_token );
939+
940+ /* Base64 encode */
941+ b64 = xmallocz (ENCODED_SIZE (strlen (raw )));
942+ b64_len = EVP_EncodeBlock ((unsigned char * )b64 , (unsigned char * )raw , strlen (raw ));
943+ free (raw );
944+
945+ if (b64_len < 0 ) {
946+ free (b64 );
947+ return NULL ;
948+ }
949+ return b64 ;
950+ }
951+
888952#else
889953
890954static char * cram (const char * challenge_64 UNUSED ,
@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,
895959 "you have to build git-imap-send with OpenSSL library." );
896960}
897961
962+ static char * oauthbearer_base64 (const char * user UNUSED ,
963+ const char * access_token UNUSED )
964+ {
965+ die ("You are trying to use OAUTHBEARER authenticate method "
966+ "with OpenSSL library, but its support has not been compiled in." );
967+ }
968+
969+ static char * xoauth2_base64 (const char * user UNUSED ,
970+ const char * access_token UNUSED )
971+ {
972+ die ("You are trying to use XOAUTH2 authenticate method "
973+ "with OpenSSL library, but its support has not been compiled in." );
974+ }
975+
898976#endif
899977
900978static int auth_cram_md5 (struct imap_store * ctx , const char * prompt )
@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
913991 return 0 ;
914992}
915993
994+ static int auth_oauthbearer (struct imap_store * ctx , const char * prompt UNUSED )
995+ {
996+ int ret ;
997+ char * b64 ;
998+
999+ b64 = oauthbearer_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
1000+ if (!b64 )
1001+ return error ("OAUTHBEARER: base64 encoding failed" );
1002+
1003+ /* Send the base64-encoded response */
1004+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
1005+ if (ret != (int )strlen (b64 )) {
1006+ free (b64 );
1007+ return error ("IMAP error: sending OAUTHBEARER response failed" );
1008+ }
1009+
1010+ free (b64 );
1011+ return 0 ;
1012+ }
1013+
1014+ static int auth_xoauth2 (struct imap_store * ctx , const char * prompt UNUSED )
1015+ {
1016+ int ret ;
1017+ char * b64 ;
1018+
1019+ b64 = xoauth2_base64 (ctx -> cfg -> user , ctx -> cfg -> pass );
1020+ if (!b64 )
1021+ return error ("XOAUTH2: base64 encoding failed" );
1022+
1023+ /* Send the base64-encoded response */
1024+ ret = socket_write (& ctx -> imap -> buf .sock , b64 , strlen (b64 ));
1025+ if (ret != (int )strlen (b64 )) {
1026+ free (b64 );
1027+ return error ("IMAP error: sending XOAUTH2 response failed" );
1028+ }
1029+
1030+ free (b64 );
1031+ return 0 ;
1032+ }
1033+
9161034static void server_fill_credential (struct imap_server_conf * srvc , struct credential * cred )
9171035{
9181036 if (srvc -> user && srvc -> pass )
@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
11041222 fprintf (stderr , "IMAP error: AUTHENTICATE CRAM-MD5 failed\n" );
11051223 goto bail ;
11061224 }
1225+ } else if (!strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1226+ if (!CAP (AUTH_OAUTHBEARER )) {
1227+ fprintf (stderr , "You specified "
1228+ "OAUTHBEARER as authentication method, "
1229+ "but %s doesn't support it.\n" , srvc -> host );
1230+ goto bail ;
1231+ }
1232+ /* OAUTHBEARER */
1233+
1234+ memset (& cb , 0 , sizeof (cb ));
1235+ cb .cont = auth_oauthbearer ;
1236+ if (imap_exec (ctx , & cb , "AUTHENTICATE OAUTHBEARER" ) != RESP_OK ) {
1237+ fprintf (stderr , "IMAP error: AUTHENTICATE OAUTHBEARER failed\n" );
1238+ goto bail ;
1239+ }
1240+ } else if (!strcmp (srvc -> auth_method , "XOAUTH2" )) {
1241+ if (!CAP (AUTH_XOAUTH2 )) {
1242+ fprintf (stderr , "You specified "
1243+ "XOAUTH2 as authentication method, "
1244+ "but %s doesn't support it.\n" , srvc -> host );
1245+ goto bail ;
1246+ }
1247+ /* XOAUTH2 */
1248+
1249+ memset (& cb , 0 , sizeof (cb ));
1250+ cb .cont = auth_xoauth2 ;
1251+ if (imap_exec (ctx , & cb , "AUTHENTICATE XOAUTH2" ) != RESP_OK ) {
1252+ fprintf (stderr , "IMAP error: AUTHENTICATE XOAUTH2 failed\n" );
1253+ goto bail ;
1254+ }
11071255 } else {
11081256 fprintf (stderr , "Unknown authentication method:%s\n" , srvc -> host );
11091257 goto bail ;
@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
14051553
14061554 server_fill_credential (srvc , cred );
14071555 curl_easy_setopt (curl , CURLOPT_USERNAME , srvc -> user );
1408- curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
1556+
1557+ if (!srvc -> auth_method ||
1558+ strcmp (srvc -> auth_method , "XOAUTH2" ) ||
1559+ strcmp (srvc -> auth_method , "OAUTHBEARER" ))
1560+ curl_easy_setopt (curl , CURLOPT_PASSWORD , srvc -> pass );
14091561
14101562 strbuf_addstr (& path , srvc -> use_ssl ? "imaps://" : "imap://" );
14111563 strbuf_addstr (& path , srvc -> host );
@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
14231575 curl_easy_setopt (curl , CURLOPT_PORT , srvc -> port );
14241576
14251577 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 );
1578+ if (!strcmp (srvc -> auth_method , "XOAUTH2" ) ||
1579+ !strcmp (srvc -> auth_method , "OAUTHBEARER" )) {
1580+
1581+ /* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
1582+ * upon debugging, it has been found that it is capable of detecting
1583+ * the best option out of OAUTHBEARER and XOAUTH2.
1584+ */
1585+ curl_easy_setopt (curl , CURLOPT_XOAUTH2_BEARER , srvc -> pass );
1586+ } else {
1587+ struct strbuf auth = STRBUF_INIT ;
1588+ strbuf_addstr (& auth , "AUTH=" );
1589+ strbuf_addstr (& auth , srvc -> auth_method );
1590+ curl_easy_setopt (curl , CURLOPT_LOGIN_OPTIONS , auth .buf );
1591+ strbuf_release (& auth );
1592+ }
14311593 }
14321594
14331595 if (!srvc -> use_ssl )
0 commit comments