@@ -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,64 @@ 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 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+
888950static int auth_cram_md5 (struct imap_store * ctx , const char * prompt )
889951{
890952 int ret ;
@@ -903,9 +965,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
903965 return 0 ;
904966}
905967
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+
9061008#else
9071009
9081010#define auth_cram_md5 NULL
1011+ #define auth_oauthbearer NULL
1012+ #define auth_xoauth2 NULL
9091013
9101014#endif
9111015
@@ -1118,6 +1222,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
11181222 if (!strcmp (srvc -> auth_method , "CRAM-MD5" )) {
11191223 if (try_auth_method (srvc , ctx , imap , "CRAM-MD5" , AUTH_CRAM_MD5 , auth_cram_md5 ))
11201224 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 ;
11211231 } else {
11221232 fprintf (stderr , "Unknown authentication method:%s\n" , srvc -> host );
11231233 goto bail ;
@@ -1419,7 +1529,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
14191529
14201530 server_fill_credential (srvc , cred );
14211531 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 );
14231542
14241543 strbuf_addstr (& path , srvc -> use_ssl ? "imaps://" : "imap://" );
14251544 strbuf_addstr (& path , srvc -> host );
@@ -1437,11 +1556,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
14371556 curl_easy_setopt (curl , CURLOPT_PORT , srvc -> port );
14381557
14391558 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+ }
14451575 }
14461576
14471577 if (!srvc -> use_ssl )
0 commit comments