On 28-05-2025 03:06 pm, Aditya Garg wrote: > > > On 28-05-2025 12:53 pm, Eric Sunshine wrote: >> On Wed, May 28, 2025 at 3:05 AM Aditya Garg <gargaditya08@xxxxxxxx> wrote: >>> v5: Added a patch to make the purpose of using app password for Gmail >>> more clear in `send-email` documentation. >>> >>> Range-diff: >>> -: ---------- > 1: 2c47cc5396 docs: add credential helper for yahoo and link Google's sendgmail tool >>> -: ---------- > 2: bc1d0471ca docs: improve formatting in git-send-email documentation >>> -: ---------- > 3: b9e41e2492 docs: remove credential helper links for emails from gitcredentials >>> -: ---------- > 4: a6ad7ac810 docs: make the purpose of using app password for Gmail more clear in send-email >> >> Strange range-diff. > > I think because I used git version 2.43. When I compiled my branch with changes, git format-patch over there > had a very different range-diff from this. 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@xxxxxxxx> ## 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@xxxxxxxx> ## 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@xxxxxxxxx +- port = 993 ++ folder = "[Gmail]/Drafts" ++ host = imaps://imap.gmail.com ++ user = user@xxxxxxxxx ++ 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@xxxxxxxxx ++ 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@xxxxxxxxxxx ++ 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 -- 2.49.0.windows.1