I get this range-diff with git 2.49
Range-diff:
-: ---------- > 1: 4757d0305d imap-send: fix bug causing cfg->folder being set to NULL
1: 62edbcfc6e ! 2: f5ad01abc5 imap-send: enable specifying the folder using the command line
@@ Metadata
Author: Aditya Garg <gargaditya08@live.com>
## Commit message ##
- imap-send: enable specifying the folder using the command line
+ imap-send: add support for OAuth2.0 authentication
- Some users may very often want to imap-send messages to a folder
- other than the default set in the config. Add a command line
- argument for the same.
+ OAuth2.0 is a new way of authentication supported by various email providers
+ these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used
+ for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas
+ XOAUTH2 is Google's proprietary mechanism (See [3]).
+
+ [1]: https://datatracker.ietf.org/doc/html/rfc5801
+ [2]: https://datatracker.ietf.org/doc/html/rfc7628
+ [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
Signed-off-by: Aditya Garg <gargaditya08@live.com>
## Documentation/config/imap.adoc ##
-@@
- imap.folder::
- The folder to drop the mails into, which is typically the Drafts
-- folder. For example: "INBOX.Drafts", "INBOX/Drafts" or
-- "[Gmail]/Drafts". Required.
-+ folder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or
-+ '[Gmail]/Drafts'. Required if `--folder` argument is not used. If
-+ set and `--folder` is also used, `--folder` will be preferred.
-
- imap.tunnel::
- Command used to set up a tunnel to the IMAP server through which
+@@ Documentation/config/imap.adoc: imap.authMethod::
+ Specify the authentication method for authenticating with the IMAP server.
+ If Git was built with the NO_CURL option, or if your curl version is older
+ than 7.34.0, or if you're running git-imap-send with the `--no-curl`
+- option, the only supported method is 'CRAM-MD5'. If this is not set
+- then 'git imap-send' uses the basic IMAP plaintext LOGIN command.
++ option, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and
++ 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP
++ plaintext LOGIN command.
## Documentation/git-imap-send.adoc ##
-@@ Documentation/git-imap-send.adoc: OPTIONS
- --quiet::
- Be quiet.
-
-+-f <folder>::
-+--folder <folder>::
-+ Specify the folder in which the emails have to saved.
-+ For example: `--folder [Gmail]/Drafts` or `-f INBOX/Drafts`.
-+
- --curl::
- Use libcurl to communicate with the IMAP server, unless tunneling
- into it. Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND
+@@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:
+
+ ---------
+ [imap]
+- folder = "[Gmail]/Drafts"
+- host = imaps://imap.gmail.com
+- user = user@gmail.com
+- port = 993
++ folder = "[Gmail]/Drafts"
++ host = imaps://imap.gmail.com
++ user = user@gmail.com
++ port = 993
+ ---------
+
++Gmail does not allow using your account password for `git imap-send`.
++If you have multi-factor authentication set up on your Gmail account, you can generate
++an app-specific password for use with `git imap-send`.
++Visit https://security.google.com/settings/security/apppasswords to create it.
++If you do not want to enable multi-factor authentication, you can use OAuth2.0
++authentication as described below.
++
+ [NOTE]
+ You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
+ that the "Folder doesn't exist".
+@@ Documentation/git-imap-send.adoc: that the "Folder doesn't exist".
+ If your Gmail account is set to another language than English, the name of the "Drafts"
+ folder will be localized.
+
++If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`
++or `XOAUTH2` mechanism in your config. In such a case you will have to use an
++OAuth2.0 access token in place of your password.
++
++---------
++[imap]
++ folder = "[Gmail]/Drafts"
++ host = imaps://imap.gmail.com
++ user = user@gmail.com
++ port = 993
++ authmethod = OAUTHBEARER
++---------
++
++Using Outlook's IMAP interface:
++
++Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it
++supports only `XOAUTH2` as the mechanism.
++
++---------
++[imap]
++ folder = "Drafts"
++ host = imaps://outlook.office365.com
++ user = user@outlook.com
++ port = 993
++ authmethod = XOAUTH2
++---------
++
+ Once the commits are ready to be sent, run the following command:
+
+ $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
+@@ Documentation/git-imap-send.adoc: Just make sure to disable line wrapping in the email client (Gmail's web
+ interface will wrap lines no matter what, so you need to use a real
+ IMAP client).
+
++In case you are using OAuth2.0 authentication, it is easier to use credential
++helpers to generate tokens. Credential helpers suggested in
++linkgit:git-send-email[1] can be used for `git imap-send` as well.
++
+ CAUTION
+ -------
+ It is still your responsibility to make sure that the email message
## imap-send.c ##
-@@
+@@ imap-send.c: enum CAPABILITY {
+ LITERALPLUS,
+ NAMESPACE,
+ STARTTLS,
+- AUTH_CRAM_MD5
++ AUTH_CRAM_MD5,
++ AUTH_OAUTHBEARER,
++ AUTH_XOAUTH2
+ };
- static int verbosity;
- static int use_curl = USE_CURL_DEFAULT;
-+static char *opt_folder = NULL;
+ static const char *cap_list[] = {
+@@ imap-send.c: static const char *cap_list[] = {
+ "NAMESPACE",
+ "STARTTLS",
+ "AUTH=CRAM-MD5",
++ "AUTH=OAUTHBEARER",
++ "AUTH=XOAUTH2",
+ };
- static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <mbox>", NULL };
+ #define RESP_OK 0
+@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)
+ return (char *)response_64;
+ }
- static struct option imap_send_options[] = {
- OPT__VERBOSITY(&verbosity),
- OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
-+ OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"),
- OPT_END()
- };
++static char *oauthbearer_base64(const char *user, const char *access_token)
++{
++ int raw_len, b64_len;
++ char *raw, *b64;
++
++ /* Compose the OAUTHBEARER string
++ *
++ * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
++ *
++ * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
++ * * gs2-cb-flag `n` -> client does not support CB
++ * * gs2-authzid `a=" {User} "`
++ *
++ * The second part are key value pairs containing host, port and auth as
++ * described in RFC7628.
++ *
++ * https://datatracker.ietf.org/doc/html/rfc5801
++ * https://datatracker.ietf.org/doc/html/rfc7628
++ */
++ raw_len = strlen(user) + strlen(access_token) + 20;
++ raw = xmallocz(raw_len + 1);
++ snprintf(raw, raw_len + 1, "n,a=%s,\001auth=Bearer %s\001\001", user, access_token);
++
++ /* Base64 encode */
++ b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
++ b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
++ free(raw);
++
++ if (b64_len < 0) {
++ free(b64);
++ return NULL;
++ }
++ return b64;
++}
++
++static char *xoauth2_base64(const char *user, const char *access_token)
++{
++ int raw_len, b64_len;
++ char *raw, *b64;
++
++ /* Compose the XOAUTH2 string
++ * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
++ * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
++ */
++ raw_len = strlen(user) + strlen(access_token) + 20;
++ raw = xmallocz(raw_len + 1);
++ snprintf(raw, raw_len + 1, "user=%s\001auth=Bearer %s\001\001", user, access_token);
++
++ /* Base64 encode */
++ b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
++ b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
++ free(raw);
++
++ if (b64_len < 0) {
++ free(b64);
++ return NULL;
++ }
++ return b64;
++}
++
+ #else
-@@ imap-send.c: int cmd_main(int argc, const char **argv)
+ static char *cram(const char *challenge_64 UNUSED,
+@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,
+ "you have to build git-imap-send with OpenSSL library.");
+ }
- argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
++static char *oauthbearer_base64(const char *user UNUSED,
++ const char *access_token UNUSED)
++{
++ die("You are trying to use OAUTHBEARER authenticate method "
++ "with OpenSSL library, but it's support has not been compiled in.");
++}
++
++static char *xoauth2_base64(const char *user UNUSED,
++ const char *access_token UNUSED)
++{
++ die("You are trying to use XOAUTH2 authenticate method "
++ "with OpenSSL library, but it's support has not been compiled in.");
++}
++
+ #endif
-+ if (opt_folder) {
-+ free(server.folder);
-+ server.folder = xstrdup(opt_folder);
+ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
+@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
+ return 0;
+ }
+
++static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)
++{
++ int ret;
++ char *b64;
++
++ b64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);
++ if (!b64)
++ return error("OAUTHBEARER: base64 encoding failed");
++
++ /* Send the base64-encoded response */
++ ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
++ if (ret != (int)strlen(b64)) {
++ free(b64);
++ return error("IMAP error: sending OAUTHBEARER response failed");
++ }
++
++ free(b64);
++ return 0;
++}
++
++static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)
++{
++ int ret;
++ char *b64;
++
++ b64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);
++ if (!b64)
++ return error("XOAUTH2: base64 encoding failed");
++
++ /* Send the base64-encoded response */
++ ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
++ if (ret != (int)strlen(b64)) {
++ free(b64);
++ return error("IMAP error: sending XOAUTH2 response failed");
+ }
+
- if (argc)
- usage_with_options(imap_send_usage, imap_send_options);
++ free(b64);
++ return 0;
++}
++
+ static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)
+ {
+ if (srvc->user && srvc->pass)
+@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
+ fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n");
+ goto bail;
+ }
++ } else if (!strcmp(srvc->auth_method, "OAUTHBEARER")) {
++ if (!CAP(AUTH_OAUTHBEARER)) {
++ fprintf(stderr, "You specified "
++ "OAUTHBEARER as authentication method, "
++ "but %s doesn't support it.\n", srvc->host);
++ goto bail;
++ }
++ /* OAUTHBEARER */
++
++ memset(&cb, 0, sizeof(cb));
++ cb.cont = auth_oauthbearer;
++ if (imap_exec(ctx, &cb, "AUTHENTICATE OAUTHBEARER") != RESP_OK) {
++ fprintf(stderr, "IMAP error: AUTHENTICATE OAUTHBEARER failed\n");
++ goto bail;
++ }
++ } else if (!strcmp(srvc->auth_method, "XOAUTH2")) {
++ if (!CAP(AUTH_XOAUTH2)) {
++ fprintf(stderr, "You specified "
++ "XOAUTH2 as authentication method, "
++ "but %s doesn't support it.\n", srvc->host);
++ goto bail;
++ }
++ /* XOAUTH2 */
++
++ memset(&cb, 0, sizeof(cb));
++ cb.cont = auth_xoauth2;
++ if (imap_exec(ctx, &cb, "AUTHENTICATE XOAUTH2") != RESP_OK) {
++ fprintf(stderr, "IMAP error: AUTHENTICATE XOAUTH2 failed\n");
++ goto bail;
++ }
+ } else {
+ fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
+ goto bail;
+@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
+
+ server_fill_credential(srvc, cred);
+ curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);
+- curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
++
++ if (!srvc->auth_method ||
++ strcmp(srvc->auth_method, "XOAUTH2") ||
++ strcmp(srvc->auth_method, "OAUTHBEARER"))
++ curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
+
+ strbuf_addstr(&path, srvc->use_ssl ? "imaps://" : "imap://");
+ strbuf_addstr(&path, srvc->host);
+@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
+ curl_easy_setopt(curl, CURLOPT_PORT, srvc->port);
+
+ if (srvc->auth_method) {
+- struct strbuf auth = STRBUF_INIT;
+- strbuf_addstr(&auth, "AUTH=");
+- strbuf_addstr(&auth, srvc->auth_method);
+- curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
+- strbuf_release(&auth);
++ if (!strcmp(srvc->auth_method, "XOAUTH2") ||
++ !strcmp(srvc->auth_method, "OAUTHBEARER")) {
++
++ /* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
++ * upon debugging, it has been found that it is capable of detecting
++ * the best option out of OAUTHBEARER and XOAUTH2.
++ */
++ curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);
++ } else {
++ struct strbuf auth = STRBUF_INIT;
++ strbuf_addstr(&auth, "AUTH=");
++ strbuf_addstr(&auth, srvc->auth_method);
++ curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
++ strbuf_release(&auth);
++ }
+ }
+ if (!srvc->use_ssl)
-: ---------- > 3: e3dc19dc49 imap-send: add PLAIN authentication method to OpenSSL
-: ---------- > 4: 11f7ac1325 imap-send: fix memory leak in case auth_cram_md5 fails
-: ---------- > 5: f6e7a5498e imap-send: enable specifying the folder using the command line
2: 245cc89cca = 6: 4769924781 imap-send: enable user to choose between libcurl and openssl using the config