{"thread":{"id":"63502","subject":"[PATCH 0/2] imap-send: make it usable again and add OAuth2.0 support","startedAt":"2025-05-22T17:29:12Z","lastAt":"2025-06-23T16:27:03Z","messageCount":248,"participants":["Aditya Garg","Eric Sunshine","Jeff King","Junio C Hamano","brian m. carlson","Ben Knoble","Phillip Wood"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"518675","messageId":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":null,"subject":"[PATCH 0/2] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T17:27:14Z","receivedAt":"2025-05-22T17:29:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Hi all,\n\nThis patch series does 2 things. Firstly it basically makes the imap-send\ncommand usable again since it was broken because of not being able to correctly\nparse the config file. The second patch adds support for OAuth2.0 authentication\nto git imap-send.\n\nP.S.: I am surprised this thing even exists xD.\n\nAditya Garg (2):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  48 +++++++++-\n imap-send.c                      | 158 ++++++++++++++++++++++++++++++-\n 3 files changed, 200 insertions(+), 11 deletions(-)\n\n-- \n2.49.0\n\n"},{"id":"518676","messageId":"PN3PR01MB95970F8CF7527648EC4BE907B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T17:27:15Z","receivedAt":"2025-05-22T17:29:15Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Upon setting up imap-send config file, I encountered the very first bug.\nAn error showing \"no imap store specified\" was being displayed on the\nterminal. Upon investigating further, in static int git_imap_config,\ncfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\nimap.tunnel and imap.authmethod were defined, and the values that these configs\nintended to set were not being set at all. Because of this, git imap-send was\nbasically not usable at all. The bug seems to be there for quite a while, and\nhas not yet been detected, likely due to better options like git send-email\nbeing available.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"518677","messageId":"PN3PR01MB959744EC6D19FEB8D197C434B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH 2/2] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T17:27:16Z","receivedAt":"2025-05-22T17:29:17Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  48 +++++++++-\n imap-send.c                      | 150 ++++++++++++++++++++++++++++++-\n 3 files changed, 196 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..dcb0db5563 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then 'git imap-send' uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..582a46672b 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,16 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,14 +120,50 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n \n+[NOTE]\n+Due to some bugs in libcurl, OAuth2.0 authentication may fail if curl is used\n+for IMAP. In case you face the same issue, use `git imap-send` with `--no-curl`\n+option.\n+\n Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..4390001cf8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"If you want to use OAUTHBEARER authenticate method, \"\n+\t    \"you have to build git-imap-send with OpenSSL library.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"If you want to use XOAUTH2 authenticate method, \"\n+\t    \"you have to build git-imap-send with OpenSSL library.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0\n\n"},{"id":"518681","messageId":"CAPig+cRNyEC5LjK1GhGBbEtf3xRu_ZS4RKizFhwjE8fP8sGwTA@mail.gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB95970F8CF7527648EC4BE907B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-22T18:00:18Z","receivedAt":"2025-05-22T18:00:31Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 22, 2025 at 1:29 PM Aditya Garg <gargaditya08@live.com> wrote:\n> Upon setting up imap-send config file, I encountered the very first bug.\n> An error showing \"no imap store specified\" was being displayed on the\n> terminal. Upon investigating further, in static int git_imap_config,\n> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n> imap.tunnel and imap.authmethod were defined, and the values that these configs\n> intended to set were not being set at all. Because of this, git imap-send was\n> basically not usable at all. The bug seems to be there for quite a while, and\n> has not yet been detected, likely due to better options like git send-email\n> being available.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n> diff --git a/imap-send.c b/imap-send.c\n> @@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n>                 FREE_AND_NULL(cfg->folder);\n>                 return git_config_string(&cfg->folder, var, val);\n>         } else if (!strcmp(\"imap.user\", var)) {\n> -               FREE_AND_NULL(cfg->folder);\n> +               FREE_AND_NULL(cfg->user);\n>                 return git_config_string(&cfg->user, var, val);\n>         } else if (!strcmp(\"imap.pass\", var)) {\n> -               FREE_AND_NULL(cfg->folder);\n> +               FREE_AND_NULL(cfg->pass);\n>                 return git_config_string(&cfg->pass, var, val);\n>         } else if (!strcmp(\"imap.tunnel\", var)) {\n> -               FREE_AND_NULL(cfg->folder);\n> +               FREE_AND_NULL(cfg->tunnel);\n>                 return git_config_string(&cfg->tunnel, var, val);\n>         } else if (!strcmp(\"imap.authmethod\", var)) {\n> -               FREE_AND_NULL(cfg->folder);\n> +               FREE_AND_NULL(cfg->auth_method);\n>                 return git_config_string(&cfg->auth_method, var, val);\n\nOkay, makes sense. It might be worth mentioning in the commit message\nthat these copy/paste bugs were introduced by 6d1f198f34 (imap-send:\nfix leaking memory in `imap_server_conf`, 2024-06-07).\n\nSquinting at the code a bit more, am I correct in thinking that\n6d1f198f34 missed a case and that the function is still leaking\n`cfg->host` in the \"imap.host\" conditional? I haven't traced the code\nor all the callers, but I wonder if server_fill_credential() in the\nsame file may also be leaky. In any event, the `cfg->host` and the\npossible server_fill_credential() leaks are outside the scope of this\nbug-fix patch.\n"},{"id":"518682","messageId":"PN3PR01MB95976C4FF54E09F369AB02EDB899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cRNyEC5LjK1GhGBbEtf3xRu_ZS4RKizFhwjE8fP8sGwTA@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T18:04:13Z","receivedAt":"2025-05-22T18:04:23Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 22-05-2025 11:30 pm, Eric Sunshine wrote:\n> On Thu, May 22, 2025 at 1:29 PM Aditya Garg <gargaditya08@live.com> wrote:\n>> Upon setting up imap-send config file, I encountered the very first bug.\n>> An error showing \"no imap store specified\" was being displayed on the\n>> terminal. Upon investigating further, in static int git_imap_config,\n>> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n>> imap.tunnel and imap.authmethod were defined, and the values that these configs\n>> intended to set were not being set at all. Because of this, git imap-send was\n>> basically not usable at all. The bug seems to be there for quite a while, and\n>> has not yet been detected, likely due to better options like git send-email\n>> being available.\n>>\n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> diff --git a/imap-send.c b/imap-send.c\n>> @@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n>>                 FREE_AND_NULL(cfg->folder);\n>>                 return git_config_string(&cfg->folder, var, val);\n>>         } else if (!strcmp(\"imap.user\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->user);\n>>                 return git_config_string(&cfg->user, var, val);\n>>         } else if (!strcmp(\"imap.pass\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->pass);\n>>                 return git_config_string(&cfg->pass, var, val);\n>>         } else if (!strcmp(\"imap.tunnel\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->tunnel);\n>>                 return git_config_string(&cfg->tunnel, var, val);\n>>         } else if (!strcmp(\"imap.authmethod\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->auth_method);\n>>                 return git_config_string(&cfg->auth_method, var, val);\n> \n> Okay, makes sense. It might be worth mentioning in the commit message\n> that these copy/paste bugs were introduced by 6d1f198f34 (imap-send:\n> fix leaking memory in `imap_server_conf`, 2024-06-07).\n\nThanks, I was wondering what commit brought this bug in the first place.\n\n> Squinting at the code a bit more, am I correct in thinking that\n> 6d1f198f34 missed a case and that the function is still leaking\n> `cfg->host` in the \"imap.host\" conditional? I haven't traced the code\n> or all the callers, but I wonder if server_fill_credential() in the\n> same file may also be leaky. In any event, the `cfg->host` and the\n> possible server_fill_credential() leaks are outside the scope of this\n> bug-fix patch.\n\nWhile they are outside the scope, in case I am able to get them fixed,\nI can sent another patch for the same.\n\n"},{"id":"518683","messageId":"PN3PR01MB95971AADEF1C768E58187419B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cRNyEC5LjK1GhGBbEtf3xRu_ZS4RKizFhwjE8fP8sGwTA@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T18:21:26Z","receivedAt":"2025-05-22T18:21:34Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 22-05-2025 11:30 pm, Eric Sunshine wrote:\n> On Thu, May 22, 2025 at 1:29 PM Aditya Garg <gargaditya08@live.com> wrote:\n>> Upon setting up imap-send config file, I encountered the very first bug.\n>> An error showing \"no imap store specified\" was being displayed on the\n>> terminal. Upon investigating further, in static int git_imap_config,\n>> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n>> imap.tunnel and imap.authmethod were defined, and the values that these configs\n>> intended to set were not being set at all. Because of this, git imap-send was\n>> basically not usable at all. The bug seems to be there for quite a while, and\n>> has not yet been detected, likely due to better options like git send-email\n>> being available.\n>>\n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> diff --git a/imap-send.c b/imap-send.c\n>> @@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n>>                 FREE_AND_NULL(cfg->folder);\n>>                 return git_config_string(&cfg->folder, var, val);\n>>         } else if (!strcmp(\"imap.user\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->user);\n>>                 return git_config_string(&cfg->user, var, val);\n>>         } else if (!strcmp(\"imap.pass\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->pass);\n>>                 return git_config_string(&cfg->pass, var, val);\n>>         } else if (!strcmp(\"imap.tunnel\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->tunnel);\n>>                 return git_config_string(&cfg->tunnel, var, val);\n>>         } else if (!strcmp(\"imap.authmethod\", var)) {\n>> -               FREE_AND_NULL(cfg->folder);\n>> +               FREE_AND_NULL(cfg->auth_method);\n>>                 return git_config_string(&cfg->auth_method, var, val);\n> \n> Okay, makes sense. It might be worth mentioning in the commit message\n> that these copy/paste bugs were introduced by 6d1f198f34 (imap-send:\n> fix leaking memory in `imap_server_conf`, 2024-06-07).\n> \n> Squinting at the code a bit more, am I correct in thinking that\n> 6d1f198f34 missed a case and that the function is still leaking\n> `cfg->host` in the \"imap.host\" conditional? I haven't traced the code\n> or all the callers, but I wonder if server_fill_credential() in the\n> same file may also be leaky. In any event, the `cfg->host` and the\n> possible server_fill_credential() leaks are outside the scope of this\n> bug-fix patch.\n\n\nNot sure about server_fill_credential(), but I think this is also\na potential memory leak\n\nstatic int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n{\n\tint ret;\n\tchar *response;\n\n\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n\n\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n\tif (ret != strlen(response))\n+\t\tfree(response); // fix for the leak\n\t\treturn error(\"IMAP error: sending response failed\");\n\n\tfree(response);\n\n\treturn 0;\n}\n"},{"id":"518684","messageId":"CAPig+cTJmeczzUcGrn98svMfK7aODYS-Ha8FxJHuKU2c2+R-FQ@mail.gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB95971AADEF1C768E58187419B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-22T18:25:47Z","receivedAt":"2025-05-22T18:25:59Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 22, 2025 at 2:21 PM Aditya Garg <gargaditya08@live.com> wrote:\n> On 22-05-2025 11:30 pm, Eric Sunshine wrote:\n> > Squinting at the code a bit more, am I correct in thinking that\n> > 6d1f198f34 missed a case and that the function is still leaking\n> > `cfg->host` in the \"imap.host\" conditional? I haven't traced the code\n> > or all the callers, but I wonder if server_fill_credential() in the\n> > same file may also be leaky. In any event, the `cfg->host` and the\n> > possible server_fill_credential() leaks are outside the scope of this\n> > bug-fix patch.\n>\n> Not sure about server_fill_credential(), but I think this is also\n> a potential memory leak\n\nAgreed.\n\n> static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> {\n>         int ret;\n>         char *response;\n>\n>         response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n>\n>         ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>         if (ret != strlen(response))\n> +               free(response); // fix for the leak\n>                 return error(\"IMAP error: sending response failed\");\n>\n>         free(response);\n>\n>         return 0;\n> }\n\nIt's subjective, but I would probably fix this a little bit\ndifferently and (to my mind) more simply:\n\n    response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n\n    ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n    free(response);\n    if (ret != strlen(response))\n        return error(\"IMAP error: sending response failed\");\n    return 0;\n"},{"id":"518685","messageId":"PN3PR01MB9597F1026B3ED4A0953FC9DFB899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cTJmeczzUcGrn98svMfK7aODYS-Ha8FxJHuKU2c2+R-FQ@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T18:28:42Z","receivedAt":"2025-05-22T18:28:49Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 22-05-2025 11:55 pm, Eric Sunshine wrote:\n> On Thu, May 22, 2025 at 2:21 PM Aditya Garg <gargaditya08@live.com> wrote:\n>> On 22-05-2025 11:30 pm, Eric Sunshine wrote:\n>>> Squinting at the code a bit more, am I correct in thinking that\n>>> 6d1f198f34 missed a case and that the function is still leaking\n>>> `cfg->host` in the \"imap.host\" conditional? I haven't traced the code\n>>> or all the callers, but I wonder if server_fill_credential() in the\n>>> same file may also be leaky. In any event, the `cfg->host` and the\n>>> possible server_fill_credential() leaks are outside the scope of this\n>>> bug-fix patch.\n>>\n>> Not sure about server_fill_credential(), but I think this is also\n>> a potential memory leak\n> \n> Agreed.\n> \n>> static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n>> {\n>>         int ret;\n>>         char *response;\n>>\n>>         response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n>>\n>>         ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>>         if (ret != strlen(response))\n>> +               free(response); // fix for the leak\n>>                 return error(\"IMAP error: sending response failed\");\n>>\n>>         free(response);\n>>\n>>         return 0;\n>> }\n> \n> It's subjective, but I would probably fix this a little bit\n> differently and (to my mind) more simply:\n> \n>     response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n> \n>     ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>     free(response);\n>     if (ret != strlen(response))\n>         return error(\"IMAP error: sending response failed\");\n>     return 0;\n\nLooks equally good. Such minor fixes can be included in this series.\n\n"},{"id":"518686","messageId":"20250522182924.GA14871@coredump.intra.peff.net","threadId":"63502","inReplyTo":"PN3PR01MB95970F8CF7527648EC4BE907B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-05-22T18:29:24Z","receivedAt":"2025-05-22T18:29:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 22, 2025 at 05:27:15PM +0000, Aditya Garg wrote:\n\n> Upon setting up imap-send config file, I encountered the very first bug.\n> An error showing \"no imap store specified\" was being displayed on the\n> terminal. Upon investigating further, in static int git_imap_config,\n> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n> imap.tunnel and imap.authmethod were defined, and the values that these configs\n> intended to set were not being set at all.\n\nI read \"these configs[...]were not being set at all\" as imap.user, etc.\nBut I think the only thing affected was imap.folder, which was\nincorrectly being reset when we saw the other fields (and of course the\nleak-fix for those fields was not kicking in correctly).\n\nSo:\n\n  [imap]\n  host = example.com\n  user = foo\n  folder = INBOX\n\nwas fine, but:\n\n  [imap]\n  host = example.com\n  folder = INBOX\n  user = foo\n\nwas not (we end up with a NULL folder variable).\n\n> Because of this, git imap-send was basically not usable at all. The\n> bug seems to be there for quite a while, and has not yet been\n> detected, likely due to better options like git send-email being\n> available.\n\nI think that probably explains why it was not detected (by users or the\ntests). It was dependent on the usage and ordering of particular config\noptions.\n\n(The patch is still doing the right thing, of course; I'm just trying to\nadd more context to the commit message).\n\n-Peff\n"},{"id":"518687","messageId":"PN3PR01MB9597DB297B1F23E65443B2F7B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"20250522182924.GA14871@coredump.intra.peff.net","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T18:31:06Z","receivedAt":"2025-05-22T18:31:14Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 22-05-2025 11:59 pm, Jeff King wrote:\n> On Thu, May 22, 2025 at 05:27:15PM +0000, Aditya Garg wrote:\n> \n>> Upon setting up imap-send config file, I encountered the very first bug.\n>> An error showing \"no imap store specified\" was being displayed on the\n>> terminal. Upon investigating further, in static int git_imap_config,\n>> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n>> imap.tunnel and imap.authmethod were defined, and the values that these configs\n>> intended to set were not being set at all.\n> \n> I read \"these configs[...]were not being set at all\" as imap.user, etc.\n> But I think the only thing affected was imap.folder, which was\n> incorrectly being reset when we saw the other fields (and of course the\n> leak-fix for those fields was not kicking in correctly).\n> \n> So:\n> \n>   [imap]\n>   host = example.com\n>   user = foo\n>   folder = INBOX\n> \n> was fine, but:\n> \n>   [imap]\n>   host = example.com\n>   folder = INBOX\n>   user = foo\n> \n> was not (we end up with a NULL folder variable).\n> \n>> Because of this, git imap-send was basically not usable at all. The\n>> bug seems to be there for quite a while, and has not yet been\n>> detected, likely due to better options like git send-email being\n>> available.\n> \n> I think that probably explains why it was not detected (by users or the\n> tests). It was dependent on the usage and ordering of particular config\n> options.\n> \n> (The patch is still doing the right thing, of course; I'm just trying to\n> add more context to the commit message).\n\nmhm, looks like I need to read the code again.\n"},{"id":"518688","messageId":"20250522183121.GB14871@coredump.intra.peff.net","threadId":"63502","inReplyTo":"CAPig+cTJmeczzUcGrn98svMfK7aODYS-Ha8FxJHuKU2c2+R-FQ@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-05-22T18:31:21Z","receivedAt":"2025-05-22T18:31:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, May 22, 2025 at 02:25:47PM -0400, Eric Sunshine wrote:\n\n> It's subjective, but I would probably fix this a little bit\n> differently and (to my mind) more simply:\n> \n>     response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n> \n>     ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>     free(response);\n>     if (ret != strlen(response))\n>         return error(\"IMAP error: sending response failed\");\n\nDoesn't that introduce a use-after-free for response? You'd have to\nstore the strlen() result in a local variable.\n\n-Peff\n"},{"id":"518689","messageId":"CAPig+cQK6i1QdA-iAcpEybQq2GcXX2uKSaeN3_5GRR-5ScJzuw@mail.gmail.com","threadId":"63502","inReplyTo":"20250522183121.GB14871@coredump.intra.peff.net","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-22T18:33:59Z","receivedAt":"2025-05-22T18:34:11Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 22, 2025 at 2:31 PM Jeff King <peff@peff.net> wrote:\n> On Thu, May 22, 2025 at 02:25:47PM -0400, Eric Sunshine wrote:\n> > It's subjective, but I would probably fix this a little bit\n> > differently and (to my mind) more simply:\n> >\n> >     response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n> >\n> >     ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n> >     free(response);\n> >     if (ret != strlen(response))\n> >         return error(\"IMAP error: sending response failed\");\n>\n> Doesn't that introduce a use-after-free for response? You'd have to\n> store the strlen() result in a local variable.\n\nYou're correct, of course. Aditya, ignore my suggestion.\n"},{"id":"518701","messageId":"xmqqldqo5uzm.fsf@gitster.g","threadId":"63502","inReplyTo":"CAPig+cRNyEC5LjK1GhGBbEtf3xRu_ZS4RKizFhwjE8fP8sGwTA@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-22T19:02:53Z","receivedAt":"2025-05-22T19:02:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Okay, makes sense. It might be worth mentioning in the commit message\n> that these copy/paste bugs were introduced by 6d1f198f34 (imap-send:\n> fix leaking memory in `imap_server_conf`, 2024-06-07).\n\nDefinitely a good thing to note in the message, together with the\nsubtlety that the bug is dependenty on the order in which these\nconfiguration variables appear in the file.\n\nIn any case, this may indicate that the population of those who\ntried to use imap-send since mid last year, whether they used to use\nit happily before last year or they tried to use it anew, must be\nvery small, or we would have heard about this obvious gotcha by now.\n\n"},{"id":"518702","messageId":"PN3PR01MB9597F896FE350C83D2A8821EB899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqldqo5uzm.fsf@gitster.g","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:04:20Z","receivedAt":"2025-05-22T19:04:27Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 23-05-2025 12:32 am, Junio C Hamano wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n>> Okay, makes sense. It might be worth mentioning in the commit message\n>> that these copy/paste bugs were introduced by 6d1f198f34 (imap-send:\n>> fix leaking memory in `imap_server_conf`, 2024-06-07).\n> \n> Definitely a good thing to note in the message, together with the\n> subtlety that the bug is dependenty on the order in which these\n> configuration variables appear in the file.\n> \n> In any case, this may indicate that the population of those who\n> tried to use imap-send since mid last year, whether they used to use\n> it happily before last year or they tried to use it anew, must be\n> very small, or we would have heard about this obvious gotcha by now.\n> \n\nI also just managed to use OAuth2.0 with curl, apparently curl has\ndedicated API for this.\n\nWill send a v2 with that fix as well.\n"},{"id":"518706","messageId":"PN3PR01MB95972EB02A873B8998F51877B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95971AADEF1C768E58187419B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:30:16Z","receivedAt":"2025-05-22T19:30:25Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"> Not sure about server_fill_credential(), but I think this is also\n> a potential memory leak\n> \n> static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> {\n> \tint ret;\n> \tchar *response;\n> \n> \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n> \n> \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n> \tif (ret != strlen(response))\n> +\t\tfree(response); // fix for the leak\n> \t\treturn error(\"IMAP error: sending response failed\");\n> \n> \tfree(response);\n> \n> \treturn 0;\n> }\n\n\nSo this change leads to:\n\n  imap-send.c:990:2: error: code will never be executed [-Werror,-Wunreachable-code]\n          free(response);\n          ^~~~\n\nI think it's better to leave the other leaks for a different series. You might want to\ntreat it as a bug report as well.\n"},{"id":"518707","messageId":"CAPig+cQkrwy2GBNh7OdBAzWnuSo7mVH1XFUyuB7LzJ4cMf10MQ@mail.gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB95972EB02A873B8998F51877B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-22T19:32:02Z","receivedAt":"2025-05-22T19:32:15Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, May 22, 2025 at 3:30 PM Aditya Garg <gargaditya08@live.com> wrote:\n> > Not sure about server_fill_credential(), but I think this is also\n> > a potential memory leak\n> >\n> > static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> > {\n> >       int ret;\n> >       char *response;\n> >\n> >       response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n> >\n> >       ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n> >       if (ret != strlen(response))\n> > +             free(response); // fix for the leak\n> >               return error(\"IMAP error: sending response failed\");\n> >\n> >       free(response);\n> >\n> >       return 0;\n> > }\n>\n> So this change leads to:\n>\n>   imap-send.c:990:2: error: code will never be executed [-Werror,-Wunreachable-code]\n>           free(response);\n>           ^~~~\n\nIs that because you forgot the curly braces around the `if` body?\n\n    if (ret != strlen(response)) {\n        free(response);\n        return error(\"IMAP error: sending response failed\");\n    }\n\n    free(response);\n"},{"id":"518708","messageId":"PN3PR01MB9597FEFB450A45DA3E9E0716B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cQkrwy2GBNh7OdBAzWnuSo7mVH1XFUyuB7LzJ4cMf10MQ@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:40:31Z","receivedAt":"2025-05-22T19:40:39Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 23-05-2025 01:02 am, Eric Sunshine wrote:\n> On Thu, May 22, 2025 at 3:30 PM Aditya Garg <gargaditya08@live.com> wrote:\n>>> Not sure about server_fill_credential(), but I think this is also\n>>> a potential memory leak\n>>>\n>>> static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n>>> {\n>>>       int ret;\n>>>       char *response;\n>>>\n>>>       response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n>>>\n>>>       ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>>>       if (ret != strlen(response))\n>>> +             free(response); // fix for the leak\n>>>               return error(\"IMAP error: sending response failed\");\n>>>\n>>>       free(response);\n>>>\n>>>       return 0;\n>>> }\n>>\n>> So this change leads to:\n>>\n>>   imap-send.c:990:2: error: code will never be executed [-Werror,-Wunreachable-code]\n>>           free(response);\n>>           ^~~~\n> \n> Is that because you forgot the curly braces around the `if` body?\n\nYes I did forget. Side effects of writing python for a few days ;)\n\nNot working on the port leaks rn though, the logic seems different from the strings.\nAnd again, feel free to treat rest as a bug report.\n"},{"id":"518709","messageId":"aC9-7Z4VW7gXw_l8@tapette.crustytoothpaste.net","threadId":"63502","inReplyTo":"PN3PR01MB959744EC6D19FEB8D197C434B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH 2/2] imap-send: add support for OAuth2.0 authentication","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-05-22T19:45:49Z","receivedAt":"2025-05-22T19:46:12Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-05-22 at 17:27:16, Aditya Garg wrote:\n> +static char *xoauth2_base64(const char *user, const char *access_token)\n> +{\n> +\tint raw_len, b64_len;\n> +\tchar *raw, *b64;\n> +\n> +\t/* Compose the XOAUTH2 string\n> +\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n> +\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n> +\t */\n> +\traw_len = strlen(user) + strlen(access_token) + 20;\n> +\traw = xmallocz(raw_len + 1);\n> +\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n> +\n> +\t/* Base64 encode */\n> +\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n> +\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n> +\tfree(raw);\n> +\n> +\tif (b64_len < 0) {\n> +\t\tfree(b64);\n> +\t\treturn NULL;\n> +\t}\n> +\treturn b64;\n> +}\n> +\n>  #else\n>  \n>  static char *cram(const char *challenge_64 UNUSED,\n> @@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n>  \t    \"you have to build git-imap-send with OpenSSL library.\");\n>  }\n>  \n> +static char *oauthbearer_base64(const char *user UNUSED,\n> +\t\t  const char *access_token UNUSED)\n> +{\n> +\tdie(\"If you want to use OAUTHBEARER authenticate method, \"\n> +\t    \"you have to build git-imap-send with OpenSSL library.\");\n> +}\n\nI don't think this is a good idea.  Linux distros and other parties who\ndistribute OpenSSL cannot legally distribute Git linked against it and\nwe should not require people to use OpenSSL for this.  It looks like all\nyou need here is a base64 encoder and it should be pretty easy to write\nsuch an encoder.  There's a minimal decoder as part of decode_b_segent\nin mailinfo.c and I'm pretty sure we could just add a suitable encoder\nthat writes to a strbuf like we have for percent-encoding.\n\nAlternatively, you could just push this into the credential helper with\nthe new credential helper extensions by adding support for that to git\nimap-send and let the helper implement the base64 encoding.  That's kind\nof the reason I implemented it in the first place.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"518710","messageId":"PN3PR01MB959796C61678ED1857C9B476B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"aC9-7Z4VW7gXw_l8@tapette.crustytoothpaste.net","subject":"Re: [PATCH 2/2] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:49:21Z","receivedAt":"2025-05-22T19:49:29Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 23-05-2025 01:15 am, brian m. carlson wrote:\n> On 2025-05-22 at 17:27:16, Aditya Garg wrote:\n>> +static char *xoauth2_base64(const char *user, const char *access_token)\n>> +{\n>> +\tint raw_len, b64_len;\n>> +\tchar *raw, *b64;\n>> +\n>> +\t/* Compose the XOAUTH2 string\n>> +\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n>> +\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n>> +\t */\n>> +\traw_len = strlen(user) + strlen(access_token) + 20;\n>> +\traw = xmallocz(raw_len + 1);\n>> +\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n>> +\n>> +\t/* Base64 encode */\n>> +\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n>> +\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n>> +\tfree(raw);\n>> +\n>> +\tif (b64_len < 0) {\n>> +\t\tfree(b64);\n>> +\t\treturn NULL;\n>> +\t}\n>> +\treturn b64;\n>> +}\n>> +\n>>  #else\n>>  \n>>  static char *cram(const char *challenge_64 UNUSED,\n>> @@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n>>  \t    \"you have to build git-imap-send with OpenSSL library.\");\n>>  }\n>>  \n>> +static char *oauthbearer_base64(const char *user UNUSED,\n>> +\t\t  const char *access_token UNUSED)\n>> +{\n>> +\tdie(\"If you want to use OAUTHBEARER authenticate method, \"\n>> +\t    \"you have to build git-imap-send with OpenSSL library.\");\n>> +}\n> \n> I don't think this is a good idea.  Linux distros and other parties who\n> distribute OpenSSL cannot legally distribute Git linked against it and\n> we should not require people to use OpenSSL for this.  It looks like all\n> you need here is a base64 encoder and it should be pretty easy to write\n> such an encoder.  There's a minimal decoder as part of decode_b_segent\n> in mailinfo.c and I'm pretty sure we could just add a suitable encoder\n> that writes to a strbuf like we have for percent-encoding.\n> \n> Alternatively, you could just push this into the credential helper with\n> the new credential helper extensions by adding support for that to git\n> imap-send and let the helper implement the base64 encoding.  That's kind\n> of the reason I implemented it in the first place.\n\nWe can still use curl to use these. Curl has a dedicated API for OAuth2.0.\n\nThat is what I am doing with v2.\n\n"},{"id":"518711","messageId":"PN3PR01MB9597EC279126820B74D2D6A5B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v2 0/3] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:49:51Z","receivedAt":"2025-05-22T19:51:48Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does 2 things. Firstly it basically makes the imap-send\ncommand usable again since it was broken because of not being able to correctly\nparse the config file. The second patch adds support for OAuth2.0 authentication\nto git imap-send.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: Added support for OAuth2.0 with curl.\n    Fixed the memory leak in case auth_cram_md5 fails.\n\nAditya Garg (3):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: fix memory leak in case auth_cram_md5 fails\n\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  43 ++++++-\n imap-send.c                      | 188 +++++++++++++++++++++++++++++--\n 3 files changed, 218 insertions(+), 18 deletions(-)\n\n-- \n2.49.0\n\n"},{"id":"518712","messageId":"PN3PR01MB9597488E63B9C1565EFD9631B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597EC279126820B74D2D6A5B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v2 1/3] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:49:52Z","receivedAt":"2025-05-22T19:51:50Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Upon setting up imap-send config file, I encountered the very first bug.\nAn error showing \"no imap store specified\" was being displayed on the\nterminal. Upon investigating further, in static int git_imap_config,\ncfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\nimap.tunnel and imap.authmethod were defined. Because of this, git imap-send\nwas basically not usable at all. The bug seems to be there for quite a while,\nand has not yet been detected, likely due to better options like git send-email\nbeing available.\n\nFixes: 6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"518713","messageId":"PN3PR01MB95977871E1B86560E5F30296B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597EC279126820B74D2D6A5B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v2 2/3] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:49:53Z","receivedAt":"2025-05-22T19:51:52Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  43 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 211 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8b73599d5e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,16 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +120,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +155,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..7616496cba 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"If you want to use OAUTHBEARER authenticate method, \"\n+\t    \"you have to build git-imap-send with OpenSSL library.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"If you want to use XOAUTH2 authenticate method, \"\n+\t    \"you have to build git-imap-send with OpenSSL library.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"518714","messageId":"PN3PR01MB9597F878D41C6CCB10F49587B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597EC279126820B74D2D6A5B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v2 3/3] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-22T19:49:54Z","receivedAt":"2025-05-22T19:51:55Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 7616496cba..2afc4fb63f 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -983,8 +983,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"518732","messageId":"xmqq7c281b14.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597488E63B9C1565EFD9631B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v2 1/3] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-22T23:27:35Z","receivedAt":"2025-05-22T23:27:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Upon setting up imap-send config file, I encountered the very first bug.\n> An error showing \"no imap store specified\" was being displayed on the\n> terminal. Upon investigating further, in static int git_imap_config,\n> cfg->folder was being incorrectly set to NULL in case imap.user, imap.pass,\n> imap.tunnel and imap.authmethod were defined. Because of this, git imap-send\n> was basically not usable at all. The bug seems to be there for quite a while,\n> and has not yet been detected, likely due to better options like git send-email\n> being available.\n>\n> Fixes: 6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\n\nThis project frowns upon this trailer.  One reason is that it may\nlater turn out that this does not fix ;-) And no, this is to suggest\nusing \"attempts-to-fix\" or anything of that sort.  Most other\ntrailers are declaration of facts.  \"Fixes:\" is not.\n\nAlso, in the long run, first-person experience of an author is not\nall that interesting to \"git log\" readers.  \"I did this, I saw that\"\nis something we try not to use too often.\n\nRather, flow it in the problem description, perhaps like\n\n    6d1f198f (imap-send: fix leaking memory in `imap_server_conf`,\n    2024-06-07) broken imap-send with mistaken copy-and-paste and\n    cleared cfg->folder when it should have cleared other members in\n    the structure the code is about to overwrite.  git-imap-send\n    since Git 2.46.0 is unusable due to this bug.\n\nor something to start the description.\n\n\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  imap-send.c | 8 ++++----\n>  1 file changed, 4 insertions(+), 4 deletions(-)\n>\n> diff --git a/imap-send.c b/imap-send.c\n> index 27dc033c7f..37f94a37e8 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n>  \t\tFREE_AND_NULL(cfg->folder);\n>  \t\treturn git_config_string(&cfg->folder, var, val);\n>  \t} else if (!strcmp(\"imap.user\", var)) {\n> -\t\tFREE_AND_NULL(cfg->folder);\n> +\t\tFREE_AND_NULL(cfg->user);\n>  \t\treturn git_config_string(&cfg->user, var, val);\n>  \t} else if (!strcmp(\"imap.pass\", var)) {\n> -\t\tFREE_AND_NULL(cfg->folder);\n> +\t\tFREE_AND_NULL(cfg->pass);\n>  \t\treturn git_config_string(&cfg->pass, var, val);\n>  \t} else if (!strcmp(\"imap.tunnel\", var)) {\n> -\t\tFREE_AND_NULL(cfg->folder);\n> +\t\tFREE_AND_NULL(cfg->tunnel);\n>  \t\treturn git_config_string(&cfg->tunnel, var, val);\n>  \t} else if (!strcmp(\"imap.authmethod\", var)) {\n> -\t\tFREE_AND_NULL(cfg->folder);\n> +\t\tFREE_AND_NULL(cfg->auth_method);\n>  \t\treturn git_config_string(&cfg->auth_method, var, val);\n>  \t} else if (!strcmp(\"imap.port\", var)) {\n>  \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n"},{"id":"518745","messageId":"PN3PR01MB9597F89DF32B700ABB8AEE11B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v3 0/3] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T03:58:31Z","receivedAt":"2025-05-23T04:04:09Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does 2 things. Firstly it basically makes the imap-send\ncommand usable again since it was broken because of not being able to correctly\nparse the config file. The second patch adds support for OAuth2.0 authentication\nto git imap-send.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: Added support for OAuth2.0 with curl.\n    Fixed the memory leak in case auth_cram_md5 fails.\nv3: Improve wording in first patch\n    Change misleading message if OAuth2.0 is used without OpenSSL\n\nAditya Garg (3):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: fix memory leak in case auth_cram_md5 fails\n\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 188 +++++++++++++++++++++++++++++--\n 3 files changed, 221 insertions(+), 18 deletions(-)\n\n-- \n2.49.0\n\n"},{"id":"518746","messageId":"PN3PR01MB9597E47BB9662200E32475CDB898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F89DF32B700ABB8AEE11B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v3 1/3] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T03:58:32Z","receivedAt":"2025-05-23T04:04:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nwas being incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel\nand imap.authmethod were defined. Because of this, git imap-send was basically\nnot usable at all. The bug seems to be there for quite a while, and has not\nyet been detected, likely due to better options like git send-email being\navailable.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"518747","messageId":"PN3PR01MB95974F17913FB881DB8CF2B3B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F89DF32B700ABB8AEE11B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v3 2/3] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T03:58:33Z","receivedAt":"2025-05-23T04:04:14Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..04b507fc14 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"518748","messageId":"PN3PR01MB9597F251FC1AE6A78CC92C93B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F89DF32B700ABB8AEE11B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v3 3/3] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T03:58:34Z","receivedAt":"2025-05-23T04:04:17Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 04b507fc14..e19dc69b7c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -983,8 +983,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"518760","messageId":"PN3PR01MB9597EB8033F1186F62159D68B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v4 0/4] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T12:14:26Z","receivedAt":"2025-05-23T12:23:28Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does 2 things. Firstly it basically makes the imap-send\ncommand usable again since it was broken because of not being able to correctly\nparse the config file. The second patch adds support for OAuth2.0 authentication\nto git imap-send.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nAditya Garg (4):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++-\n imap-send.c                      | 268 +++++++++++++++++++++++++++++--\n 3 files changed, 300 insertions(+), 19 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"518761","messageId":"PN3PR01MB95978DC78F56DD0994BD65D6B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v4 2/4] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T12:14:28Z","receivedAt":"2025-05-23T12:23:30Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..04b507fc14 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.43.0\n\n"},{"id":"518762","messageId":"PN3PR01MB9597D0F7CD8B8738875D5F48B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v4 1/4] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T12:14:27Z","receivedAt":"2025-05-23T12:23:31Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.43.0\n\n"},{"id":"518763","messageId":"PN3PR01MB959744963D207BB8BAADB4DFB898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v4 3/4] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T12:14:29Z","receivedAt":"2025-05-23T12:23:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 04b507fc14..ad54aceb28 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.43.0\n\n"},{"id":"518764","messageId":"PN3PR01MB95970C9374353E47060C8703B898A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v4 4/4] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-23T12:14:30Z","receivedAt":"2025-05-23T12:23:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex ad54aceb28..87abfd15f3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.43.0\n\n"},{"id":"518845","messageId":"E78F34A7-359E-4049-9780-30FC290DA7E2@gmail.com","threadId":"63502","inReplyTo":"CAPig+cTJmeczzUcGrn98svMfK7aODYS-Ha8FxJHuKU2c2+R-FQ@mail.gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-05-24T16:28:24Z","receivedAt":"2025-05-24T16:28:36Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n> Le 22 mai 2025 à 14:26, Eric Sunshine <sunshine@sunshineco.com> a écrit :\n> \n>    ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>    free(response);\n>    if (ret != strlen(response))\n>        return error(\"IMAP error: sending response failed\");\n>    return 0;\n\nApologies if I missed something , but : strlen _after_ free?"},{"id":"518846","messageId":"PN3PR01MB95975A24B46FCF0829F31767B89BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"E78F34A7-359E-4049-9780-30FC290DA7E2@gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-24T16:30:04Z","receivedAt":"2025-05-24T16:30:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 24-05-2025 09:58 pm, Ben Knoble wrote:\n> \n>> Le 22 mai 2025 à 14:26, Eric Sunshine <sunshine@sunshineco.com> a écrit :\n>>\n>>    ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>>    free(response);\n>>    if (ret != strlen(response))\n>>        return error(\"IMAP error: sending response failed\");\n>>    return 0;\n> \n> Apologies if I missed something , but : strlen _after_ free?\n\nIt was a mistake, and had been acknowledged already :)\n\nThe latest version of this patch series does not have this.\n"},{"id":"518847","messageId":"C0BA42B3-D312-4575-A326-23E56D0EE8B5@gmail.com","threadId":"63502","inReplyTo":"E78F34A7-359E-4049-9780-30FC290DA7E2@gmail.com","subject":"Re: [PATCH 1/2] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-05-24T16:32:34Z","receivedAt":"2025-05-24T16:32:45Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"\n> Le 24 mai 2025 à 12:28, Ben Knoble <ben.knoble@gmail.com> a écrit :\n> \n> ﻿\n>> Le 22 mai 2025 à 14:26, Eric Sunshine <sunshine@sunshineco.com> a écrit :\n>> \n>>   ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n>>   free(response);\n>>   if (ret != strlen(response))\n>>       return error(\"IMAP error: sending response failed\");\n>>   return 0;\n> \n> Apologies if I missed something , but : strlen _after_ free?\n\nAh, others caught this already (and I hadn’t finished reading yet). I really need to find a way to queue up my messages and then go back after finishing a thread and edit before sending ;)"},{"id":"518871","messageId":"20250525185447.29982-1-gargaditya08@live.com","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v5 0/6] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:54:52Z","receivedAt":"2025-05-25T18:54:58Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and set a default between\ncurl and openssl using the config.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nv5: - Add ability to specify destination folder using the command line\n    - Add ability to set a default between curl and openssl using the config\n\nAditya Garg (6):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: enable user to choose between libcurl and openssl using the\n    config\n\n Documentation/config/imap.adoc   |  17 +-\n Documentation/git-imap-send.adoc |  55 +++++-\n imap-send.c                      | 277 +++++++++++++++++++++++++++++--\n 3 files changed, 326 insertions(+), 23 deletions(-)\n\n-- \n2.43.0\n\n\n"},{"id":"518872","messageId":"20250525185447.29982-2-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 1/6] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:54:55Z","receivedAt":"2025-05-25T18:55:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.43.0\n\n\n"},{"id":"518873","messageId":"20250525185447.29982-3-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 2/6] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:54:57Z","receivedAt":"2025-05-25T18:55:03Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..04b507fc14 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.43.0\n\n\n"},{"id":"518874","messageId":"20250525185447.29982-4-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 3/6] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:54:59Z","receivedAt":"2025-05-25T18:55:05Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 04b507fc14..ad54aceb28 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.43.0\n\n\n"},{"id":"518875","messageId":"20250525185447.29982-5-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 4/6] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:55:00Z","receivedAt":"2025-05-25T18:55:08Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex ad54aceb28..87abfd15f3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.43.0\n\n\n"},{"id":"518876","messageId":"20250525185447.29982-6-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 5/6] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:55:02Z","receivedAt":"2025-05-25T18:55:10Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   | 5 +++--\n Documentation/git-imap-send.adoc | 5 +++++\n imap-send.c                      | 7 +++++++\n 3 files changed, 15 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex c3a46070ac..de3613928f 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -37,6 +37,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder <folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder [Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 87abfd15f3..e062758198 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1762,6 +1764,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.43.0\n\n\n"},{"id":"518877","messageId":"20250525185447.29982-7-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"[PATCH v5 6/6] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-25T18:55:04Z","receivedAt":"2025-05-25T18:55:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Currently, imap-send allows the user to choose between libcurl and\nopenssl in case Git is compiled with both libraries only using the\ncommand line, and no option to set a default using the config is\navailable. Add support for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   | 7 +++++++\n Documentation/git-imap-send.adoc | 4 ++--\n imap-send.c                      | 2 ++\n 3 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 829d9e0bac..608c0be7ab 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -25,6 +25,13 @@ imap.port::\n \tDefaults to 143 for imap:// hosts and 993 for imaps:// hosts.\n \tIgnored when imap.tunnel is set.\n \n+imap.usecurl::\n+\tA boolean to choose whether to use libcurl or not to communicate\n+\twith the IMAP server.\n+\tIgnored if Git was built without `USE_CURL_FOR_IMAP_SEND` option\n+\tor with `NO_OPENSSL` option set.\n+\t`--[no]-curl` argument will override this option.\n+\n imap.sslverify::\n \tA boolean to enable/disable verification of the server certificate\n \tused by the SSL/TLS connection. Default is `true`. Ignored when\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex de3613928f..efaa2b774e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -44,12 +44,12 @@ OPTIONS\n \n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n-\tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n+\tinto it.  Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND`\n \toption set.\n \n --no-curl::\n \tTalk to the IMAP server using git's own IMAP routines instead of\n-\tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n+\tusing libcurl.  Ignored if Git was built with the `NO_OPENSSL` option\n \tset.\n \n \ndiff --git a/imap-send.c b/imap-send.c\nindex e062758198..90819eb856 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1559,6 +1559,8 @@ static int git_imap_config(const char *var, const char *val,\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n+\t} else if (!strcmp(\"imap.usecurl\", var)) {\n+\t\tuse_curl = git_config_bool(var, val);\n \t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n-- \n2.43.0\n\n\n"},{"id":"518892","messageId":"CAPig+cTdaGsJpaE2wHv9miPyRk2GusETOykmLT2O-MGiavfY6g@mail.gmail.com","threadId":"63502","inReplyTo":"20250525185447.29982-1-gargaditya08@live.com","subject":"Re: [PATCH v5 0/6] imap-send: make it usable again and add OAuth2.0 support","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-25T20:34:12Z","receivedAt":"2025-05-25T20:34:23Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, May 25, 2025 at 2:55 PM Aditya Garg <gargaditya08@live.com> wrote:\n> v2: - Added support for OAuth2.0 with curl.\n>     - Fixed the memory leak in case auth_cram_md5 fails.\n> v3: - Improve wording in first patch\n>     - Change misleading message if OAuth2.0 is used without OpenSSL\n> v4: - Add PLAIN authentication mechanism for OpenSSL\n>     - Improved wording in the first patch a bit more\n> v5: - Add ability to specify destination folder using the command line\n>     - Add ability to set a default between curl and openssl using the config\n\nThanks for describing the changes between versions. Reviewers\nappreciate the thoughtfulness.\n\nIn addition to describing the changes in prose, you can further assist\nreviewers by including a range-diff (see the --range-diff option of\ngit-format-patch).\n"},{"id":"518912","messageId":"PN3PR01MB9597533601501DF4EF04C99DB865A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cTdaGsJpaE2wHv9miPyRk2GusETOykmLT2O-MGiavfY6g@mail.gmail.com","subject":"Re: [PATCH v5 0/6] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-26T09:06:32Z","receivedAt":"2025-05-26T09:06:39Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 26/05/25 2:04 am, Eric Sunshine wrote:\n> On Sun, May 25, 2025 at 2:55 PM Aditya Garg <gargaditya08@live.com> wrote:\n>> v2: - Added support for OAuth2.0 with curl.\n>>     - Fixed the memory leak in case auth_cram_md5 fails.\n>> v3: - Improve wording in first patch\n>>     - Change misleading message if OAuth2.0 is used without OpenSSL\n>> v4: - Add PLAIN authentication mechanism for OpenSSL\n>>     - Improved wording in the first patch a bit more\n>> v5: - Add ability to specify destination folder using the command line\n>>     - Add ability to set a default between curl and openssl using the config\n> \n> Thanks for describing the changes between versions. Reviewers\n> appreciate the thoughtfulness.\n> \n> In addition to describing the changes in prose, you can further assist\n> reviewers by including a range-diff (see the --range-diff option of\n> git-format-patch).\n\nWill do in future. Thanks.\n\n"},{"id":"519059","messageId":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 0/6] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:08Z","receivedAt":"2025-05-28T07:38:47Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and set a default between\ncurl and openssl using the config.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nv5: - Add ability to specify destination folder using the command line\n    - Add ability to set a default between curl and openssl using the config\nv6: - Fix minor mistakes in --folder documentation\n\nAditya Garg (6):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: enable user to choose between libcurl and openssl using the\n    config\n\n Documentation/config/imap.adoc   |  17 +-\n Documentation/git-imap-send.adoc |  65 +++++--\n imap-send.c                      | 279 +++++++++++++++++++++++++++++--\n 3 files changed, 333 insertions(+), 28 deletions(-)\n\nRange-diff:\n-:  ---------- > 1:  4757d0305d imap-send: fix bug causing cfg->folder being set to NULL\n-:  ---------- > 2:  f5ad01abc5 imap-send: add support for OAuth2.0 authentication\n-:  ---------- > 3:  e3dc19dc49 imap-send: add PLAIN authentication method to OpenSSL\n-:  ---------- > 4:  11f7ac1325 imap-send: fix memory leak in case auth_cram_md5 fails\n1:  62edbcfc6e ! 5:  f6e7a5498e imap-send: enable specifying the folder using the command line\n    @@ Documentation/config/imap.adoc\n      \tCommand used to set up a tunnel to the IMAP server through which\n     \n      ## Documentation/git-imap-send.adoc ##\n    +@@ Documentation/git-imap-send.adoc: git-imap-send - Send a collection of patches from stdin to an IMAP folder\n    + SYNOPSIS\n    + --------\n    + [verse]\n    +-'git imap-send' [-v] [-q] [--[no-]curl]\n    ++'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n    + \n    + \n    + DESCRIPTION\n    + -----------\n    +-This command uploads a mailbox generated with 'git format-patch'\n    ++This command uploads a mailbox generated with `git format-patch`\n    + into an IMAP drafts folder.  This allows patches to be sent as\n    + other email is when using mail clients that cannot read mailbox\n    + files directly. The command also works with any general mailbox\n    +-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n    ++in which emails have the fields 'From', 'Date', and 'Subject' in\n    + that order.\n    + \n    + Typical usage is something like:\n    + \n    +-git format-patch --signoff --stdout --attach origin | git imap-send\n    ++------\n    ++$ git format-patch --signoff --stdout --attach origin | git imap-send\n    ++------\n    + \n    + \n    + OPTIONS\n     @@ Documentation/git-imap-send.adoc: OPTIONS\n      --quiet::\n      \tBe quiet.\n      \n     +-f <folder>::\n    -+--folder <folder>::\n    ++--folder=<folder>::\n     +\tSpecify the folder in which the emails have to saved.\n    -+\tFor example: `--folder [Gmail]/Drafts` or `-f INBOX/Drafts`.\n    ++\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n     +\n      --curl::\n      \tUse libcurl to communicate with the IMAP server, unless tunneling\n    @@ imap-send.c\n      static int use_curl = USE_CURL_DEFAULT;\n     +static char *opt_folder = NULL;\n      \n    - static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n    +-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n    ++static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n      \n      static struct option imap_send_options[] = {\n      \tOPT__VERBOSITY(&verbosity),\n2:  245cc89cca = 6:  4769924781 imap-send: enable user to choose between libcurl and openssl using the config\n3:  4b91d3bf89 < -:  ---------- fix\n-- \n2.43.0\n\n"},{"id":"519060","messageId":"PN3PR01MB9597DE5A2B08B546D3837183B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 1/6] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:09Z","receivedAt":"2025-05-28T07:38:50Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.43.0\n\n"},{"id":"519061","messageId":"PN3PR01MB95978B5D144D282BE48F9CFDB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 4/6] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:12Z","receivedAt":"2025-05-28T07:38:51Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex ad54aceb28..87abfd15f3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.43.0\n\n"},{"id":"519062","messageId":"PN3PR01MB959766A045625C06EAC0B9D1B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 2/6] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:10Z","receivedAt":"2025-05-28T07:38:52Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..04b507fc14 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.43.0\n\n"},{"id":"519063","messageId":"PN3PR01MB959754E8F2C07E29C83B99F5B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 5/6] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:13Z","receivedAt":"2025-05-28T07:38:54Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex c3a46070ac..a35f278baf 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 87abfd15f3..51372e1811 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1762,6 +1764,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.43.0\n\n"},{"id":"519064","messageId":"PN3PR01MB95978FD46BD02C9EB6CD27D2B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 3/6] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:11Z","receivedAt":"2025-05-28T07:38:55Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 04b507fc14..ad54aceb28 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.43.0\n\n"},{"id":"519065","messageId":"PN3PR01MB959731669B2B76C272D63C8BB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BA4D1168C87920EDE6A9B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v6 6/6] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T07:38:14Z","receivedAt":"2025-05-28T07:38:56Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Currently, imap-send allows the user to choose between libcurl and\nopenssl in case Git is compiled with both libraries only using the\ncommand line, and no option to set a default using the config is\navailable. Add support for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   | 7 +++++++\n Documentation/git-imap-send.adoc | 4 ++--\n imap-send.c                      | 2 ++\n 3 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 829d9e0bac..608c0be7ab 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -25,6 +25,13 @@ imap.port::\n \tDefaults to 143 for imap:// hosts and 993 for imaps:// hosts.\n \tIgnored when imap.tunnel is set.\n \n+imap.usecurl::\n+\tA boolean to choose whether to use libcurl or not to communicate\n+\twith the IMAP server.\n+\tIgnored if Git was built without `USE_CURL_FOR_IMAP_SEND` option\n+\tor with `NO_OPENSSL` option set.\n+\t`--[no]-curl` argument will override this option.\n+\n imap.sslverify::\n \tA boolean to enable/disable verification of the server certificate\n \tused by the SSL/TLS connection. Default is `true`. Ignored when\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex a35f278baf..cbbe534ec2 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -46,12 +46,12 @@ OPTIONS\n \n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n-\tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n+\tinto it.  Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND`\n \toption set.\n \n --no-curl::\n \tTalk to the IMAP server using git's own IMAP routines instead of\n-\tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n+\tusing libcurl.  Ignored if Git was built with the `NO_OPENSSL` option\n \tset.\n \n \ndiff --git a/imap-send.c b/imap-send.c\nindex 51372e1811..18aba005cf 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1559,6 +1559,8 @@ static int git_imap_config(const char *var, const char *val,\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n+\t} else if (!strcmp(\"imap.usecurl\", var)) {\n+\t\tuse_curl = git_config_bool(var, val);\n \t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n-- \n2.43.0\n\n"},{"id":"519085","messageId":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 0/9] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:46Z","receivedAt":"2025-05-28T17:18:26Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and set a default between\ncurl and openssl using the config.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nv5: - Add ability to specify destination folder using the command line\n    - Add ability to set a default between curl and openssl using the config\nv6: - Fix minor mistakes in --folder documentation\nv7: - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n    - Display port alongwith host when git credential is invoked and asks for a password\n    - Display the destination mailbox when sending a message\n\nAditya Garg (9):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: enable user to choose between libcurl and openssl using the\n    config\n  imap-send: fix numerous spelling and grammar mistakes in logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n\n Documentation/config/imap.adoc   |  17 +-\n Documentation/git-imap-send.adoc |  65 +++++-\n imap-send.c                      | 327 +++++++++++++++++++++++++++----\n 3 files changed, 358 insertions(+), 51 deletions(-)\n\nRange-diff:\n -:  ---------- >  1:  4757d0305d imap-send: fix bug causing cfg->folder being set to NULL\n 1:  f5ad01abc5 !  2:  c4e2a5659b imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n     +\t\t  const char *access_token UNUSED)\n     +{\n     +\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n    -+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n    ++\t    \"with OpenSSL library, but its support has not been compiled in.\");\n     +}\n     +\n     +static char *xoauth2_base64(const char *user UNUSED,\n     +\t\t  const char *access_token UNUSED)\n     +{\n     +\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n    -+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n    ++\t    \"with OpenSSL library, but its support has not been compiled in.\");\n     +}\n     +\n      #endif\n -:  ---------- >  3:  af9aa85cab imap-send: add PLAIN authentication method to OpenSSL\n 3:  11f7ac1325 =  4:  2ca10774db imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  f6e7a5498e =  5:  190bed0bff imap-send: enable specifying the folder using the command line\n 5:  4769924781 =  6:  469c05321b imap-send: enable user to choose between libcurl and openssl using the config\n 2:  e3dc19dc49 !  7:  6a839e5f4d imap-send: add PLAIN authentication method to OpenSSL\n    @@ Metadata\n     Author: Aditya Garg <gargaditya08@live.com>\n     \n      ## Commit message ##\n    -    imap-send: add PLAIN authentication method to OpenSSL\n    +    imap-send: fix numerous spelling and grammar mistakes in logs\n     \n    -    The current implementation for PLAIN in imap-send works just fine\n    -    if using curl, but if attempted to use for OpenSSL, it is treated\n    -    as an invalid mechanism. The default implementation for OpenSSL is\n    -    IMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\n    -    still used today by many email providers in form of app passwords,\n    -    lets add an implementation that can use AUTH PLAIN if specified.\n    +    A lot of spelling and grammar mistakes were found in the logs shown to\n    +    the user while using imap-send. Most of them are lack of a full stop at\n    +    the end of a sentence and first word of a sentence not being capitalized.\n     \n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n    - ## Documentation/config/imap.adoc ##\n    -@@ Documentation/config/imap.adoc: imap.authMethod::\n    - \tSpecify the authentication method for authenticating with the IMAP server.\n    - \tIf Git was built with the NO_CURL option, or if your curl version is older\n    - \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n    --\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n    --\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    -+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n    -+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    - \tplaintext LOGIN command.\n    -\n      ## imap-send.c ##\n    -@@ imap-send.c: enum CAPABILITY {\n    - \tLITERALPLUS,\n    - \tNAMESPACE,\n    - \tSTARTTLS,\n    -+\tAUTH_PLAIN,\n    - \tAUTH_CRAM_MD5,\n    - \tAUTH_OAUTHBEARER,\n    - \tAUTH_XOAUTH2\n    -@@ imap-send.c: static const char *cap_list[] = {\n    - \t\"LITERAL+\",\n    - \t\"NAMESPACE\",\n    - \t\"STARTTLS\",\n    -+\t\"AUTH=PLAIN\",\n    - \t\"AUTH=CRAM-MD5\",\n    - \t\"AUTH=OAUTHBEARER\",\n    - \t\"AUTH=XOAUTH2\",\n    -@@ imap-send.c: static char hexchar(unsigned int b)\n    +@@ imap-send.c: static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n    + \t\t\t      const struct imap_server_conf *cfg UNUSED,\n    + \t\t\t      int use_tls_only UNUSED)\n    + {\n    +-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n    ++\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n    + \treturn -1;\n      }\n      \n    - #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n    -+static char *plain_base64(const char *user, const char *pass)\n    -+{\n    -+\tint user_len = strlen(user);\n    -+\tint pass_len = strlen(pass);\n    -+\tint raw_len = 1 + user_len + 1 + pass_len;\n    -+\tint b64_len;\n    -+\tchar *raw, *b64;\n    -+\n    -+\t/* Compose the PLAIN string\n    -+\t *\n    -+\t * The username and password are combined to one string and base64 encoded.\n    -+\t * \"\\0user\\0pass\"\n    -+\t *\n    -+\t * The method has been described in RFC4616.\n    -+\t *\n    -+\t * https://datatracker.ietf.org/doc/html/rfc4616\n    -+\t */\n    -+\traw = xmallocz(raw_len);\n    -+\traw[0] = '\\0';\n    -+\tmemcpy(raw + 1, user, user_len);\n    -+\traw[1 + user_len] = '\\0';\n    -+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n    -+\n    -+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n    -+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n    -+\tfree(raw);\n    -+\n    -+\tif (b64_len < 0) {\n    -+\t\tfree(b64);\n    -+\t\treturn NULL;\n    -+\t}\n    -+\treturn b64;\n    -+}\n    -+\n    - static char *cram(const char *challenge_64, const char *user, const char *pass)\n    - {\n    - \tint i, resp_len, encoded_len, decoded_len;\n    -@@ imap-send.c: static char *xoauth2_base64(const char *user, const char *access_token)\n    - \n    - #else\n    - \n    -+static char *plain_base64(const char *user UNUSED,\n    -+\t\t  const char *access_token UNUSED)\n    -+{\n    -+\tdie(\"You are trying to use PLAIN authenticate method \"\n    -+\t    \"with OpenSSL library, but it's support has not been compiled in.\");\n    -+}\n    -+\n    - static char *cram(const char *challenge_64 UNUSED,\n    - \t\t  const char *user UNUSED,\n    - \t\t  const char *pass UNUSED)\n    -@@ imap-send.c: static char *xoauth2_base64(const char *user UNUSED,\n    +@@ imap-send.c: static int verify_hostname(X509 *cert, const char *hostname)\n    + \n    + \t/* try the common name */\n    + \tif (!(subj = X509_get_subject_name(cert)))\n    +-\t\treturn error(\"cannot get certificate subject\");\n    ++\t\treturn error(\"Cannot get certificate subject\");\n    + \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n    +-\t\treturn error(\"cannot get certificate common name\");\n    ++\t\treturn error(\"Cannot get certificate common name\");\n    + \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n    + \t\treturn 0;\n    + \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    + \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n    + \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n    + \tif (decoded_len < 0)\n    +-\t\tdie(\"invalid challenge %s\", challenge_64);\n    ++\t\tdie(\"Invalid challenge %s\", challenge_64);\n    + \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n    + \t\tdie(\"HMAC error\");\n    + \n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n    + \tif (ret != strlen(response)) {\n    + \t\tfree(response);\n    +-\t\treturn error(\"IMAP error: sending response failed\");\n    ++\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n    + \t}\n    + \n    + \tfree(response);\n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\ttunnel.in = -1;\n    + \t\ttunnel.out = -1;\n    + \t\tif (start_command(&tunnel))\n    +-\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n    ++\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n      \n    + \t\timap->buf.sock.fd[0] = tunnel.out;\n    + \t\timap->buf.sock.fd[1] = tunnel.in;\n    + \n    +-\t\timap_info(\"ok\\n\");\n    ++\t\timap_info(\"OK\\n\");\n    + \t} else {\n    + #ifndef NO_IPV6\n    + \t\tstruct addrinfo hints, *ai0, *ai;\n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n    + \t\t\tgoto bail;\n    + \t\t}\n    +-\t\timap_info(\"ok\\n\");\n    ++\t\timap_info(\"OK\\n\");\n    + \n    + \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n    + \t\t\tchar addr[NI_MAXHOST];\n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\tperror(\"gethostbyname\");\n    + \t\t\tgoto bail;\n    + \t\t}\n    +-\t\timap_info(\"ok\\n\");\n    ++\t\timap_info(\"OK\\n\");\n    + \n    + \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n    + \n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t}\n      #endif\n    + \t\tif (s < 0) {\n    +-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n    ++\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n    + \t\t\tgoto bail;\n    + \t\t}\n      \n    -+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n    -+{\n    -+\tint ret;\n    -+\tchar *b64;\n    -+\n    -+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n    -+\tif (!b64)\n    -+\t\treturn error(\"PLAIN: base64 encoding failed\");\n    -+\n    -+\t/* Send the base64-encoded response */\n    -+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    -+\tif (ret != (int)strlen(b64)) {\n    -+\t\tfree(b64);\n    -+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n    -+\t}\n    -+\n    -+\tfree(b64);\n    -+\treturn 0;\n    -+}\n    -+\n    - static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    - {\n    - \tint ret;\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\tif (srvc->auth_method) {\n    - \t\t\tstruct imap_cmd_cb cb;\n    - \n    --\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    -+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n    -+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n    -+\t\t\t\t\tfprintf(stderr, \"You specified \"\n    -+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n    -+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    -+\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n    -+\t\t\t\t/* PLAIN */\n    -+\n    -+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    -+\t\t\t\tcb.cont = auth_plain;\n    -+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n    -+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n    -+\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n    -+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    - \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n    - \t\t\t\t\tfprintf(stderr, \"You specified \"\n    - \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n    + \t\t\tclose(s);\n    + \t\t\tgoto bail;\n    + \t\t}\n    +-\t\timap_info(\"ok\\n\");\n    ++\t\timap_info(\"OK\\n\");\n    + \t}\n    + \n    + \t/* read the greeting string */\n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\t}\n    + \t\t} else {\n    + \t\t\tif (CAP(NOLOGIN)) {\n    +-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n    ++\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n    + \t\t\t\t\tsrvc->user, srvc->host);\n    + \t\t\t\tgoto bail;\n    + \t\t\t}\n    + \t\t\tif (!imap->buf.sock.ssl)\n    + \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n    +-\t\t\t\t\t  \"sent in the clear\\n\");\n    ++\t\t\t\t\t  \"sent in the clear.\\n\");\n    + \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n    + \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n    + \t\t\t\tgoto bail;\n    +@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n    + \n    + \tctx = imap_open_store(server, server->folder);\n    + \tif (!ctx) {\n    +-\t\tfprintf(stderr, \"failed to open store\\n\");\n    ++\t\tfprintf(stderr, \"Failed to open store.\\n\");\n    + \t\treturn 1;\n    + \t}\n    + \tctx->name = server->folder;\n    + \n    +-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    ++\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    + \twhile (1) {\n    + \t\tunsigned percent = n * 100 / total;\n    + \n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \n    + \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n    + \tif (!uri_encoded_folder)\n    +-\t\tdie(\"failed to encode server folder\");\n    ++\t\tdie(\"Failed to encode server folder.\");\n    + \tstrbuf_addstr(&path, uri_encoded_folder);\n    + \tcurl_free(uri_encoded_folder);\n    + \n    +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    + \tcurl = setup_curl(server, &cred);\n    + \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n    + \n    +-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    ++\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    + \twhile (1) {\n    + \t\tunsigned percent = n * 100 / total;\n    + \t\tint prev_len;\n    +@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + \t\tserver.port = server.use_ssl ? 993 : 143;\n    + \n    + \tif (!server.folder) {\n    +-\t\tfprintf(stderr, \"no imap store specified\\n\");\n    ++\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    + \t\tret = 1;\n    + \t\tgoto out;\n    + \t}\n    + \tif (!server.host) {\n    + \t\tif (!server.tunnel) {\n    +-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n    ++\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n    + \t\t\tret = 1;\n    + \t\t\tgoto out;\n    + \t\t}\n    +@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + \n    + \t/* read the messages */\n    + \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n    +-\t\terror_errno(_(\"could not read from stdin\"));\n    ++\t\terror_errno(_(\"Could not read from stdin.\"));\n    + \t\tret = 1;\n    + \t\tgoto out;\n    + \t}\n    + \n    + \tif (all_msgs.len == 0) {\n    +-\t\tfprintf(stderr, \"nothing to send\\n\");\n    ++\t\tfprintf(stderr, \"Nothing to send.\\n\");\n    + \t\tret = 1;\n    + \t\tgoto out;\n    + \t}\n    + \n    + \ttotal = count_messages(&all_msgs);\n    + \tif (!total) {\n    +-\t\tfprintf(stderr, \"no messages to send\\n\");\n    ++\t\tfprintf(stderr, \"No messages found to send.\\n\");\n    + \t\tret = 1;\n    + \t\tgoto out;\n    + \t}\n -:  ---------- >  8:  a60d8f458f imap-send: display port alongwith host when git credential is invoked\n -:  ---------- >  9:  5db5b64a3b imap-send: display the destination mailbox when sending a message\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519086","messageId":"PN3PR01MB95979D54753AD690BCB4F092B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 1/9] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:47Z","receivedAt":"2025-05-28T17:18:29Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519087","messageId":"PN3PR01MB95973F67A61F8CBCC17FE0A1B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:49Z","receivedAt":"2025-05-28T17:18:31Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 0c7844aff2..c07ff98c3a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519088","messageId":"PN3PR01MB959757C4C2C376A0E44F1AE6B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 4/9] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:50Z","receivedAt":"2025-05-28T17:18:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex c07ff98c3a..d0c7bac030 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519089","messageId":"PN3PR01MB9597FBA69F0E45E02BB224BBB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:48Z","receivedAt":"2025-05-28T17:18:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..0c7844aff2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519090","messageId":"PN3PR01MB9597D4A0A928A95548774B13B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:51Z","receivedAt":"2025-05-28T17:18:35Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex c3a46070ac..a35f278baf 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex d0c7bac030..337f1049ca 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1762,6 +1764,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519091","messageId":"PN3PR01MB9597B56233DA6815FC7CA96EB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 6/9] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:52Z","receivedAt":"2025-05-28T17:18:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Currently, imap-send allows the user to choose between libcurl and\nopenssl in case Git is compiled with both libraries only using the\ncommand line, and no option to set a default using the config is\navailable. Add support for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   | 7 +++++++\n Documentation/git-imap-send.adoc | 4 ++--\n imap-send.c                      | 2 ++\n 3 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 829d9e0bac..608c0be7ab 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -25,6 +25,13 @@ imap.port::\n \tDefaults to 143 for imap:// hosts and 993 for imaps:// hosts.\n \tIgnored when imap.tunnel is set.\n \n+imap.usecurl::\n+\tA boolean to choose whether to use libcurl or not to communicate\n+\twith the IMAP server.\n+\tIgnored if Git was built without `USE_CURL_FOR_IMAP_SEND` option\n+\tor with `NO_OPENSSL` option set.\n+\t`--[no]-curl` argument will override this option.\n+\n imap.sslverify::\n \tA boolean to enable/disable verification of the server certificate\n \tused by the SSL/TLS connection. Default is `true`. Ignored when\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex a35f278baf..cbbe534ec2 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -46,12 +46,12 @@ OPTIONS\n \n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n-\tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n+\tinto it.  Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND`\n \toption set.\n \n --no-curl::\n \tTalk to the IMAP server using git's own IMAP routines instead of\n-\tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n+\tusing libcurl.  Ignored if Git was built with the `NO_OPENSSL` option\n \tset.\n \n \ndiff --git a/imap-send.c b/imap-send.c\nindex 337f1049ca..b08ec0e1d5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1559,6 +1559,8 @@ static int git_imap_config(const char *var, const char *val,\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n+\t} else if (!strcmp(\"imap.usecurl\", var)) {\n+\t\tuse_curl = git_config_bool(var, val);\n \t} else if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\treturn config_error_nonbool(var);\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519092","messageId":"PN3PR01MB9597328E6F0DB62CA1280992B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 8/9] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:54Z","receivedAt":"2025-05-28T17:18:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 204d2a14b2..3172cd5191 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1104,7 +1104,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519093","messageId":"PN3PR01MB9597F25DF4C2F5FEF2CAC0F1B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 7/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:53Z","receivedAt":"2025-05-28T17:18:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"A lot of spelling and grammar mistakes were found in the logs shown to\nthe user while using imap-send. Most of them are lack of a full stop at\nthe end of a sentence and first word of a sentence not being capitalized.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 44 ++++++++++++++++++++++----------------------\n 1 file changed, 22 insertions(+), 22 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex b08ec0e1d5..204d2a14b2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \n \t/* try the common name */\n \tif (!(subj = X509_get_subject_name(cert)))\n-\t\treturn error(\"cannot get certificate subject\");\n+\t\treturn error(\"Cannot get certificate subject\");\n \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n-\t\treturn error(\"cannot get certificate common name\");\n+\t\treturn error(\"Cannot get certificate common name\");\n \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n \t\treturn 0;\n \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n@@ -905,7 +905,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n \tif (decoded_len < 0)\n-\t\tdie(\"invalid challenge %s\", challenge_64);\n+\t\tdie(\"Invalid challenge %s\", challenge_64);\n \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n \t\tdie(\"HMAC error\");\n \n@@ -1050,7 +1050,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1144,12 +1144,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\ttunnel.in = -1;\n \t\ttunnel.out = -1;\n \t\tif (start_command(&tunnel))\n-\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n+\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1168,7 +1168,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1206,7 +1206,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1220,7 +1220,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1232,7 +1232,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1340,13 +1340,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t\tif (!imap->buf.sock.ssl)\n \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n-\t\t\t\t\t  \"sent in the clear\\n\");\n+\t\t\t\t\t  \"sent in the clear.\\n\");\n \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n \t\t\t\tgoto bail;\n@@ -1593,12 +1593,12 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \n \tctx = imap_open_store(server, server->folder);\n \tif (!ctx) {\n-\t\tfprintf(stderr, \"failed to open store\\n\");\n+\t\tfprintf(stderr, \"Failed to open store.\\n\");\n \t\treturn 1;\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1650,7 +1650,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n \tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n+\t\tdie(\"Failed to encode server folder.\");\n \tstrbuf_addstr(&path, uri_encoded_folder);\n \tcurl_free(uri_encoded_folder);\n \n@@ -1706,7 +1706,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1790,13 +1790,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1805,20 +1805,20 @@ int cmd_main(int argc, const char **argv)\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n-\t\terror_errno(_(\"could not read from stdin\"));\n+\t\terror_errno(_(\"Could not read from stdin.\"));\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n-\t\tfprintf(stderr, \"nothing to send\\n\");\n+\t\tfprintf(stderr, \"Nothing to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"No messages found to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519094","messageId":"PN3PR01MB9597747297F1569DE7106B8BB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979EAD9EEEB3385693EBE7B867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v7 9/9] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-28T17:17:55Z","receivedAt":"2025-05-28T17:18:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3172cd5191..fd589f8aa1 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1598,7 +1598,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1706,7 +1707,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.638.g5db5b64a3b.dirty\n\n"},{"id":"519139","messageId":"42e07f4d-9888-4a1e-826a-b53b7d84fef6@gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB9597B56233DA6815FC7CA96EB867A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v7 6/9] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-05-29T13:58:17Z","receivedAt":"2025-05-29T13:58:20Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Aditya\n\nOn 28/05/2025 18:17, Aditya Garg wrote:\n> Currently, imap-send allows the user to choose between libcurl and\n> openssl in case Git is compiled with both libraries only using the\n> command line, and no option to set a default using the config is\n> available. Add support for the same.\n\nI'm wondering why anyone would want to switch the backend at run-time? \nThere has been talk in the past about removing the openssl code [1] and \njust relying on the curl backend. I think that is a worthwhile goal as \nit simplifies the code and means we would avoid having to worry about \nwhether we're using openssl correctly [2]. That would be harder to do if \nwe add this config setting. If we don't already do so, perhaps we could \nstart using libcurl even when openssl is also available though that does \nnot need to be part of this patch series.\n\nBest Wishes\n\nPhillip\n\n[1] https://lore.kernel.org/git/Y+LNitGAude1vogv@coredump.intra.peff.net/\n[2] \nhttps://lore.kernel.org/git/pull.1886.git.1742819282360.gitgitgadget@gmail.com/\n\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>   Documentation/config/imap.adoc   | 7 +++++++\n>   Documentation/git-imap-send.adoc | 4 ++--\n>   imap-send.c                      | 2 ++\n>   3 files changed, 11 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\n> index 829d9e0bac..608c0be7ab 100644\n> --- a/Documentation/config/imap.adoc\n> +++ b/Documentation/config/imap.adoc\n> @@ -25,6 +25,13 @@ imap.port::\n>   \tDefaults to 143 for imap:// hosts and 993 for imaps:// hosts.\n>   \tIgnored when imap.tunnel is set.\n>   \n> +imap.usecurl::\n> +\tA boolean to choose whether to use libcurl or not to communicate\n> +\twith the IMAP server.\n> +\tIgnored if Git was built without `USE_CURL_FOR_IMAP_SEND` option\n> +\tor with `NO_OPENSSL` option set.\n> +\t`--[no]-curl` argument will override this option.\n> +\n>   imap.sslverify::\n>   \tA boolean to enable/disable verification of the server certificate\n>   \tused by the SSL/TLS connection. Default is `true`. Ignored when\n> diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\n> index a35f278baf..cbbe534ec2 100644\n> --- a/Documentation/git-imap-send.adoc\n> +++ b/Documentation/git-imap-send.adoc\n> @@ -46,12 +46,12 @@ OPTIONS\n>   \n>   --curl::\n>   \tUse libcurl to communicate with the IMAP server, unless tunneling\n> -\tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n> +\tinto it.  Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND`\n>   \toption set.\n>   \n>   --no-curl::\n>   \tTalk to the IMAP server using git's own IMAP routines instead of\n> -\tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n> +\tusing libcurl.  Ignored if Git was built with the `NO_OPENSSL` option\n>   \tset.\n>   \n>   \n> diff --git a/imap-send.c b/imap-send.c\n> index 337f1049ca..b08ec0e1d5 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1559,6 +1559,8 @@ static int git_imap_config(const char *var, const char *val,\n>   \t\treturn git_config_string(&cfg->auth_method, var, val);\n>   \t} else if (!strcmp(\"imap.port\", var)) {\n>   \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n> +\t} else if (!strcmp(\"imap.usecurl\", var)) {\n> +\t\tuse_curl = git_config_bool(var, val);\n>   \t} else if (!strcmp(\"imap.host\", var)) {\n>   \t\tif (!val) {\n>   \t\t\treturn config_error_nonbool(var);\n\n"},{"id":"519140","messageId":"PN3PR01MB959721C6731825B259E5C8D0B866A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"42e07f4d-9888-4a1e-826a-b53b7d84fef6@gmail.com","subject":"Re: [PATCH v7 6/9] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T14:09:51Z","receivedAt":"2025-05-29T14:09:59Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 29/05/25 7:28 pm, Phillip Wood wrote:\n> Hi Aditya\n> \n> On 28/05/2025 18:17, Aditya Garg wrote:\n>> Currently, imap-send allows the user to choose between libcurl and\n>> openssl in case Git is compiled with both libraries only using the\n>> command line, and no option to set a default using the config is\n>> available. Add support for the same.\n> \n> I'm wondering why anyone would want to switch the backend at run-time? There has been talk in the past about removing the openssl code [1] and just relying on the curl backend. I think that is a worthwhile goal as it simplifies the code and means we would avoid having to worry about whether we're using openssl correctly [2]. That would be harder to do if we add this config setting. If we don't already do so, perhaps we could start using libcurl even when openssl is also available though that does not need to be part of this patch series.\n\nOpenSSL is still needed for CRAM-MD5, something which curl does not support at all.\nI agree CRAM-MD5 is not popular today, but I have seen servers that still use it\neven today (For example rediffmail).\n\nAlso, implementing more features is simply more easy and feasible with OpenSSL,\ncurl has limitations.\n\nLastly, in my tests, OpenSSL did perform much better in terms of sending mails\nthan curl, and thus having that implementation does not harm if someone is ready\nto compile git themselves.\n"},{"id":"519153","messageId":"20250529162020.45187-1-gargaditya08@live.com","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v8 0/9] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:00Z","receivedAt":"2025-05-29T16:21:05Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nv5: - Add ability to specify destination folder using the command line\n    - Add ability to set a default between curl and openssl using the config\nv6: - Fix minor mistakes in --folder documentation\nv7: - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n    - Display port alongwith host when git credential is invoked and asks for a password\n    - Display the destination mailbox when sending a message\nv8: - Drop the patch that enabled user to choose between libcurl and openssl using the config\n    - Add ability to list the available folders by adding a `--list` option\n\nAditya Garg (9):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix numerous spelling and grammar mistakes in logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  10 +-\n Documentation/git-imap-send.adoc |  67 ++++-\n imap-send.c                      | 417 +++++++++++++++++++++++++++----\n 3 files changed, 431 insertions(+), 63 deletions(-)\n\nRange-diff against v7:\n 1:  4757d0305d =  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 2:  c4e2a5659b =  2:  f0743d46e1 imap-send: add support for OAuth2.0 authentication\n 3:  af9aa85cab =  3:  b1602644b7 imap-send: add PLAIN authentication method to OpenSSL\n 4:  2ca10774db =  4:  49790e60cc imap-send: fix memory leak in case auth_cram_md5 fails\n 5:  190bed0bff =  5:  2efe897379 imap-send: enable specifying the folder using the command line\n 7:  6a839e5f4d =  6:  8f6676a046 imap-send: fix numerous spelling and grammar mistakes in logs\n 8:  a60d8f458f =  7:  69fdae55cd imap-send: display port alongwith host when git credential is invoked\n 9:  5db5b64a3b =  8:  187dbccd03 imap-send: display the destination mailbox when sending a message\n 6:  469c05321b !  9:  03d7d6a772 imap-send: enable user to choose between libcurl and openssl using the config\n    @@ Metadata\n     Author: Aditya Garg <gargaditya08@live.com>\n     \n      ## Commit message ##\n    -    imap-send: enable user to choose between libcurl and openssl using the config\n    +    imap-send: add ability to list the available folders\n     \n    -    Currently, imap-send allows the user to choose between libcurl and\n    -    openssl in case Git is compiled with both libraries only using the\n    -    command line, and no option to set a default using the config is\n    -    available. Add support for the same.\n    +    Various IMAP servers have different ways to name common folders.\n    +    For example, the folder where all deleted messages are stored is often\n    +    named \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\n    +    Similarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\n    +    on Gmail it is named \"[Gmail]/Drafts\".\n     \n    -    Signed-off-by: Aditya Garg <gargaditya08@live.com>\n    +    This commit adds a `--list` command to the `imap-send` tool that lists\n    +    the available folders on the IMAP server, allowing users to see\n    +    which folders are available and how they are named. A sample output\n    +    looks like this when run against a Gmail server:\n     \n    - ## Documentation/config/imap.adoc ##\n    -@@ Documentation/config/imap.adoc: imap.port::\n    - \tDefaults to 143 for imap:// hosts and 993 for imaps:// hosts.\n    - \tIgnored when imap.tunnel is set.\n    - \n    -+imap.usecurl::\n    -+\tA boolean to choose whether to use libcurl or not to communicate\n    -+\twith the IMAP server.\n    -+\tIgnored if Git was built without `USE_CURL_FOR_IMAP_SEND` option\n    -+\tor with `NO_OPENSSL` option set.\n    -+\t`--[no]-curl` argument will override this option.\n    -+\n    - imap.sslverify::\n    - \tA boolean to enable/disable verification of the server certificate\n    - \tused by the SSL/TLS connection. Default is `true`. Ignored when\n    +        Fetching the list of available folders...\n    +        * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    +        * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    +        * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    +        * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    +        * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    +        * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    +        * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    +        * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    +        * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n    +\n    +    For OpenSSL, this is achived by running the 'IMAP LIST' command and\n    +    parsing the response. This command is specified in RFC6154:\n    +    https://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n    +\n    +    For libcurl, the example code published in the libcurl documentation\n    +    is used to implement this functionality:\n    +    https://curl.se/libcurl/c/imap-list.html\n    +\n    +    Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n      ## Documentation/git-imap-send.adoc ##\n    -@@ Documentation/git-imap-send.adoc: OPTIONS\n    +@@ Documentation/git-imap-send.adoc: SYNOPSIS\n    + --------\n    + [verse]\n    + 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n    ++'git imap-send' --list\n      \n    - --curl::\n    - \tUse libcurl to communicate with the IMAP server, unless tunneling\n    --\tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n    -+\tinto it.  Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND`\n    - \toption set.\n      \n    - --no-curl::\n    - \tTalk to the IMAP server using git's own IMAP routines instead of\n    --\tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n    -+\tusing libcurl.  Ignored if Git was built with the `NO_OPENSSL` option\n    + DESCRIPTION\n    +@@ Documentation/git-imap-send.adoc: OPTIONS\n    + \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n      \tset.\n      \n    ++--list::\n    ++\tRun the IMAP LIST command to output a list of all the folders present.\n    + \n    + CONFIGURATION\n    + -------------\n    +@@ Documentation/git-imap-send.adoc: authentication as described below.\n      \n    + [NOTE]\n    + You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    +-that the \"Folder doesn't exist\".\n    ++that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n    ++list of available folders.\n    + \n    + [NOTE]\n    + If your Gmail account is set to another language than English, the name of the \"Drafts\"\n     \n      ## imap-send.c ##\n    -@@ imap-send.c: static int git_imap_config(const char *var, const char *val,\n    - \t\treturn git_config_string(&cfg->auth_method, var, val);\n    - \t} else if (!strcmp(\"imap.port\", var)) {\n    - \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n    -+\t} else if (!strcmp(\"imap.usecurl\", var)) {\n    -+\t\tuse_curl = git_config_bool(var, val);\n    - \t} else if (!strcmp(\"imap.host\", var)) {\n    - \t\tif (!val) {\n    - \t\t\treturn config_error_nonbool(var);\n    +@@\n    + #endif\n    + \n    + static int verbosity;\n    ++static int list_folders = 0;\n    + static int use_curl = USE_CURL_DEFAULT;\n    + static char *opt_folder = NULL;\n    + \n    +-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n    ++static char const * const imap_send_usage[] = {\n    ++\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n    ++\t\"git imap-send --list\",\n    ++\tNULL\n    ++};\n    + \n    + static struct option imap_send_options[] = {\n    + \tOPT__VERBOSITY(&verbosity),\n    + \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n    + \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n    ++\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n    + \tOPT_END()\n    + };\n    + \n    +@@ imap-send.c: static int buffer_gets(struct imap_buffer *b, char **s)\n    + \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n    + \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n    + \t\t\t\tb->offset += 2; /* next line */\n    +-\t\t\t\tif (0 < verbosity)\n    ++\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n    + \t\t\t\t\tputs(*s);\n    + \t\t\t\treturn 0;\n    + \t\t\t}\n    +@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n    + \treturn 0;\n    + }\n    + \n    ++static int list_imap_folders(struct imap_server_conf *server)\n    ++{\n    ++\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n    ++\tif (!ctx) {\n    ++\t\tfprintf(stderr, \"Failed to connect to IMAP server.\\n\");\n    ++\t\treturn 1;\n    ++\t}\n    ++\n    ++\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n    ++\t/* Issue the LIST command and print the results */\n    ++\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n    ++\t\tfprintf(stderr, \"Failed to list folders.\\n\");\n    ++\t\timap_close_store(ctx);\n    ++\t\treturn 1;\n    ++\t}\n    ++\n    ++\timap_close_store(ctx);\n    ++\treturn 0;\n    ++}\n    ++\n    + #ifdef USE_CURL_FOR_IMAP_SEND\n    + static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + {\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \tif (!path.len || path.buf[path.len - 1] != '/')\n    + \t\tstrbuf_addch(&path, '/');\n    + \n    +-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n    +-\tif (!uri_encoded_folder)\n    +-\t\tdie(\"Failed to encode server folder.\");\n    +-\tstrbuf_addstr(&path, uri_encoded_folder);\n    +-\tcurl_free(uri_encoded_folder);\n    ++\tif (!list_folders) {\n    ++\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n    ++\t\tif (!uri_encoded_folder)\n    ++\t\t\tdie(\"Failed to encode server folder.\");\n    ++\t\tstrbuf_addstr(&path, uri_encoded_folder);\n    ++\t\tcurl_free(uri_encoded_folder);\n    ++\t}\n    + \n    + \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n    + \tstrbuf_release(&path);\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n    + \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n    + \n    +-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n    +-\n    +-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n    +-\n    + \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n    + \t\thttp_trace_curl_no_data();\n    + \tsetup_curl_trace(curl);\n    +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    + \tstruct credential cred = CREDENTIAL_INIT;\n    + \n    + \tcurl = setup_curl(server, &cred);\n    ++\n    ++\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n    ++\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n    ++\n    + \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n    + \n    + \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n    +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    + \n    + \treturn res != CURLE_OK;\n    + }\n    ++\n    ++static int curl_list_imap_folders(struct imap_server_conf *server)\n    ++{\n    ++\tCURL *curl;\n    ++\tCURLcode res = CURLE_OK;\n    ++\tstruct credential cred = CREDENTIAL_INIT;\n    ++\n    ++\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n    ++\tcurl = setup_curl(server, &cred);\n    ++\tres = curl_easy_perform(curl);\n    ++\n    ++\tcurl_easy_cleanup(curl);\n    ++\tcurl_global_cleanup();\n    ++\n    ++\tif (cred.username) {\n    ++\t\tif (res == CURLE_OK)\n    ++\t\t\tcredential_approve(the_repository, &cred);\n    ++\t\telse if (res == CURLE_LOGIN_DENIED)\n    ++\t\t\tcredential_reject(the_repository, &cred);\n    ++\t}\n    ++\n    ++\tcredential_clear(&cred);\n    ++\n    ++\treturn res != CURLE_OK;\n    ++}\n    + #endif\n    + \n    + int cmd_main(int argc, const char **argv)\n    +@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + \tif (!server.port)\n    + \t\tserver.port = server.use_ssl ? 993 : 143;\n    + \n    +-\tif (!server.folder) {\n    +-\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    +-\t\tret = 1;\n    +-\t\tgoto out;\n    +-\t}\n    + \tif (!server.host) {\n    + \t\tif (!server.tunnel) {\n    + \t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n    +@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + \t\tserver.host = xstrdup(\"tunnel\");\n    + \t}\n    + \n    ++\tif (list_folders) {\n    ++\t\tif (server.tunnel)\n    ++\t\t\tret = list_imap_folders(&server);\n    ++#ifdef USE_CURL_FOR_IMAP_SEND\n    ++\t\telse if (use_curl)\n    ++\t\t\tret = curl_list_imap_folders(&server);\n    ++#endif\n    ++\t\telse\n    ++\t\t\tret = list_imap_folders(&server);\n    ++\t\tgoto out;\n    ++\t}\n    ++\n    ++\tif (!server.folder) {\n    ++\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    ++\t\tret = 1;\n    ++\t\tgoto out;\n    ++\t}\n    ++\n    + \t/* read the messages */\n    + \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n    + \t\terror_errno(_(\"Could not read from stdin.\"));\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519154","messageId":"20250529162020.45187-2-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 1/9] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:02Z","receivedAt":"2025-05-29T16:21:08Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519155","messageId":"20250529162020.45187-3-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:04Z","receivedAt":"2025-05-29T16:21:10Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  46 +++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 214 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..c3a46070ac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,19 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your account password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n+authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +123,33 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n+OAuth2.0 access token in place of your password.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +158,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..0c7844aff2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519156","messageId":"20250529162020.45187-4-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:06Z","receivedAt":"2025-05-29T16:21:13Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 0c7844aff2..c07ff98c3a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519157","messageId":"20250529162020.45187-5-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 4/9] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:07Z","receivedAt":"2025-05-29T16:21:15Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex c07ff98c3a..d0c7bac030 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519158","messageId":"20250529162020.45187-6-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:09Z","receivedAt":"2025-05-29T16:21:17Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex c3a46070ac..a35f278baf 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex d0c7bac030..337f1049ca 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1762,6 +1764,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519159","messageId":"20250529162020.45187-7-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:11Z","receivedAt":"2025-05-29T16:21:20Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"A lot of spelling and grammar mistakes were found in the logs shown to\nthe user while using imap-send. Most of them are lack of a full stop at\nthe end of a sentence and first word of a sentence not being capitalized.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 44 ++++++++++++++++++++++----------------------\n 1 file changed, 22 insertions(+), 22 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 337f1049ca..d99eed0659 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \n \t/* try the common name */\n \tif (!(subj = X509_get_subject_name(cert)))\n-\t\treturn error(\"cannot get certificate subject\");\n+\t\treturn error(\"Cannot get certificate subject\");\n \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n-\t\treturn error(\"cannot get certificate common name\");\n+\t\treturn error(\"Cannot get certificate common name\");\n \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n \t\treturn 0;\n \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n@@ -905,7 +905,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n \tif (decoded_len < 0)\n-\t\tdie(\"invalid challenge %s\", challenge_64);\n+\t\tdie(\"Invalid challenge %s\", challenge_64);\n \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n \t\tdie(\"HMAC error\");\n \n@@ -1050,7 +1050,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1144,12 +1144,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\ttunnel.in = -1;\n \t\ttunnel.out = -1;\n \t\tif (start_command(&tunnel))\n-\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n+\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1168,7 +1168,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1206,7 +1206,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1220,7 +1220,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1232,7 +1232,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1340,13 +1340,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t\tif (!imap->buf.sock.ssl)\n \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n-\t\t\t\t\t  \"sent in the clear\\n\");\n+\t\t\t\t\t  \"sent in the clear.\\n\");\n \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n \t\t\t\tgoto bail;\n@@ -1591,12 +1591,12 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \n \tctx = imap_open_store(server, server->folder);\n \tif (!ctx) {\n-\t\tfprintf(stderr, \"failed to open store\\n\");\n+\t\tfprintf(stderr, \"Failed to open store.\\n\");\n \t\treturn 1;\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1648,7 +1648,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n \tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n+\t\tdie(\"Failed to encode server folder.\");\n \tstrbuf_addstr(&path, uri_encoded_folder);\n \tcurl_free(uri_encoded_folder);\n \n@@ -1704,7 +1704,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1788,13 +1788,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1803,20 +1803,20 @@ int cmd_main(int argc, const char **argv)\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n-\t\terror_errno(_(\"could not read from stdin\"));\n+\t\terror_errno(_(\"Could not read from stdin.\"));\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n-\t\tfprintf(stderr, \"nothing to send\\n\");\n+\t\tfprintf(stderr, \"Nothing to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"No messages found to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519160","messageId":"20250529162020.45187-8-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 7/9] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:12Z","receivedAt":"2025-05-29T16:21:22Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex d99eed0659..c963ce62d8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1104,7 +1104,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519161","messageId":"20250529162020.45187-9-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 8/9] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:14Z","receivedAt":"2025-05-29T16:21:24Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex c963ce62d8..95b78fda42 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1596,7 +1596,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1704,7 +1705,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519162","messageId":"20250529162020.45187-10-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250529162020.45187-1-gargaditya08@live.com","subject":"[PATCH v8 9/9] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:21:15Z","receivedAt":"2025-05-29T16:21:26Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex a35f278baf..24e1459f5c 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -124,7 +127,8 @@ authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 95b78fda42..60562dc9b8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1619,6 +1625,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"Failed to connect to IMAP server.\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"Failed to list folders.\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1647,11 +1673,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"Failed to encode server folder.\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"Failed to encode server folder.\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1681,10 +1709,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1703,6 +1727,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1749,6 +1777,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1789,11 +1842,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n@@ -1803,6 +1851,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"Could not read from stdin.\"));\n-- \n2.49.0.638.g602e07a80b.dirty\n\n"},{"id":"519163","messageId":"xmqqldqfl6z3.fsf@gitster.g","threadId":"63502","inReplyTo":"42e07f4d-9888-4a1e-826a-b53b7d84fef6@gmail.com","subject":"Re: [PATCH v7 6/9] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-29T16:25:20Z","receivedAt":"2025-05-29T16:26:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I'm wondering why anyone would want to switch the backend at run-time?\n> There has been talk in the past about removing the openssl code [1]\n> and just relying on the curl backend. I think that is a worthwhile\n> goal as it simplifies the code and means we would avoid having to\n> worry about whether we're using openssl correctly [2].\n\nExcellent point.  Is there a downside if we only do imap via cURL\nlibrary and lose the code that directly use OpenSSL?\n"},{"id":"519164","messageId":"PN0PR01MB9588797F057CCDCE8EFD51BAB866A@PN0PR01MB9588.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqldqfl6z3.fsf@gitster.g","subject":"Re: [PATCH v7 6/9] imap-send: enable user to choose between libcurl and openssl using the config","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-29T16:28:06Z","receivedAt":"2025-05-29T16:28:24Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 29/05/25 9:55 pm, Junio C Hamano wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> I'm wondering why anyone would want to switch the backend at run-time?\n>> There has been talk in the past about removing the openssl code [1]\n>> and just relying on the curl backend. I think that is a worthwhile\n>> goal as it simplifies the code and means we would avoid having to\n>> worry about whether we're using openssl correctly [2].\n> \n> Excellent point.  Is there a downside if we only do imap via cURL\n> library and lose the code that directly use OpenSSL?\n\nI think the only real down side is removing CRAM-MD5 support, which\nwon't impact much, especially considering the fact that how little\nthis is used.\n\nAlthough, most code will still be needed is you are using the tunnel\noption, so in terms of code cleanup, not much will be lost.\n\nAnyways, in v8, I have removed this patch, and added another ability\nto list the available folders.\n"},{"id":"519275","messageId":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 0/9] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:13Z","receivedAt":"2025-05-30T17:32:47Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2: - Added support for OAuth2.0 with curl.\n    - Fixed the memory leak in case auth_cram_md5 fails.\nv3: - Improve wording in first patch\n    - Change misleading message if OAuth2.0 is used without OpenSSL\nv4: - Add PLAIN authentication mechanism for OpenSSL\n    - Improved wording in the first patch a bit more\nv5: - Add ability to specify destination folder using the command line\n    - Add ability to set a default between curl and openssl using the config\nv6: - Fix minor mistakes in --folder documentation\nv7: - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n    - Display port alongwith host when git credential is invoked and asks for a password\n    - Display the destination mailbox when sending a message\nv8: - Drop the patch that enabled user to choose between libcurl and openssl using the config\n    - Add ability to list the available folders by adding a `--list` option\nv9: - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\n\nAditya Garg (9):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix numerous spelling and grammar mistakes in logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  10 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 417 +++++++++++++++++++++++++++----\n 3 files changed, 432 insertions(+), 63 deletions(-)\n\nRange-diff against v8:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  f0743d46e1 !  2:  c5ee87051f imap-send: add support for OAuth2.0 authentication\n    @@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n     +    port = 993\n      ---------\n      \n    -+Gmail does not allow using your account password for `git imap-send`.\n    ++Gmail does not allow using your regular password for `git imap-send`.\n     +If you have multi-factor authentication set up on your Gmail account, you can generate\n     +an app-specific password for use with `git imap-send`.\n     +Visit https://security.google.com/settings/security/apppasswords to create it.\n    -+If you do not want to enable multi-factor authentication, you can use OAuth2.0\n    -+authentication as described below.\n    ++Alternatively, use OAuth2.0 authentication as described below.\n     +\n      [NOTE]\n      You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    @@ Documentation/git-imap-send.adoc: that the \"Folder doesn't exist\".\n      folder will be localized.\n      \n     +If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n    -+or `XOAUTH2` mechanism in your config. In such a case you will have to use an\n    -+OAuth2.0 access token in place of your password.\n    ++or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n    ++passwords, and also does not enforce the need of having multi-factor authentication.\n    ++You will have to use an OAuth2.0 access token in place of your password when using this\n    ++authentication.\n     +\n     +---------\n     +[imap]\n 2:  b1602644b7 =  3:  17e263ea27 imap-send: add PLAIN authentication method to OpenSSL\n 3:  49790e60cc =  4:  5c471f640b imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  2efe897379 =  5:  db8ee71785 imap-send: enable specifying the folder using the command line\n 5:  8f6676a046 =  6:  a8fbcdf9d5 imap-send: fix numerous spelling and grammar mistakes in logs\n 6:  69fdae55cd =  7:  a5dad0f2b2 imap-send: display port alongwith host when git credential is invoked\n 7:  187dbccd03 =  8:  d2569a5e36 imap-send: display the destination mailbox when sending a message\n 8:  03d7d6a772 !  9:  cf844b2632 imap-send: add ability to list the available folders\n    @@ Documentation/git-imap-send.adoc: OPTIONS\n      \n      CONFIGURATION\n      -------------\n    -@@ Documentation/git-imap-send.adoc: authentication as described below.\n    +@@ Documentation/git-imap-send.adoc: Alternatively, use OAuth2.0 authentication as described below.\n      \n      [NOTE]\n      You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519276","messageId":"PN3PR01MB9597B41B3091DB81D604D33FB861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 1/9] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:14Z","receivedAt":"2025-05-30T17:32:50Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519277","messageId":"PN3PR01MB9597C330AACC5BB89FC0B143B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:16Z","receivedAt":"2025-05-30T17:32:52Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 80 +++++++++++++++++++++++++++++++++-\n 2 files changed, 81 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 0c7844aff2..c07ff98c3a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,40 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +987,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -975,6 +1018,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1207,7 +1270,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519280","messageId":"PN3PR01MB9597BD09F7A450C6CA26B421B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:15Z","receivedAt":"2025-05-30T17:32:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 ++++++++-\n imap-send.c                      | 176 +++++++++++++++++++++++++++++--\n 3 files changed, 215 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..08ecb1e829 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n+passwords, and also does not enforce the need of having multi-factor authentication.\n+You will have to use an OAuth2.0 access token in place of your password when using this\n+authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..0c7844aff2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/* Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +959,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +991,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1222,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1553,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1575,21 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519278","messageId":"PN3PR01MB9597A0C8761F3E0E6B6A8539B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 4/9] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:17Z","receivedAt":"2025-05-30T17:32:54Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex c07ff98c3a..d0c7bac030 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1046,8 +1046,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519279","messageId":"PN3PR01MB9597C14385FFA97ADF5ED3C9B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:18Z","receivedAt":"2025-05-30T17:32:56Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 08ecb1e829..8f221240d0 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex d0c7bac030..337f1049ca 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1762,6 +1764,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519281","messageId":"PN3PR01MB9597BE246D115B06E3CC2373B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:19Z","receivedAt":"2025-05-30T17:32:58Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"A lot of spelling and grammar mistakes were found in the logs shown to\nthe user while using imap-send. Most of them are lack of a full stop at\nthe end of a sentence and first word of a sentence not being capitalized.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 44 ++++++++++++++++++++++----------------------\n 1 file changed, 22 insertions(+), 22 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 337f1049ca..d99eed0659 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \n \t/* try the common name */\n \tif (!(subj = X509_get_subject_name(cert)))\n-\t\treturn error(\"cannot get certificate subject\");\n+\t\treturn error(\"Cannot get certificate subject\");\n \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n-\t\treturn error(\"cannot get certificate common name\");\n+\t\treturn error(\"Cannot get certificate common name\");\n \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n \t\treturn 0;\n \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n@@ -905,7 +905,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n \tif (decoded_len < 0)\n-\t\tdie(\"invalid challenge %s\", challenge_64);\n+\t\tdie(\"Invalid challenge %s\", challenge_64);\n \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n \t\tdie(\"HMAC error\");\n \n@@ -1050,7 +1050,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1144,12 +1144,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\ttunnel.in = -1;\n \t\ttunnel.out = -1;\n \t\tif (start_command(&tunnel))\n-\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n+\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1168,7 +1168,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1206,7 +1206,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1220,7 +1220,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1232,7 +1232,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1340,13 +1340,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t\tif (!imap->buf.sock.ssl)\n \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n-\t\t\t\t\t  \"sent in the clear\\n\");\n+\t\t\t\t\t  \"sent in the clear.\\n\");\n \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n \t\t\t\tgoto bail;\n@@ -1591,12 +1591,12 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \n \tctx = imap_open_store(server, server->folder);\n \tif (!ctx) {\n-\t\tfprintf(stderr, \"failed to open store\\n\");\n+\t\tfprintf(stderr, \"Failed to open store.\\n\");\n \t\treturn 1;\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1648,7 +1648,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n \tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n+\t\tdie(\"Failed to encode server folder.\");\n \tstrbuf_addstr(&path, uri_encoded_folder);\n \tcurl_free(uri_encoded_folder);\n \n@@ -1704,7 +1704,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1788,13 +1788,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1803,20 +1803,20 @@ int cmd_main(int argc, const char **argv)\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n-\t\terror_errno(_(\"could not read from stdin\"));\n+\t\terror_errno(_(\"Could not read from stdin.\"));\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n-\t\tfprintf(stderr, \"nothing to send\\n\");\n+\t\tfprintf(stderr, \"Nothing to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"No messages found to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519282","messageId":"PN3PR01MB959702EF7D8B0DFA146766D7B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 7/9] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:20Z","receivedAt":"2025-05-30T17:32:59Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex d99eed0659..c963ce62d8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1104,7 +1104,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519283","messageId":"PN3PR01MB9597BCBC9EE3A93DA00A98FFB861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 8/9] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:21Z","receivedAt":"2025-05-30T17:33:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex c963ce62d8..95b78fda42 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1596,7 +1596,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1704,7 +1705,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519284","messageId":"PN3PR01MB95979DD3572EF490BE5D5223B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597B444D85F7A7D1B6546C3B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v9 9/9] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-30T17:32:22Z","receivedAt":"2025-05-30T17:33:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8f221240d0..379a371c08 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 95b78fda42..60562dc9b8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1619,6 +1625,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"Failed to connect to IMAP server.\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"Failed to list folders.\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1647,11 +1673,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"Failed to encode server folder.\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"Failed to encode server folder.\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1681,10 +1709,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1703,6 +1727,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1749,6 +1777,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1789,11 +1842,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n@@ -1803,6 +1851,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"Could not read from stdin.\"));\n-- \n2.49.0.639.ge2dd5d9d81\n\n"},{"id":"519290","messageId":"CAPig+cSFLF9de3i7RsG6j4wSGkRRhGgOGOovoFXm9U2+t=qC+g@mail.gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB9597BD09F7A450C6CA26B421B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v9 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-05-30T20:51:21Z","receivedAt":"2025-05-30T20:51:33Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, May 30, 2025 at 1:32 PM Aditya Garg <gargaditya08@live.com> wrote:\n> OAuth2.0 is a new way of authentication supported by various email providers\n> these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\n> for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\n> XOAUTH2 is Google's proprietary mechanism (See [3]).\n>\n> [1]: https://datatracker.ietf.org/doc/html/rfc5801\n> [2]: https://datatracker.ietf.org/doc/html/rfc7628\n> [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n\nNot a proper review, just something I spotted several versions back\nbut assumed that someone else -- providing a proper review -- would\nmention...\n\n> diff --git a/imap-send.c b/imap-send.c\n> @@ -885,6 +889,66 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n> +static char *oauthbearer_base64(const char *user, const char *access_token)\n> +{\n> +       /* Compose the OAUTHBEARER string\n> +        *\n> +        * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n> +        *\n> +        * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n> +        * * gs2-cb-flag `n` -> client does not support CB\n> +        * * gs2-authzid `a=\" {User} \"`\n> +        *\n> +        * The second part are key value pairs containing host, port and auth as\n> +        * described in RFC7628.\n> +        *\n> +        * https://datatracker.ietf.org/doc/html/rfc5801\n> +        * https://datatracker.ietf.org/doc/html/rfc7628\n> +        */\n\nOn this project, multi-line comments are formatted like this:\n\n    /*\n     * Line 1\n     * Line 2\n     * ...\n     */\n\nThe same observation applies to other parts of this patch, as well.\n"},{"id":"519293","messageId":"xmqqfrglerbx.fsf@gitster.g","threadId":"63502","inReplyTo":"CAPig+cSFLF9de3i7RsG6j4wSGkRRhGgOGOovoFXm9U2+t=qC+g@mail.gmail.com","subject":"Re: [PATCH v9 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-30T21:12:02Z","receivedAt":"2025-05-30T21:12:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Not a proper review, just something I spotted several versions back\n> but assumed that someone else -- providing a proper review -- would\n> mention...\n\nI suspect that we weren't ready to prifvide \"a proper review\" yet on\nthis series at the coding style level, while the design at a bit\nhigher level, like \"should choice of openssl/curl be runtime?\", was\ndiscussed.\n\n> On this project, multi-line comments are formatted like this:\n>\n>     /*\n>      * Line 1\n>      * Line 2\n>      * ...\n>      */\n>\n> The same observation applies to other parts of this patch, as well.\n\nThanks.\n"},{"id":"519300","messageId":"xmqqecw5d2w0.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597C14385FFA97ADF5ED3C9B861A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v9 5/9] imap-send: enable specifying the folder using the command line","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-31T00:45:19Z","receivedAt":"2025-05-31T00:45:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Some users may very often want to imap-send messages to a folder\n> other than the default set in the config. Add a command line\n> argument for the same.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  Documentation/config/imap.adoc   |  5 +++--\n>  Documentation/git-imap-send.adoc | 15 +++++++++++----\n>  imap-send.c                      |  9 ++++++++-\n>  3 files changed, 22 insertions(+), 7 deletions(-)\n\nDid you forget to adjust tests that expect the traditional messages?\n"},{"id":"519306","messageId":"PN3PR01MB95972787839A31A0152A16ABB860A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqfrglerbx.fsf@gitster.g","subject":"Re: [PATCH v9 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-31T09:06:39Z","receivedAt":"2025-05-31T09:06:44Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 31 May 2025, at 2:42 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Eric Sunshine <sunshine@sunshineco.com> writes:\n> \n>> Not a proper review, just something I spotted several versions back\n>> but assumed that someone else -- providing a proper review -- would\n>> mention...\n> \n> I suspect that we weren't ready to prifvide \"a proper review\" yet on\n> this series at the coding style level, while the design at a bit\n> higher level, like \"should choice of openssl/curl be runtime?\", was\n> discussed.\n\nI think that has been made clear. Just leave it as it is right now and\ndrop the patch allowing user to set it using the config.\n"},{"id":"519307","messageId":"PN3PR01MB95978465D637DDDAE5B66D66B860A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqecw5d2w0.fsf@gitster.g","subject":"Re: [PATCH v9 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-05-31T09:16:01Z","receivedAt":"2025-05-31T09:16:10Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 31 May 2025, at 6:15 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Some users may very often want to imap-send messages to a folder\n>> other than the default set in the config. Add a command line\n>> argument for the same.\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> Documentation/config/imap.adoc   |  5 +++--\n>> Documentation/git-imap-send.adoc | 15 +++++++++++----\n>> imap-send.c                      |  9 ++++++++-\n>> 3 files changed, 22 insertions(+), 7 deletions(-)\n> \n> Did you forget to adjust tests that expect the traditional messages?\n\nI am not sure what you mean here. Could be more specific?\n\nIn any case, whatever folder is passed using the --folder argument,\nwill be treated the same way as the same folder would be when set\nusing the config."},{"id":"519321","messageId":"xmqqo6v8xjyy.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB95978465D637DDDAE5B66D66B860A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v9 5/9] imap-send: enable specifying the folder using the command line","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-01T02:40:37Z","receivedAt":"2025-06-01T02:40:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n>> On 31 May 2025, at 6:15 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> \n>> ﻿Aditya Garg <gargaditya08@live.com> writes:\n>> \n>>> Some users may very often want to imap-send messages to a folder\n>>> other than the default set in the config. Add a command line\n>>> argument for the same.\n>>> \n>>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>>> ---\n>>> Documentation/config/imap.adoc   |  5 +++--\n>>> Documentation/git-imap-send.adoc | 15 +++++++++++----\n>>> imap-send.c                      |  9 ++++++++-\n>>> 3 files changed, 22 insertions(+), 7 deletions(-)\n>> \n>> Did you forget to adjust tests that expect the traditional messages?\n>\n> I am not sure what you mean here. Could be more specific?\n>\n> In any case, whatever folder is passed using the --folder argument,\n> will be treated the same way as the same folder would be when set\n> using the config.\n\nAh, not this step, but if you ran\n\n$ make test\n\nyou will see what I meant.  It failed during one of my integration\nrun.\n\nPlease make it a habit to always do so, if you haven't already,\nbefore sending your patches.\n\n\n"},{"id":"519322","messageId":"PN3PR01MB9597D45FBA73D751656811C7B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqo6v8xjyy.fsf@gitster.g","subject":"Re: [PATCH v9 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T04:41:50Z","receivedAt":"2025-06-01T04:41:55Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 1 Jun 2025, at 8:10 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>>>> On 31 May 2025, at 6:15 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>> \n>>> ﻿Aditya Garg <gargaditya08@live.com> writes:\n>>> \n>>>> Some users may very often want to imap-send messages to a folder\n>>>> other than the default set in the config. Add a command line\n>>>> argument for the same.\n>>>> \n>>>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>>>> ---\n>>>> Documentation/config/imap.adoc   |  5 +++--\n>>>> Documentation/git-imap-send.adoc | 15 +++++++++++----\n>>>> imap-send.c                      |  9 ++++++++-\n>>>> 3 files changed, 22 insertions(+), 7 deletions(-)\n>>> \n>>> Did you forget to adjust tests that expect the traditional messages?\n>> \n>> I am not sure what you mean here. Could be more specific?\n>> \n>> In any case, whatever folder is passed using the --folder argument,\n>> will be treated the same way as the same folder would be when set\n>> using the config.\n> \n> Ah, not this step, but if you ran\n> \n> $ make test\n> \n> you will see what I meant.  It failed during one of my integration\n> run.\n> \n> Please make it a habit to always do so, if you haven't already,\n> before sending your patches.\n\nOh ok.\n"},{"id":"519323","messageId":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 0/9] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:26Z","receivedAt":"2025-06-01T07:12:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\n\nAditya Garg (9):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix numerous spelling and grammar mistakes in logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  10 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 421 +++++++++++++++++++++++++++----\n t/t1517-outside-repo.sh          |   2 +-\n 4 files changed, 437 insertions(+), 64 deletions(-)\n\nRange-diff against v9:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  c5ee87051f !  2:  02037873a1 imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\tint raw_len, b64_len;\n     +\tchar *raw, *b64;\n     +\n    -+\t/* Compose the OAUTHBEARER string\n    ++\t/*\n    ++\t * Compose the OAUTHBEARER string\n     +\t *\n     +\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n     +\t *\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\tint raw_len, b64_len;\n     +\tchar *raw, *b64;\n     +\n    -+\t/* Compose the XOAUTH2 string\n    ++\t/*\n    ++\t * Compose the XOAUTH2 string\n     +\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n     +\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n     +\t */\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n     +\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n     +\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n     +\n    -+\t\t\t/* While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n    ++\t\t\t/*\n    ++\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n     +\t\t\t * upon debugging, it has been found that it is capable of detecting\n     +\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n     +\t\t\t */\n 2:  17e263ea27 !  3:  3a0be43838 imap-send: add PLAIN authentication method to OpenSSL\n    @@ imap-send.c: static char hexchar(unsigned int b)\n     +\tint b64_len;\n     +\tchar *raw, *b64;\n     +\n    -+\t/* Compose the PLAIN string\n    ++\t/*\n    ++\t * Compose the PLAIN string\n     +\t *\n     +\t * The username and password are combined to one string and base64 encoded.\n     +\t * \"\\0user\\0pass\"\n 3:  5c471f640b =  4:  45f5b3f1ff imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  db8ee71785 =  5:  8899f686d7 imap-send: enable specifying the folder using the command line\n 5:  a8fbcdf9d5 !  6:  991f978c22 imap-send: fix numerous spelling and grammar mistakes in logs\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\tret = 1;\n      \t\tgoto out;\n      \t}\n    +\n    + ## t/t1517-outside-repo.sh ##\n    +@@ t/t1517-outside-repo.sh: test_expect_success 'imap-send outside repository' '\n    + \ttest_config_global imap.host imaps://localhost &&\n    + \ttest_config_global imap.folder Drafts &&\n    + \n    +-\techo nothing to send >expect &&\n    ++\techo Nothing to send. >expect &&\n    + \ttest_must_fail git imap-send -v </dev/null 2>actual &&\n    + \ttest_cmp expect actual &&\n    + \n 6:  a5dad0f2b2 =  7:  e436a12198 imap-send: display port alongwith host when git credential is invoked\n 7:  d2569a5e36 =  8:  5183253004 imap-send: display the destination mailbox when sending a message\n 8:  cf844b2632 =  9:  c33469a5db imap-send: add ability to list the available folders\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519324","messageId":"PN3PR01MB95974D14FA95D297D82E8B46B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:28Z","receivedAt":"2025-06-01T07:12:15Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++-\n imap-send.c                      | 179 +++++++++++++++++++++++++++++--\n 3 files changed, 218 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..08ecb1e829 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n+passwords, and also does not enforce the need of having multi-factor authentication.\n+You will have to use an OAuth2.0 access token in place of your password when using this\n+authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..4f3a1fb5b1 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +961,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +993,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1224,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1555,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1577,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519326","messageId":"PN3PR01MB95976B6E2D472ED8FAA6D699B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 1/9] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:27Z","receivedAt":"2025-06-01T07:12:16Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519325","messageId":"PN3PR01MB95973304BB7834B25377396EB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:29Z","receivedAt":"2025-06-01T07:12:18Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 81 +++++++++++++++++++++++++++++++++-\n 2 files changed, 82 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 4f3a1fb5b1..bc26abd150 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -953,6 +990,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -977,6 +1021,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1209,7 +1273,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519327","messageId":"PN3PR01MB95971A2A81DD69A58D2506BFB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 4/9] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:30Z","receivedAt":"2025-06-01T07:12:20Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bc26abd150..e169c5e919 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1049,8 +1049,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519328","messageId":"PN3PR01MB9597B2AF1ED0AB5902C72C93B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:31Z","receivedAt":"2025-06-01T07:12:21Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 08ecb1e829..8f221240d0 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex e169c5e919..cfa335b647 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1766,6 +1768,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519329","messageId":"PN3PR01MB9597A33B17E07B461A5C276EB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:32Z","receivedAt":"2025-06-01T07:12:22Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"A lot of spelling and grammar mistakes were found in the logs shown to\nthe user while using imap-send. Most of them are lack of a full stop at\nthe end of a sentence and first word of a sentence not being capitalized.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c             | 44 ++++++++++++++++++++---------------------\n t/t1517-outside-repo.sh |  2 +-\n 2 files changed, 23 insertions(+), 23 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex cfa335b647..d791cbff43 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n \n \t/* try the common name */\n \tif (!(subj = X509_get_subject_name(cert)))\n-\t\treturn error(\"cannot get certificate subject\");\n+\t\treturn error(\"Cannot get certificate subject\");\n \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n-\t\treturn error(\"cannot get certificate common name\");\n+\t\treturn error(\"Cannot get certificate common name\");\n \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n \t\treturn 0;\n \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n@@ -906,7 +906,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n \tif (decoded_len < 0)\n-\t\tdie(\"invalid challenge %s\", challenge_64);\n+\t\tdie(\"Invalid challenge %s\", challenge_64);\n \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n \t\tdie(\"HMAC error\");\n \n@@ -1053,7 +1053,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1147,12 +1147,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\ttunnel.in = -1;\n \t\ttunnel.out = -1;\n \t\tif (start_command(&tunnel))\n-\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n+\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n \n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1171,7 +1171,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1209,7 +1209,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1235,7 +1235,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1343,13 +1343,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t\tif (!imap->buf.sock.ssl)\n \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n-\t\t\t\t\t  \"sent in the clear\\n\");\n+\t\t\t\t\t  \"sent in the clear.\\n\");\n \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n \t\t\t\tgoto bail;\n@@ -1594,12 +1594,12 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \n \tctx = imap_open_store(server, server->folder);\n \tif (!ctx) {\n-\t\tfprintf(stderr, \"failed to open store\\n\");\n+\t\tfprintf(stderr, \"Failed to open store.\\n\");\n \t\treturn 1;\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1651,7 +1651,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n \tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n+\t\tdie(\"Failed to encode server folder.\");\n \tstrbuf_addstr(&path, uri_encoded_folder);\n \tcurl_free(uri_encoded_folder);\n \n@@ -1708,7 +1708,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1792,13 +1792,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1807,20 +1807,20 @@ int cmd_main(int argc, const char **argv)\n \n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n-\t\terror_errno(_(\"could not read from stdin\"));\n+\t\terror_errno(_(\"Could not read from stdin.\"));\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \tif (all_msgs.len == 0) {\n-\t\tfprintf(stderr, \"nothing to send\\n\");\n+\t\tfprintf(stderr, \"Nothing to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"No messages found to send.\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\ndiff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh\nindex 6824581317..bc6e79613f 100755\n--- a/t/t1517-outside-repo.sh\n+++ b/t/t1517-outside-repo.sh\n@@ -59,7 +59,7 @@ test_expect_success 'imap-send outside repository' '\n \ttest_config_global imap.host imaps://localhost &&\n \ttest_config_global imap.folder Drafts &&\n \n-\techo nothing to send >expect &&\n+\techo Nothing to send. >expect &&\n \ttest_must_fail git imap-send -v </dev/null 2>actual &&\n \ttest_cmp expect actual &&\n \n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519330","messageId":"PN3PR01MB95979E9E58704262074EE877B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 7/9] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:33Z","receivedAt":"2025-06-01T07:12:23Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex d791cbff43..3ffe3ae5cc 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1107,7 +1107,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519331","messageId":"PN3PR01MB95972AA75D947243D11B746AB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 8/9] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:34Z","receivedAt":"2025-06-01T07:12:24Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3ffe3ae5cc..86d46395de 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1599,7 +1599,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1708,7 +1709,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519332","messageId":"PN3PR01MB95970D54BD76076100CEDC5AB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C4D1176FC7E9A459C42DB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v10 9/9] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:10:35Z","receivedAt":"2025-06-01T07:12:25Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8f221240d0..379a371c08 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 86d46395de..4a7130c20b 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1622,6 +1628,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"Failed to connect to IMAP server.\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"Failed to list folders.\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1650,11 +1676,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"Failed to encode server folder.\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"Failed to encode server folder.\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1685,10 +1713,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1707,6 +1731,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1753,6 +1781,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1793,11 +1846,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n@@ -1807,6 +1855,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"Could not read from stdin.\"));\n-- \n2.49.0.638.g67a2d115ec\n\n"},{"id":"519333","messageId":"CAPig+cTUFAFYx7SLtSbgmxuZUbbwRG3011fH8s_jFkWbwO=uig@mail.gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB9597A33B17E07B461A5C276EB863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v10 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-06-01T07:28:56Z","receivedAt":"2025-06-01T07:29:08Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jun 1, 2025 at 3:12 AM Aditya Garg <gargaditya08@live.com> wrote:\n> A lot of spelling and grammar mistakes were found in the logs shown to\n> the user while using imap-send. Most of them are lack of a full stop at\n> the end of a sentence and first word of a sentence not being capitalized.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n> diff --git a/imap-send.c b/imap-send.c\n> @@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n>         if (!(subj = X509_get_subject_name(cert)))\n> -               return error(\"cannot get certificate subject\");\n> +               return error(\"Cannot get certificate subject\");\n>         if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n> -               return error(\"cannot get certificate common name\");\n> +               return error(\"Cannot get certificate common name\");\n\nThis patch generally seems to be taking the code in a direction\nopposite the rest of the project. Quoting from\nDocumentation/CodingGuidelines:\n\n  Error Messages\n\n   - Do not end a single-sentence error message with a full stop.\n\n   - Do not capitalize the first word, only because it is the first\n     word in the message (\"unable to open '%s'\", not \"Unable to open\n     '%s'\"). But \"SHA-3 not supported\" is fine, because the reason the\n     first word is capitalized is not because it is at the beginning\n     of the sentence, but because the word would be spelled in capital\n     letters even when it appeared in the middle of the sentence.\n\n> @@ -1053,7 +1053,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n>         if (ret != strlen(response)) {\n>                 free(response);\n> -               return error(\"IMAP error: sending response failed\");\n> +               return error(\"IMAP error: sending CRAM-MD5 response failed\");\n>         }\n\nProviding more context in the error message, as done here, seems welcome.\n\n> @@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n> -                       fputs(\"Error: unable to connect to server.\\n\", stderr);\n> +                       fputs(\"Error: unable to connect to server\\n\", stderr);\n> @@ -1343,13 +1343,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n> -                               fprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n> +                               fprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n> @@ -1594,12 +1594,12 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n> -               fprintf(stderr, \"failed to open store\\n\");\n> +               fprintf(stderr, \"Failed to open store.\\n\");\n\nTaking the above guidelines into account, these probably ought to be\nchanged to start with lowercase.\n"},{"id":"519334","messageId":"PN3PR01MB95971E7CD9AE473139CBE0F7B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cTUFAFYx7SLtSbgmxuZUbbwRG3011fH8s_jFkWbwO=uig@mail.gmail.com","subject":"Re: [PATCH v10 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:30:52Z","receivedAt":"2025-06-01T07:31:00Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 01/06/25 12:58 pm, Eric Sunshine wrote:\n> On Sun, Jun 1, 2025 at 3:12 AM Aditya Garg <gargaditya08@live.com> wrote:\n>> A lot of spelling and grammar mistakes were found in the logs shown to\n>> the user while using imap-send. Most of them are lack of a full stop at\n>> the end of a sentence and first word of a sentence not being capitalized.\n>>\n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> diff --git a/imap-send.c b/imap-send.c\n>> @@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n>>         if (!(subj = X509_get_subject_name(cert)))\n>> -               return error(\"cannot get certificate subject\");\n>> +               return error(\"Cannot get certificate subject\");\n>>         if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n>> -               return error(\"cannot get certificate common name\");\n>> +               return error(\"Cannot get certificate common name\");\n> \n> This patch generally seems to be taking the code in a direction\n> opposite the rest of the project. Quoting from\n> Documentation/CodingGuidelines:\n> \n\nLets drop this patch itself\n"},{"id":"519335","messageId":"PN3PR01MB9597894F4B4815A27E9BF7E0B863A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"CAPig+cTUFAFYx7SLtSbgmxuZUbbwRG3011fH8s_jFkWbwO=uig@mail.gmail.com","subject":"Re: [PATCH v10 6/9] imap-send: fix numerous spelling and grammar mistakes in logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T07:32:38Z","receivedAt":"2025-06-01T07:32:46Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 01/06/25 12:58 pm, Eric Sunshine wrote:\n> On Sun, Jun 1, 2025 at 3:12 AM Aditya Garg <gargaditya08@live.com> wrote:\n>> A lot of spelling and grammar mistakes were found in the logs shown to\n>> the user while using imap-send. Most of them are lack of a full stop at\n>> the end of a sentence and first word of a sentence not being capitalized.\n>>\n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> diff --git a/imap-send.c b/imap-send.c\n>> @@ -249,9 +249,9 @@ static int verify_hostname(X509 *cert, const char *hostname)\n>>         if (!(subj = X509_get_subject_name(cert)))\n>> -               return error(\"cannot get certificate subject\");\n>> +               return error(\"Cannot get certificate subject\");\n>>         if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n>> -               return error(\"cannot get certificate common name\");\n>> +               return error(\"Cannot get certificate common name\");\n> \n> This patch generally seems to be taking the code in a direction\n> opposite the rest of the project. Quoting from\n> Documentation/CodingGuidelines:\n> \n>   Error Messages\n> \n>    - Do not end a single-sentence error message with a full stop.\n> \n>    - Do not capitalize the first word, only because it is the first\n>      word in the message (\"unable to open '%s'\", not \"Unable to open\n>      '%s'\"). But \"SHA-3 not supported\" is fine, because the reason the\n>      first word is capitalized is not because it is at the beginning\n>      of the sentence, but because the word would be spelled in capital\n>      letters even when it appeared in the middle of the sentence.\n> \n>> @@ -1053,7 +1053,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n>>         if (ret != strlen(response)) {\n>>                 free(response);\n>> -               return error(\"IMAP error: sending response failed\");\n>> +               return error(\"IMAP error: sending CRAM-MD5 response failed\");\n>>         }\n> \n> Providing more context in the error message, as done here, seems welcome.\n\nHmm, ok\n"},{"id":"519337","messageId":"20250601083821.2440110-1-gargaditya08@live.com","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v11 0/9] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:49Z","receivedAt":"2025-06-01T08:38:56Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\n\nAditya Garg (9):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix minor mistakes in the logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  10 +-\n Documentation/git-imap-send.adoc |  68 +++++-\n imap-send.c                      | 407 +++++++++++++++++++++++++++----\n 3 files changed, 429 insertions(+), 56 deletions(-)\n\nRange-diff against v10:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  991f978c22 !  2:  02037873a1 imap-send: fix numerous spelling and grammar mistakes in logs\n    @@ Metadata\n     Author: Aditya Garg <gargaditya08@live.com>\n     \n      ## Commit message ##\n    -    imap-send: fix numerous spelling and grammar mistakes in logs\n    +    imap-send: add support for OAuth2.0 authentication\n     \n    -    A lot of spelling and grammar mistakes were found in the logs shown to\n    -    the user while using imap-send. Most of them are lack of a full stop at\n    -    the end of a sentence and first word of a sentence not being capitalized.\n    +    OAuth2.0 is a new way of authentication supported by various email providers\n    +    these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\n    +    for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\n    +    XOAUTH2 is Google's proprietary mechanism (See [3]).\n    +\n    +    [1]: https://datatracker.ietf.org/doc/html/rfc5801\n    +    [2]: https://datatracker.ietf.org/doc/html/rfc7628\n    +    [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n     \n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n    + ## Documentation/config/imap.adoc ##\n    +@@ Documentation/config/imap.adoc: imap.authMethod::\n    + \tSpecify the authentication method for authenticating with the IMAP server.\n    + \tIf Git was built with the NO_CURL option, or if your curl version is older\n    + \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n    +-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n    +-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n    ++\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n    ++\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    ++\tplaintext LOGIN command.\n    +\n    + ## Documentation/git-imap-send.adoc ##\n    +@@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n    + \n    + ---------\n    + [imap]\n    +-\tfolder = \"[Gmail]/Drafts\"\n    +-\thost = imaps://imap.gmail.com\n    +-\tuser = user@gmail.com\n    +-\tport = 993\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    + ---------\n    + \n    ++Gmail does not allow using your regular password for `git imap-send`.\n    ++If you have multi-factor authentication set up on your Gmail account, you can generate\n    ++an app-specific password for use with `git imap-send`.\n    ++Visit https://security.google.com/settings/security/apppasswords to create it.\n    ++Alternatively, use OAuth2.0 authentication as described below.\n    ++\n    + [NOTE]\n    + You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    + that the \"Folder doesn't exist\".\n    +@@ Documentation/git-imap-send.adoc: that the \"Folder doesn't exist\".\n    + If your Gmail account is set to another language than English, the name of the \"Drafts\"\n    + folder will be localized.\n    + \n    ++If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n    ++or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n    ++passwords, and also does not enforce the need of having multi-factor authentication.\n    ++You will have to use an OAuth2.0 access token in place of your password when using this\n    ++authentication.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    ++    authmethod = OAUTHBEARER\n    ++---------\n    ++\n    ++Using Outlook's IMAP interface:\n    ++\n    ++Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n    ++supports only `XOAUTH2` as the mechanism.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"Drafts\"\n    ++    host = imaps://outlook.office365.com\n    ++    user = user@outlook.com\n    ++    port = 993\n    ++    authmethod = XOAUTH2\n    ++---------\n    ++\n    + Once the commits are ready to be sent, run the following command:\n    + \n    +   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n    +@@ Documentation/git-imap-send.adoc: Just make sure to disable line wrapping in the email client (Gmail's web\n    + interface will wrap lines no matter what, so you need to use a real\n    + IMAP client).\n    + \n    ++In case you are using OAuth2.0 authentication, it is easier to use credential\n    ++helpers to generate tokens. Credential helpers suggested in\n    ++linkgit:git-send-email[1] can be used for `git imap-send` as well.\n    ++\n    + CAUTION\n    + -------\n    + It is still your responsibility to make sure that the email message\n    +\n      ## imap-send.c ##\n    -@@ imap-send.c: static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n    - \t\t\t      const struct imap_server_conf *cfg UNUSED,\n    - \t\t\t      int use_tls_only UNUSED)\n    - {\n    --\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n    -+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n    - \treturn -1;\n    - }\n    +@@ imap-send.c: enum CAPABILITY {\n    + \tLITERALPLUS,\n    + \tNAMESPACE,\n    + \tSTARTTLS,\n    +-\tAUTH_CRAM_MD5\n    ++\tAUTH_CRAM_MD5,\n    ++\tAUTH_OAUTHBEARER,\n    ++\tAUTH_XOAUTH2\n    + };\n      \n    -@@ imap-send.c: static int verify_hostname(X509 *cert, const char *hostname)\n    - \n    - \t/* try the common name */\n    - \tif (!(subj = X509_get_subject_name(cert)))\n    --\t\treturn error(\"cannot get certificate subject\");\n    -+\t\treturn error(\"Cannot get certificate subject\");\n    - \tif ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)\n    --\t\treturn error(\"cannot get certificate common name\");\n    -+\t\treturn error(\"Cannot get certificate common name\");\n    - \tif (strlen(cname) == (size_t)len && host_matches(hostname, cname))\n    - \t\treturn 0;\n    - \treturn error(\"certificate owner '%s' does not match hostname '%s'\",\n    -@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    - \tdecoded_len = EVP_DecodeBlock((unsigned char *)challenge,\n    - \t\t\t\t      (unsigned char *)challenge_64, encoded_len);\n    - \tif (decoded_len < 0)\n    --\t\tdie(\"invalid challenge %s\", challenge_64);\n    -+\t\tdie(\"Invalid challenge %s\", challenge_64);\n    - \tif (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))\n    - \t\tdie(\"HMAC error\");\n    + static const char *cap_list[] = {\n    +@@ imap-send.c: static const char *cap_list[] = {\n    + \t\"NAMESPACE\",\n    + \t\"STARTTLS\",\n    + \t\"AUTH=CRAM-MD5\",\n    ++\t\"AUTH=OAUTHBEARER\",\n    ++\t\"AUTH=XOAUTH2\",\n    + };\n      \n    -@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    - \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n    - \tif (ret != strlen(response)) {\n    - \t\tfree(response);\n    --\t\treturn error(\"IMAP error: sending response failed\");\n    -+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n    - \t}\n    + #define RESP_OK    0\n    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    + \treturn (char *)response_64;\n    + }\n      \n    - \tfree(response);\n    -@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\ttunnel.in = -1;\n    - \t\ttunnel.out = -1;\n    - \t\tif (start_command(&tunnel))\n    --\t\t\tdie(\"cannot start proxy %s\", srvc->tunnel);\n    -+\t\t\tdie(\"Cannot start proxy %s\", srvc->tunnel);\n    - \n    - \t\timap->buf.sock.fd[0] = tunnel.out;\n    - \t\timap->buf.sock.fd[1] = tunnel.in;\n    - \n    --\t\timap_info(\"ok\\n\");\n    -+\t\timap_info(\"OK\\n\");\n    - \t} else {\n    - #ifndef NO_IPV6\n    - \t\tstruct addrinfo hints, *ai0, *ai;\n    -@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n    - \t\t\tgoto bail;\n    - \t\t}\n    --\t\timap_info(\"ok\\n\");\n    -+\t\timap_info(\"OK\\n\");\n    - \n    - \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n    - \t\t\tchar addr[NI_MAXHOST];\n    -@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\tperror(\"gethostbyname\");\n    - \t\t\tgoto bail;\n    - \t\t}\n    --\t\timap_info(\"ok\\n\");\n    -+\t\timap_info(\"OK\\n\");\n    ++static char *oauthbearer_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the OAUTHBEARER string\n    ++\t *\n    ++\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n    ++\t *\n    ++\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n    ++\t * * gs2-cb-flag `n` -> client does not support CB\n    ++\t * * gs2-authzid `a=\" {User} \"`\n    ++\t *\n    ++\t * The second part are key value pairs containing host, port and auth as\n    ++\t * described in RFC7628.\n    ++\t *\n    ++\t * https://datatracker.ietf.org/doc/html/rfc5801\n    ++\t * https://datatracker.ietf.org/doc/html/rfc7628\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    ++static char *xoauth2_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the XOAUTH2 string\n    ++\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n    ++\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    + #else\n      \n    - \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n    + static char *cram(const char *challenge_64 UNUSED,\n    +@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    + \t    \"you have to build git-imap-send with OpenSSL library.\");\n    + }\n      \n    -@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t}\n    ++static char *oauthbearer_base64(const char *user UNUSED,\n    ++\t\t  const char *access_token UNUSED)\n    ++{\n    ++\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n    ++\t    \"with OpenSSL library, but its support has not been compiled in.\");\n    ++}\n    ++\n    ++static char *xoauth2_base64(const char *user UNUSED,\n    ++\t\t  const char *access_token UNUSED)\n    ++{\n    ++\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n    ++\t    \"with OpenSSL library, but its support has not been compiled in.\");\n    ++}\n    ++\n      #endif\n    - \t\tif (s < 0) {\n    --\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n    -+\t\t\tfputs(\"Error: unable to connect to server\\n\", stderr);\n    - \t\t\tgoto bail;\n    - \t\t}\n      \n    -@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\tclose(s);\n    - \t\t\tgoto bail;\n    - \t\t}\n    --\t\timap_info(\"ok\\n\");\n    -+\t\timap_info(\"OK\\n\");\n    - \t}\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \treturn 0;\n    + }\n      \n    - \t/* read the greeting string */\n    ++static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n    ++\t}\n    ++\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n    ++\t}\n    ++\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    + static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n    + {\n    + \tif (srvc->user && srvc->pass)\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\t}\n    - \t\t} else {\n    - \t\t\tif (CAP(NOLOGIN)) {\n    --\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n    -+\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN.\\n\",\n    - \t\t\t\t\tsrvc->user, srvc->host);\n    + \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n    + \t\t\t\t\tgoto bail;\n    + \t\t\t\t}\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n    ++\t\t\t\t\tfprintf(stderr, \"You specified \"\n    ++\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n    ++\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t\t}\n    ++\t\t\t\t/* OAUTHBEARER */\n    ++\n    ++\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    ++\t\t\t\tcb.cont = auth_oauthbearer;\n    ++\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n    ++\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t\t}\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n    ++\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n    ++\t\t\t\t\tfprintf(stderr, \"You specified \"\n    ++\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n    ++\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t\t}\n    ++\t\t\t\t/* XOAUTH2 */\n    ++\n    ++\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    ++\t\t\t\tcb.cont = auth_xoauth2;\n    ++\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n    ++\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t\t}\n    + \t\t\t} else {\n    + \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n      \t\t\t\tgoto bail;\n    - \t\t\t}\n    - \t\t\tif (!imap->buf.sock.ssl)\n    - \t\t\t\timap_warn(\"*** IMAP Warning *** Password is being \"\n    --\t\t\t\t\t  \"sent in the clear\\n\");\n    -+\t\t\t\t\t  \"sent in the clear.\\n\");\n    - \t\t\tif (imap_exec(ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user, srvc->pass) != RESP_OK) {\n    - \t\t\t\tfprintf(stderr, \"IMAP error: LOGIN failed\\n\");\n    - \t\t\t\tgoto bail;\n    -@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n    - \n    - \tctx = imap_open_store(server, server->folder);\n    - \tif (!ctx) {\n    --\t\tfprintf(stderr, \"failed to open store\\n\");\n    -+\t\tfprintf(stderr, \"Failed to open store.\\n\");\n    - \t\treturn 1;\n    - \t}\n    - \tctx->name = server->folder;\n    - \n    --\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \n     @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n      \n    - \turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n    - \tif (!uri_encoded_folder)\n    --\t\tdie(\"failed to encode server folder\");\n    -+\t\tdie(\"Failed to encode server folder.\");\n    - \tstrbuf_addstr(&path, uri_encoded_folder);\n    - \tcurl_free(uri_encoded_folder);\n    - \n    -@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    - \tcurl = setup_curl(server, &cred);\n    - \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n    - \n    --\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \t\tint prev_len;\n    -@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    - \t\tserver.port = server.use_ssl ? 993 : 143;\n    - \n    - \tif (!server.folder) {\n    --\t\tfprintf(stderr, \"no imap store specified\\n\");\n    -+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    - \t\tret = 1;\n    - \t\tgoto out;\n    - \t}\n    - \tif (!server.host) {\n    - \t\tif (!server.tunnel) {\n    --\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n    -+\t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n    - \t\t\tret = 1;\n    - \t\t\tgoto out;\n    - \t\t}\n    -@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    - \n    - \t/* read the messages */\n    - \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n    --\t\terror_errno(_(\"could not read from stdin\"));\n    -+\t\terror_errno(_(\"Could not read from stdin.\"));\n    - \t\tret = 1;\n    - \t\tgoto out;\n    - \t}\n    + \tserver_fill_credential(srvc, cred);\n    + \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n    +-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n    ++\n    ++\tif (!srvc->auth_method ||\n    ++\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n    ++\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n    ++\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n      \n    - \tif (all_msgs.len == 0) {\n    --\t\tfprintf(stderr, \"nothing to send\\n\");\n    -+\t\tfprintf(stderr, \"Nothing to send.\\n\");\n    - \t\tret = 1;\n    - \t\tgoto out;\n    - \t}\n    + \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n    + \tstrbuf_addstr(&path, srvc->host);\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n      \n    - \ttotal = count_messages(&all_msgs);\n    - \tif (!total) {\n    --\t\tfprintf(stderr, \"no messages to send\\n\");\n    -+\t\tfprintf(stderr, \"No messages found to send.\\n\");\n    - \t\tret = 1;\n    - \t\tgoto out;\n    + \tif (srvc->auth_method) {\n    +-\t\tstruct strbuf auth = STRBUF_INIT;\n    +-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    +-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    +-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    +-\t\tstrbuf_release(&auth);\n    ++\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n    ++\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\n    ++\t\t\t/*\n    ++\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n    ++\t\t\t * upon debugging, it has been found that it is capable of detecting\n    ++\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n    ++\t\t\t */\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n    ++\t\t} else {\n    ++\t\t\tstruct strbuf auth = STRBUF_INIT;\n    ++\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    ++\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    ++\t\t\tstrbuf_release(&auth);\n    ++\t\t}\n      \t}\n    -\n    - ## t/t1517-outside-repo.sh ##\n    -@@ t/t1517-outside-repo.sh: test_expect_success 'imap-send outside repository' '\n    - \ttest_config_global imap.host imaps://localhost &&\n    - \ttest_config_global imap.folder Drafts &&\n    - \n    --\techo nothing to send >expect &&\n    -+\techo Nothing to send. >expect &&\n    - \ttest_must_fail git imap-send -v </dev/null 2>actual &&\n    - \ttest_cmp expect actual &&\n      \n    + \tif (!srvc->use_ssl)\n -:  ---------- >  3:  3a0be43838 imap-send: add PLAIN authentication method to OpenSSL\n -:  ---------- >  4:  45f5b3f1ff imap-send: fix memory leak in case auth_cram_md5 fails\n -:  ---------- >  5:  8899f686d7 imap-send: enable specifying the folder using the command line\n -:  ---------- >  6:  c2dfd0178c imap-send: fix minor mistakes in the logs\n 2:  e436a12198 =  7:  4e1b51acd5 imap-send: display port alongwith host when git credential is invoked\n 3:  5183253004 =  8:  85c40d8491 imap-send: display the destination mailbox when sending a message\n 4:  c33469a5db !  9:  5e24c6cde8 imap-send: add ability to list the available folders\n    @@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n     +{\n     +\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n     +\tif (!ctx) {\n    -+\t\tfprintf(stderr, \"Failed to connect to IMAP server.\\n\");\n    ++\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n     +\t\treturn 1;\n     +\t}\n     +\n     +\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n     +\t/* Issue the LIST command and print the results */\n     +\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n    -+\t\tfprintf(stderr, \"Failed to list folders.\\n\");\n    ++\t\tfprintf(stderr, \"failed to list folders\\n\");\n     +\t\timap_close_store(ctx);\n     +\t\treturn 1;\n     +\t}\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n      \n     -\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n     -\tif (!uri_encoded_folder)\n    --\t\tdie(\"Failed to encode server folder.\");\n    +-\t\tdie(\"failed to encode server folder\");\n     -\tstrbuf_addstr(&path, uri_encoded_folder);\n     -\tcurl_free(uri_encoded_folder);\n     +\tif (!list_folders) {\n     +\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n     +\t\tif (!uri_encoded_folder)\n    -+\t\t\tdie(\"Failed to encode server folder.\");\n    ++\t\t\tdie(\"failed to encode server folder\");\n     +\t\tstrbuf_addstr(&path, uri_encoded_folder);\n     +\t\tcurl_free(uri_encoded_folder);\n     +\t}\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\tserver.port = server.use_ssl ? 993 : 143;\n      \n     -\tif (!server.folder) {\n    --\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    +-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n     -\t\tret = 1;\n     -\t\tgoto out;\n     -\t}\n      \tif (!server.host) {\n      \t\tif (!server.tunnel) {\n    - \t\t\tfprintf(stderr, \"No IMAP host specified.\\n\");\n    + \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\tserver.host = xstrdup(\"tunnel\");\n      \t}\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n     +\t}\n     +\n     +\tif (!server.folder) {\n    -+\t\tfprintf(stderr, \"No IMAP store specified.\\n\");\n    ++\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n     +\t\tret = 1;\n     +\t\tgoto out;\n     +\t}\n     +\n      \t/* read the messages */\n      \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n    - \t\terror_errno(_(\"Could not read from stdin.\"));\n    + \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519338","messageId":"20250601083821.2440110-3-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:51Z","receivedAt":"2025-06-01T08:39:00Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++-\n imap-send.c                      | 179 +++++++++++++++++++++++++++++--\n 3 files changed, 218 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..fef6487293 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext LOGIN command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..08ecb1e829 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you can generate\n+an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create it.\n+Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n+or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n+passwords, and also does not enforce the need of having multi-factor authentication.\n+You will have to use an OAuth2.0 access token in place of your password when using this\n+authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..4f3a1fb5b1 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +961,20 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+static char *oauthbearer_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n+static char *xoauth2_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -913,6 +993,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1104,6 +1224,36 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1555,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1577,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519339","messageId":"20250601083821.2440110-7-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 6/9] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:55Z","receivedAt":"2025-06-01T08:39:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex cfa335b647..97e7fb197f 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -1053,7 +1053,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1152,7 +1152,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1171,7 +1171,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1209,7 +1209,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1235,7 +1235,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1338,12 +1338,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1599,7 +1599,7 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1708,7 +1708,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1792,13 +1792,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1820,7 +1820,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519340","messageId":"20250601083821.2440110-2-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 1/9] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:50Z","receivedAt":"2025-06-01T08:39:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519341","messageId":"20250601083821.2440110-4-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:52Z","receivedAt":"2025-06-01T08:39:03Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 81 +++++++++++++++++++++++++++++++++-\n 2 files changed, 82 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex fef6487293..24e88228d0 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n-\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext LOGIN command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 4f3a1fb5b1..bc26abd150 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -953,6 +990,13 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \n #else\n \n+static char *plain_base64(const char *user UNUSED,\n+\t\t  const char *access_token UNUSED)\n+{\n+\tdie(\"You are trying to use PLAIN authenticate method \"\n+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n+}\n+\n static char *cram(const char *challenge_64 UNUSED,\n \t\t  const char *user UNUSED,\n \t\t  const char *pass UNUSED)\n@@ -977,6 +1021,26 @@ static char *xoauth2_base64(const char *user UNUSED,\n \n #endif\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1209,7 +1273,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519342","messageId":"20250601083821.2440110-8-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 7/9] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:56Z","receivedAt":"2025-06-01T08:39:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 97e7fb197f..9c3c8d8c3c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1107,7 +1107,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519343","messageId":"20250601083821.2440110-5-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 4/9] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:53Z","receivedAt":"2025-06-01T08:39:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex bc26abd150..e169c5e919 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1049,8 +1049,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519344","messageId":"20250601083821.2440110-6-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:54Z","receivedAt":"2025-06-01T08:39:05Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  5 +++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 22 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 24e88228d0..829d9e0bac 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,8 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n+\tset and `--folder` is also used, `--folder` will be preferred.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 08ecb1e829..8f221240d0 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields 'From', 'Date', and 'Subject' in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex e169c5e919..cfa335b647 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1766,6 +1768,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519345","messageId":"20250601083821.2440110-9-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 8/9] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:57Z","receivedAt":"2025-06-01T08:39:06Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 9c3c8d8c3c..3565a91ca3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1599,7 +1599,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1708,7 +1709,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519346","messageId":"20250601083821.2440110-10-gargaditya08@live.com","threadId":"63502","inReplyTo":"20250601083821.2440110-1-gargaditya08@live.com","subject":"[PATCH v11 9/9] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-01T08:38:58Z","receivedAt":"2025-06-01T08:39:08Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8f221240d0..379a371c08 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 3565a91ca3..ca95eef652 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1622,6 +1628,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1650,11 +1676,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1685,10 +1713,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1707,6 +1731,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1753,6 +1781,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1793,11 +1846,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n@@ -1807,6 +1855,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.638.g5e24c6cde8\n\n"},{"id":"519357","messageId":"xmqqtt4zvw4n.fsf@gitster.g","threadId":"63502","inReplyTo":"20250601083821.2440110-3-gargaditya08@live.com","subject":"Re: [PATCH v11 2/9] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-02T00:13:12Z","receivedAt":"2025-06-02T00:13:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> OAuth2.0 is a new way of authentication supported by various email providers\n> these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\n> for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\n> XOAUTH2 is Google's proprietary mechanism (See [3]).\n>\n> [1]: https://datatracker.ietf.org/doc/html/rfc5801\n> [2]: https://datatracker.ietf.org/doc/html/rfc7628\n> [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  Documentation/config/imap.adoc   |   5 +-\n>  Documentation/git-imap-send.adoc |  47 +++++++-\n>  imap-send.c                      | 179 +++++++++++++++++++++++++++++--\n>  3 files changed, 218 insertions(+), 13 deletions(-)\n>\n> diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\n> index 3d28f72643..fef6487293 100644\n> --- a/Documentation/config/imap.adoc\n> +++ b/Documentation/config/imap.adoc\n> @@ -40,5 +40,6 @@ imap.authMethod::\n>  \tSpecify the authentication method for authenticating with the IMAP server.\n>  \tIf Git was built with the NO_CURL option, or if your curl version is older\n>  \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n> -\toption, the only supported method is 'CRAM-MD5'. If this is not set\n> -\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n> +\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n> +\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n> +\tplaintext LOGIN command.\n> diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\n> index 26ccf4e433..08ecb1e829 100644\n> --- a/Documentation/git-imap-send.adoc\n> +++ b/Documentation/git-imap-send.adoc\n> @@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n>  \n>  ---------\n>  [imap]\n> -\tfolder = \"[Gmail]/Drafts\"\n> -\thost = imaps://imap.gmail.com\n> -\tuser = user@gmail.com\n> -\tport = 993\n> +    folder = \"[Gmail]/Drafts\"\n> +    host = imaps://imap.gmail.com\n> +    user = user@gmail.com\n> +    port = 993\n\nNice to see such an attention to the detail here.\n\n>  ---------\n>  \n> +Gmail does not allow using your regular password for `git imap-send`.\n> +If you have multi-factor authentication set up on your Gmail account, you can generate\n> +an app-specific password for use with `git imap-send`.\n> +Visit https://security.google.com/settings/security/apppasswords to create it.\n> +Alternatively, use OAuth2.0 authentication as described below.\n\nThe new lines added by this part of the documentation tends to be\noverly long but with minor rewrapping you can stay under 75 columns\nor so without too much effort.\n\n> +If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n> +or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n> +passwords, and also does not enforce the need of having multi-factor authentication.\n> +You will have to use an OAuth2.0 access token in place of your password when using this\n\nDitto.\n\n> @@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n>  interface will wrap lines no matter what, so you need to use a real\n>  IMAP client).\n>  \n> +In case you are using OAuth2.0 authentication, it is easier to use credential\n> +helpers to generate tokens. Credential helpers suggested in\n> +linkgit:git-send-email[1] can be used for `git imap-send` as well.\n> +\n>  CAUTION\n>  -------\n>  It is still your responsibility to make sure that the email message\n> diff --git a/imap-send.c b/imap-send.c\n> index 37f94a37e8..4f3a1fb5b1 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -139,7 +139,9 @@ enum CAPABILITY {\n>  \tLITERALPLUS,\n>  \tNAMESPACE,\n>  \tSTARTTLS,\n> -\tAUTH_CRAM_MD5\n> +\tAUTH_CRAM_MD5,\n> +\tAUTH_OAUTHBEARER,\n> +\tAUTH_XOAUTH2\n>  };\n\nThese days, we allow and encourage ending the last member of an enum\nwith a trailing comma (cf. Documentation/CodingGuidelines) to reduce\nfuture patch noise, just like you did ...\n\n>  static const char *cap_list[] = {\n> @@ -149,6 +151,8 @@ static const char *cap_list[] = {\n>  \t\"NAMESPACE\",\n>  \t\"STARTTLS\",\n>  \t\"AUTH=CRAM-MD5\",\n> +\t\"AUTH=OAUTHBEARER\",\n> +\t\"AUTH=XOAUTH2\",\n>  };\n\n... here for an array initializer elements.\n\n> +static char *oauthbearer_base64(const char *user UNUSED,\n> +\t\t  const char *access_token UNUSED)\n> +{\n> +\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n> +\t    \"with OpenSSL library, but its support has not been compiled in.\");\n> +}\n> +\n> +static char *xoauth2_base64(const char *user UNUSED,\n> +\t\t  const char *access_token UNUSED)\n> +{\n> +\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n> +\t    \"with OpenSSL library, but its support has not been compiled in.\");\n> +}\n> +\n>  #endif\n\nLet's keep a mental note that the lowest layer of the auth_*\nfunction can die() for the methods that are not supported.\n\n>  static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> @@ -913,6 +993,46 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n>  \treturn 0;\n>  }\n>  \n> +static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n> +{\n> +\tint ret;\n> +\tchar *b64;\n> +\n> +\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n> +\tif (!b64)\n> +\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n> +\n> +\t/* Send the base64-encoded response */\n> +\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n> +\tif (ret != (int)strlen(b64)) {\n> +\t\tfree(b64);\n> +\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n> +\t}\n> +\n> +\tfree(b64);\n> +\treturn 0;\n> +}\n> +\n> +static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n> +{\n> +\tint ret;\n> +\tchar *b64;\n> +\n> +\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n> +\tif (!b64)\n> +\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n> +\n> +\t/* Send the base64-encoded response */\n> +\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n> +\tif (ret != (int)strlen(b64)) {\n> +\t\tfree(b64);\n> +\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n> +\t}\n> +\n> +\tfree(b64);\n> +\treturn 0;\n> +}\n\nIt feels very strange to see auth_xoauth2() defined unconditionally\nand leave xoauth2_base64() to die when built without OpenSSL.\nExactly the same comment applies to auth_oauthbearer() vs\noauthbearer_base64(), and auth_cram_md5() vs cram().\n\nThe reason why it looks strange to me is that I suspect that we'd\nend up with an inconsistent behaviour like we see below.\n\nFor example, here, when we at runtime detect that ...\n\n> +\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n> +\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n> +\t\t\t\t\tfprintf(stderr, \"You specified \"\n> +\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n> +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n> +\t\t\t\t\tgoto bail;\n\n... the other end of the connection does not support the method the\nend user specified, we gracefully fail like so, but we do not even\nbother detecting at runtime that _we_ do not support the method the\nend user specified, until ...\n\n> +\t\t\t\t}\n> +\t\t\t\t/* OAUTHBEARER */\n> +\n> +\t\t\t\tmemset(&cb, 0, sizeof(cb));\n> +\t\t\t\tcb.cont = auth_oauthbearer;\n\n... this callback function, which is a stub when we do not support\nthe method, gets called ...\n\n> +\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n\n... here inside imap_exec().  It is probably no use that the\nimap_exec() call is prepared to catch an error, as the unimplemented\nmethod would call die() as we saw above.\n\n> +\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n> +\t\t\t\t\tgoto bail;\n> +\t\t\t\t}\n\nInstead of making the lowest level helpers like cram(),\noauthbearer_base64(), and xoauth2_base64() conditionally be stubs\nthat die(), wouldn't it make more sense to conditionally define\nhelpers one lebel higher, i.e. those you stuff in cb.cont, only when\nthe user permits Git to be linked with OpenSSL, e.g.\n\n#ifdef OpenSSL\nstatic int auth_oauthbearer(struct imap_store *ctx, const char *p UNUSED)\n{\n\t...\n}\n#else /* !OpenSSL */\n#define auth_oauthbearer NULL\n#endif\n\nand then write this part of the code more like this?\n\n\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n\t\t\t\t... the other side does not support ...\n\t\t\t\tgoto bail;\n\t\t\t}\n\t\t\tif (!auth_oauthbearer) {\n\t\t\t\t... we do not support ...\n\t\t\t\tgoto bail;\n\t\t\t}\n\t\t\tmemset(&cb, 0, sizeof(cb));\n\t\t\tcb.cont = auth_oauthbearer;\n\t\t\tif (imap_exec(ctx, &cb, ...) != RESP_OK) {\n\t\t\t\t... we both support but we failed ...\n\t\t\t\tgoto bail;\n\t\t\t}\n\nExactly the same comments appli to other methods.\n\nIncluding \"CRAM-MD5\", that is.  That one may have been iffy before\nyou started touching this file, but it is not a good excuse for\nadding two similarly bad code in the patch series.\n"},{"id":"519358","messageId":"xmqqldqbvvgu.fsf@gitster.g","threadId":"63502","inReplyTo":"20250601083821.2440110-4-gargaditya08@live.com","subject":"Re: [PATCH v11 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-02T00:27:29Z","receivedAt":"2025-06-02T00:27:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n>  #else\n>  \n> +static char *plain_base64(const char *user UNUSED,\n> +\t\t  const char *access_token UNUSED)\n> +{\n> +\tdie(\"You are trying to use PLAIN authenticate method \"\n> +\t    \"with OpenSSL library, but its support has not been compiled in.\");\n> +}\n\nThis comment may apply also to the earlier OAuth related two stub\nfunctions, but this is the \"#else\" side of \"#ifndef NO_OPENSSL\";\ndouble negation always makes all our spin, but in short, this is\n\"You are not building with OpenSSL\".  We cannot quite look at the\npost context of this hunk for sanity check, but inside the cram()\nstub function ...\n\n>  static char *cram(const char *challenge_64 UNUSED,\n>  \t\t  const char *user UNUSED,\n>  \t\t  const char *pass UNUSED)\n\n\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n\t    \"you have to build git-imap-send with OpenSSL library.\");\n\n... is the message it dies with.  So, shouldn't the error from the\nnew stub function also say \"If you want to use PLAIN, you have to\nbuild with OpenSSL\"?\n\n> +static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n> +{\n> +\tint ret;\n> +\tchar *b64;\n> +\n> +\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n> +\tif (!b64)\n> +\t\treturn error(\"PLAIN: base64 encoding failed\");\n> +\n> +\t/* Send the base64-encoded response */\n> +\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n> +\tif (ret != (int)strlen(b64)) {\n> +\t\tfree(b64);\n> +\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n> +\t}\n> +\n> +\tfree(b64);\n> +\treturn 0;\n> +}\n\nAnd the same comment about not gracefully failing when our side lack\nsupport, even though we gracefully fail when the other side lacks\nsupport, given to an earlier step also applies here.\n\n> @@ -1209,7 +1273,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n>  \t\tif (srvc->auth_method) {\n>  \t\t\tstruct imap_cmd_cb cb;\n>  \n> -\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n> +\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n> +\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n> +\t\t\t\t\tfprintf(stderr, \"You specified \"\n> +\t\t\t\t\t\t\"PLAIN as authentication method, \"\n> +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n> +\t\t\t\t\tgoto bail;\n> +\t\t\t\t}\n> +\t\t\t\t/* PLAIN */\n> +\n> +\t\t\t\tmemset(&cb, 0, sizeof(cb));\n> +\t\t\t\tcb.cont = auth_plain;\n> +\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n> +\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n> +\t\t\t\t\tgoto bail;\n> +\t\t\t\t}\n> +\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n>  \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n>  \t\t\t\t\tfprintf(stderr, \"You specified \"\n>  \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n"},{"id":"519359","messageId":"xmqqfrgjvuw8.fsf@gitster.g","threadId":"63502","inReplyTo":"20250601083821.2440110-6-gargaditya08@live.com","subject":"Re: [PATCH v11 5/9] imap-send: enable specifying the folder using the command line","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-02T00:39:51Z","receivedAt":"2025-06-02T00:39:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Some users may very often want to imap-send messages to a folder\n> other than the default set in the config. Add a command line\n> argument for the same.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  Documentation/config/imap.adoc   |  5 +++--\n>  Documentation/git-imap-send.adoc | 15 +++++++++++----\n>  imap-send.c                      |  9 ++++++++-\n>  3 files changed, 22 insertions(+), 7 deletions(-)\n>\n> diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\n> index 24e88228d0..829d9e0bac 100644\n> --- a/Documentation/config/imap.adoc\n> +++ b/Documentation/config/imap.adoc\n> @@ -1,7 +1,8 @@\n>  imap.folder::\n>  \tThe folder to drop the mails into, which is typically the Drafts\n> -\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n> -\t\"[Gmail]/Drafts\". Required.\n> +\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n> +\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n> +\tset and `--folder` is also used, `--folder` will be preferred.\n\nShouldn't these literals be `typeset like this` with backquotes?\n\nMore importantly, when we mention that the command line option\ntrumps the corresponding configuration variable, the more common\nverb we use than \"prefer\" is \"override\".  Because it is a general\nrule that the configuration variable is used as a back-up in case\nthere is no command line option is given, it is less confusing if\nyou omitted the last sentence.  Perhaps rewrite the last two\nsentence with something like this?\n\n\tThe IMAP folder to interact with MUST be specified; the\n\tvalue of this configuration variable is used as the fallback\n\tdefault value when the `--folder` option is not given.\n\nI dunno.\n\n> @@ -37,6 +39,11 @@ OPTIONS\n>  --quiet::\n>  \tBe quiet.\n>  \n> +-f <folder>::\n> +--folder=<folder>::\n> +\tSpecify the folder in which the emails have to saved.\n> +\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n> +\n>  --curl::\n>  \tUse libcurl to communicate with the IMAP server, unless tunneling\n>  \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n\nThere are four existing options and this adds another.  I am\ndebating myself if this deserves a preliminary clean-up patch so\nthat the enumerated options are more like\n\n\t`-v`::\n\t`--verbose`::\n\t\tBe verbose.\n\nIf we did so, this patch can add\n\n\t`-f` _<folder>_::\n\t`--folder=<folder>`::\n\t\tSpecify the folder to save the e-mails in.\n\t\tRequired.  Defaults to the value of the `imap.folder`\n\t\tconfiguration variable\n\nwithout worrying about it not following the prevailing (and stale)\nstyle.\n\nIf we are not doing a preliminary clean-up patch, what you sent is\nmore in line.  We'll leave the clean-up to somebody else and adding\none new option in a stale style to 4 existing ones may not be too\nbad.  At least such an intermediate state is locally consistent.\n\nThanks.\n\n\n"},{"id":"519360","messageId":"xmqq8qmbvurr.fsf@gitster.g","threadId":"63502","inReplyTo":"20250601083821.2440110-7-gargaditya08@live.com","subject":"Re: [PATCH v11 6/9] imap-send: fix minor mistakes in the logs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-02T00:42:32Z","receivedAt":"2025-06-02T00:42:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Some minor mistakes have been found in the logs. Most of them include\n> error messages starting with a capital letter, and ending with a period.\n> Also, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  imap-send.c | 28 ++++++++++++++--------------\n>  1 file changed, 14 insertions(+), 14 deletions(-)\n\nQuite honestly, I am not sure if this churn is worth it.\n\nUnless we are moving to the same error reporting mechanism more\nprevalently used elsewhere in our codebase and consistenly use the\nerror() function instead of calling fprintf().\n\n\n"},{"id":"519361","messageId":"xmqq4iwzvuqn.fsf@gitster.g","threadId":"63502","inReplyTo":"20250601083821.2440110-9-gargaditya08@live.com","subject":"Re: [PATCH v11 8/9] imap-send: display the destination mailbox when sending a message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-02T00:43:12Z","receivedAt":"2025-06-02T00:43:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Whenever we sent a message using the `imap-send` command, it would\n> display a log showing the number of messages which are to be sent.\n> For example:\n>\n>     Sending 1 message\n>      100% (1/1) done\n>\n> This had been made more informative by adding the name of the destination\n> folder as well:\n>\n>     Sending 1 message to Drafts folder...\n\nNice ;-)\n\n>      100% (1/1) done\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  imap-send.c | 6 ++++--\n>  1 file changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/imap-send.c b/imap-send.c\n> index 9c3c8d8c3c..3565a91ca3 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1599,7 +1599,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n>  \t}\n>  \tctx->name = server->folder;\n>  \n> -\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n> +\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n> +\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n>  \twhile (1) {\n>  \t\tunsigned percent = n * 100 / total;\n>  \n> @@ -1708,7 +1709,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n>  \tcurl = setup_curl(server, &cred);\n>  \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n>  \n> -\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n> +\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n> +\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n>  \twhile (1) {\n>  \t\tunsigned percent = n * 100 / total;\n>  \t\tint prev_len;\n"},{"id":"519366","messageId":"PN3PR01MB9597F8C2F0A8F38225B9CF96B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqq8qmbvurr.fsf@gitster.g","subject":"Re: [PATCH v11 6/9] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T03:41:42Z","receivedAt":"2025-06-02T03:41:47Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 2 Jun 2025, at 6:12 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Some minor mistakes have been found in the logs. Most of them include\n>> error messages starting with a capital letter, and ending with a period.\n>> Also, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> imap-send.c | 28 ++++++++++++++--------------\n>> 1 file changed, 14 insertions(+), 14 deletions(-)\n> \n> Quite honestly, I am not sure if this churn is worth it.\n\nIt was a significantly long patch before Eric's review, even I was thinking\nabout dropping it. Then I saw the amount of conflicts it will cause .......\n\nI think its just fine to keep it, its not causing anything degrading.\n"},{"id":"519367","messageId":"PN3PR01MB95975D2159C4D6B4D111910AB862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqfrgjvuw8.fsf@gitster.g","subject":"Re: [PATCH v11 5/9] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T03:45:48Z","receivedAt":"2025-06-02T03:45:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 2 Jun 2025, at 6:10 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Some users may very often want to imap-send messages to a folder\n>> other than the default set in the config. Add a command line\n>> argument for the same.\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> Documentation/config/imap.adoc   |  5 +++--\n>> Documentation/git-imap-send.adoc | 15 +++++++++++----\n>> imap-send.c                      |  9 ++++++++-\n>> 3 files changed, 22 insertions(+), 7 deletions(-)\n>> \n>> diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\n>> index 24e88228d0..829d9e0bac 100644\n>> --- a/Documentation/config/imap.adoc\n>> +++ b/Documentation/config/imap.adoc\n>> @@ -1,7 +1,8 @@\n>> imap.folder::\n>>    The folder to drop the mails into, which is typically the Drafts\n>> -    folder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n>> -    \"[Gmail]/Drafts\". Required.\n>> +    folder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n>> +    '[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n>> +    set and `--folder` is also used, `--folder` will be preferred.\n> \n> Shouldn't these literals be `typeset like this` with backquotes?\n> \n> More importantly, when we mention that the command line option\n> trumps the corresponding configuration variable, the more common\n> verb we use than \"prefer\" is \"override\".  Because it is a general\n> rule that the configuration variable is used as a back-up in case\n> there is no command line option is given, it is less confusing if\n> you omitted the last sentence.  Perhaps rewrite the last two\n> sentence with something like this?\n> \n>    The IMAP folder to interact with MUST be specified; the\n>    value of this configuration variable is used as the fallback\n>    default value when the `--folder` option is not given.\n\nOk\n> \n> I dunno.\n> \n>> @@ -37,6 +39,11 @@ OPTIONS\n>> --quiet::\n>>    Be quiet.\n>> \n>> +-f <folder>::\n>> +--folder=<folder>::\n>> +    Specify the folder in which the emails have to saved.\n>> +    For example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n>> +\n>> --curl::\n>>    Use libcurl to communicate with the IMAP server, unless tunneling\n>>    into it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n> \n> There are four existing options and this adds another.  I am\n> debating myself if this deserves a preliminary clean-up patch so\n> that the enumerated options are more like\n> \n>    `-v`::\n>    `--verbose`::\n>        Be verbose.\n> \n> If we did so, this patch can add\n> \n>    `-f` _<folder>_::\n>    `--folder=<folder>`::\n>        Specify the folder to save the e-mails in.\n>        Required.  Defaults to the value of the `imap.folder`\n>        configuration variable\n> \n> without worrying about it not following the prevailing (and stale)\n> style.\n> \n> If we are not doing a preliminary clean-up patch, what you sent is\n> more in line.  We'll leave the clean-up to somebody else and adding\n> one new option in a stale style to 4 existing ones may not be too\n> bad.  At least such an intermediate state is locally consistent.\n\nLet's keep this style change for some other patch series, since many\nother docs also would need this change.\n\n"},{"id":"519368","messageId":"PN3PR01MB95972182C2CDCA1BDA80B0F4B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqldqbvvgu.fsf@gitster.g","subject":"Re: [PATCH v11 3/9] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T04:01:29Z","receivedAt":"2025-06-02T04:01:34Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 2 Jun 2025, at 5:57 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> #else\n>> \n>> +static char *plain_base64(const char *user UNUSED,\n>> +          const char *access_token UNUSED)\n>> +{\n>> +    die(\"You are trying to use PLAIN authenticate method \"\n>> +        \"with OpenSSL library, but its support has not been compiled in.\");\n>> +}\n> \n> This comment may apply also to the earlier OAuth related two stub\n> functions, but this is the \"#else\" side of \"#ifndef NO_OPENSSL\";\n> double negation always makes all our spin, but in short, this is\n> \"You are not building with OpenSSL\".  We cannot quite look at the\n> post context of this hunk for sanity check, but inside the cram()\n> stub function ...\n> \n>> static char *cram(const char *challenge_64 UNUSED,\n>>          const char *user UNUSED,\n>>          const char *pass UNUSED)\n> \n>    die(\"If you want to use CRAM-MD5 authenticate method, \"\n>        \"you have to build git-imap-send with OpenSSL library.\");\n> \n> ... is the message it dies with.  So, shouldn't the error from the\n> new stub function also say \"If you want to use PLAIN, you have to\n> build with OpenSSL\"?\n\nNo, there is a difference here. CRAM-MD5 works ONLY with OpenSSL.\nOAuth2 and PLAIN work with BOTH OpenSSL and libcurl.\n\nAnyways, taking comments from the OAuth2.0 reply, let me see if I can\nremove these statements.\n\n>> +static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n>> +{\n>> +    int ret;\n>> +    char *b64;\n>> +\n>> +    b64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n>> +    if (!b64)\n>> +        return error(\"PLAIN: base64 encoding failed\");\n>> +\n>> +    /* Send the base64-encoded response */\n>> +    ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n>> +    if (ret != (int)strlen(b64)) {\n>> +        free(b64);\n>> +        return error(\"IMAP error: sending PLAIN response failed\");\n>> +    }\n>> +\n>> +    free(b64);\n>> +    return 0;\n>> +}\n> \n> And the same comment about not gracefully failing when our side lack\n> support, even though we gracefully fail when the other side lacks\n> support, given to an earlier step also applies here.\n> \n>> @@ -1209,7 +1273,22 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n>>        if (srvc->auth_method) {\n>>            struct imap_cmd_cb cb;\n>> \n>> -            if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n>> +            if (!strcmp(srvc->auth_method, \"PLAIN\")) {\n>> +                if (!CAP(AUTH_PLAIN)) {\n>> +                    fprintf(stderr, \"You specified \"\n>> +                        \"PLAIN as authentication method, \"\n>> +                        \"but %s doesn't support it.\\n\", srvc->host);\n>> +                    goto bail;\n>> +                }\n>> +                /* PLAIN */\n>> +\n>> +                memset(&cb, 0, sizeof(cb));\n>> +                cb.cont = auth_plain;\n>> +                if (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n>> +                    fprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n>> +                    goto bail;\n>> +                }\n>> +            } else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n>>                if (!CAP(AUTH_CRAM_MD5)) {\n>>                    fprintf(stderr, \"You specified \"\n>>                        \"CRAM-MD5 as authentication method, \"\n"},{"id":"519428","messageId":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:31Z","receivedAt":"2025-06-02T11:00:28Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix minor mistakes in the logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 425 +++++++++++++++++++++++++++----\n 3 files changed, 441 insertions(+), 63 deletions(-)\n\nRange-diff against v11:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  02037873a1 !  2:  ab12f713d2 imap-send: add support for OAuth2.0 authentication\n    @@ Documentation/config/imap.adoc: imap.authMethod::\n      \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n     -\toption, the only supported method is 'CRAM-MD5'. If this is not set\n     -\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n    -+\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n    -+\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    -+\tplaintext LOGIN command.\n    ++\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n    ++\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n    ++\tplaintext `LOGIN` command.\n     \n      ## Documentation/git-imap-send.adoc ##\n     @@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n    @@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n      ---------\n      \n     +Gmail does not allow using your regular password for `git imap-send`.\n    -+If you have multi-factor authentication set up on your Gmail account, you can generate\n    -+an app-specific password for use with `git imap-send`.\n    -+Visit https://security.google.com/settings/security/apppasswords to create it.\n    -+Alternatively, use OAuth2.0 authentication as described below.\n    ++If you have multi-factor authentication set up on your Gmail account, you\n    ++can generate an app-specific password for use with `git imap-send`.\n    ++Visit https://security.google.com/settings/security/apppasswords to create\n    ++it. Alternatively, use OAuth2.0 authentication as described below.\n     +\n      [NOTE]\n      You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    @@ Documentation/git-imap-send.adoc: that the \"Folder doesn't exist\".\n      If your Gmail account is set to another language than English, the name of the \"Drafts\"\n      folder will be localized.\n      \n    -+If you want to use OAuth2.0 based authentication, you can specify `OAUTHBEARER`\n    -+or `XOAUTH2` mechanism in your config. It is more secure than using app-specific\n    -+passwords, and also does not enforce the need of having multi-factor authentication.\n    -+You will have to use an OAuth2.0 access token in place of your password when using this\n    -+authentication.\n    ++If you want to use OAuth2.0 based authentication, you can specify\n    ++`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n    ++than using app-specific passwords, and also does not enforce the need of\n    ++having multi-factor authentication. You will have to use an OAuth2.0\n    ++access token in place of your password when using this authentication.\n     +\n     +---------\n     +[imap]\n    @@ imap-send.c: enum CAPABILITY {\n     -\tAUTH_CRAM_MD5\n     +\tAUTH_CRAM_MD5,\n     +\tAUTH_OAUTHBEARER,\n    -+\tAUTH_XOAUTH2\n    ++\tAUTH_XOAUTH2,\n      };\n      \n      static const char *cap_list[] = {\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\treturn b64;\n     +}\n     +\n    - #else\n    - \n    - static char *cram(const char *challenge_64 UNUSED,\n    -@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    - \t    \"you have to build git-imap-send with OpenSSL library.\");\n    - }\n    - \n    -+static char *oauthbearer_base64(const char *user UNUSED,\n    -+\t\t  const char *access_token UNUSED)\n    -+{\n    -+\tdie(\"You are trying to use OAUTHBEARER authenticate method \"\n    -+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n    -+}\n    -+\n    -+static char *xoauth2_base64(const char *user UNUSED,\n    -+\t\t  const char *access_token UNUSED)\n    -+{\n    -+\tdie(\"You are trying to use XOAUTH2 authenticate method \"\n    -+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n    -+}\n    -+\n    - #endif\n    - \n    - static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    -@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    - \treturn 0;\n    - }\n    - \n     +static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n     +{\n     +\tint ret;\n    @@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt\n     +\treturn 0;\n     +}\n     +\n    - static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n    - {\n    - \tif (srvc->user && srvc->pass)\n    + #else\n    + \n    + static char *cram(const char *challenge_64 UNUSED,\n    +@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    + \t    \"you have to build git-imap-send with OpenSSL library.\");\n    + }\n    + \n    ++#define auth_oauthbearer NULL\n    ++#define auth_xoauth2 NULL\n    ++\n    + #endif\n    + \n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n      \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n      \t\t\t\t\tgoto bail;\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n     +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n     +\t\t\t\t\tgoto bail;\n     +\t\t\t\t}\n    ++\n    ++\t\t\t\t#ifdef NO_OPENSSL\n    ++\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n    ++\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    ++\t\t\t\tgoto bail;\n    ++\t\t\t\t#endif\n    ++\n     +\t\t\t\t/* OAUTHBEARER */\n     +\n     +\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n     +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n     +\t\t\t\t\tgoto bail;\n     +\t\t\t\t}\n    ++\n    ++\t\t\t\t#ifdef NO_OPENSSL\n    ++\t\t\t\tfprintf(stderr, \"You are trying to use XOAUTH2 authentication mechanism \"\n    ++\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    ++\t\t\t\tgoto bail;\n    ++\t\t\t\t#endif\n    ++\n     +\t\t\t\t/* XOAUTH2 */\n     +\n     +\t\t\t\tmemset(&cb, 0, sizeof(cb));\n 2:  3a0be43838 !  3:  ba9c3fb756 imap-send: add PLAIN authentication method to OpenSSL\n    @@ Documentation/config/imap.adoc: imap.authMethod::\n      \tSpecify the authentication method for authenticating with the IMAP server.\n      \tIf Git was built with the NO_CURL option, or if your curl version is older\n      \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n    --\toption, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and\n    --\t'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    -+\toption, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER'\n    -+\tand 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP\n    - \tplaintext LOGIN command.\n    +-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n    +-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n    ++\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n    ++\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n    + \tplaintext `LOGIN` command.\n     \n      ## imap-send.c ##\n     @@ imap-send.c: enum CAPABILITY {\n    @@ imap-send.c: enum CAPABILITY {\n     +\tAUTH_PLAIN,\n      \tAUTH_CRAM_MD5,\n      \tAUTH_OAUTHBEARER,\n    - \tAUTH_XOAUTH2\n    + \tAUTH_XOAUTH2,\n     @@ imap-send.c: static const char *cap_list[] = {\n      \t\"LITERAL+\",\n      \t\"NAMESPACE\",\n    @@ imap-send.c: static char hexchar(unsigned int b)\n      {\n      \tint i, resp_len, encoded_len, decoded_len;\n     @@ imap-send.c: static char *xoauth2_base64(const char *user, const char *access_token)\n    - \n    - #else\n    - \n    -+static char *plain_base64(const char *user UNUSED,\n    -+\t\t  const char *access_token UNUSED)\n    -+{\n    -+\tdie(\"You are trying to use PLAIN authenticate method \"\n    -+\t    \"with OpenSSL library, but its support has not been compiled in.\");\n    -+}\n    -+\n    - static char *cram(const char *challenge_64 UNUSED,\n    - \t\t  const char *user UNUSED,\n    - \t\t  const char *pass UNUSED)\n    -@@ imap-send.c: static char *xoauth2_base64(const char *user UNUSED,\n    - \n    - #endif\n    + \treturn b64;\n    + }\n      \n     +static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n     +{\n    @@ imap-send.c: static char *xoauth2_base64(const char *user UNUSED,\n     +\treturn 0;\n     +}\n     +\n    - static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n      {\n      \tint ret;\n    +@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    + \t    \"you have to build git-imap-send with OpenSSL library.\");\n    + }\n    + \n    ++#define auth_plain NULL\n    + #define auth_oauthbearer NULL\n    + #define auth_xoauth2 NULL\n    + \n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n      \t\tif (srvc->auth_method) {\n      \t\t\tstruct imap_cmd_cb cb;\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n     +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n     +\t\t\t\t\tgoto bail;\n     +\t\t\t\t}\n    ++\n    ++\t\t\t\t#ifdef NO_OPENSSL\n    ++\t\t\t\tfprintf(stderr, \"You are trying to use PLAIN authentication mechanism \"\n    ++\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    ++\t\t\t\tgoto bail;\n    ++\t\t\t\t#endif\n    ++\n     +\t\t\t\t/* PLAIN */\n     +\n     +\t\t\t\tmemset(&cb, 0, sizeof(cb));\n 3:  45f5b3f1ff =  4:  3d1a66da57 imap-send: fix memory leak in case auth_cram_md5 fails\n -:  ---------- >  5:  70bb9388b8 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n 4:  8899f686d7 !  6:  0d00a5e135 imap-send: enable specifying the folder using the command line\n    @@ Documentation/config/imap.adoc\n      \tThe folder to drop the mails into, which is typically the Drafts\n     -\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n     -\t\"[Gmail]/Drafts\". Required.\n    -+\tfolder. For example: 'INBOX.Drafts', 'INBOX/Drafts' or\n    -+\t'[Gmail]/Drafts'. Required if `--folder` argument is not used. If\n    -+\tset and `--folder` is also used, `--folder` will be preferred.\n    ++\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n    ++\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n    ++\tthe value of this configuration variable is used as the fallback\n    ++\tdefault value when the `--folder` option is not given.\n      \n      imap.tunnel::\n      \tCommand used to set up a tunnel to the IMAP server through which\n    @@ Documentation/git-imap-send.adoc: git-imap-send - Send a collection of patches f\n      other email is when using mail clients that cannot read mailbox\n      files directly. The command also works with any general mailbox\n     -in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n    -+in which emails have the fields 'From', 'Date', and 'Subject' in\n    ++in which emails have the fields `From`, `Date`, and `Subject` in\n      that order.\n      \n      Typical usage is something like:\n 5:  c2dfd0178c =  7:  999c65438f imap-send: fix minor mistakes in the logs\n 6:  4e1b51acd5 =  8:  d0315aebd4 imap-send: display port alongwith host when git credential is invoked\n 7:  85c40d8491 =  9:  73352a18cf imap-send: display the destination mailbox when sending a message\n 8:  5e24c6cde8 ! 10:  36d50d01f0 imap-send: add ability to list the available folders\n    @@ Documentation/git-imap-send.adoc: OPTIONS\n      \n      CONFIGURATION\n      -------------\n    -@@ Documentation/git-imap-send.adoc: Alternatively, use OAuth2.0 authentication as described below.\n    +@@ Documentation/git-imap-send.adoc: it. Alternatively, use OAuth2.0 authentication as described below.\n      \n      [NOTE]\n      You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519429","messageId":"PN3PR01MB9597A278677355CC3D65443AB862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:32Z","receivedAt":"2025-06-02T11:00:32Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519430","messageId":"PN3PR01MB9597D1C148578224A02B9773B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:33Z","receivedAt":"2025-06-02T11:00:34Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++-\n imap-send.c                      | 182 +++++++++++++++++++++++++++++--\n 3 files changed, 221 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..37a8b48ea2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,108 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +1001,9 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -1104,6 +1213,50 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use XOAUTH2 authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1580,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519431","messageId":"PN3PR01MB959791DDE729A568B62BE4D8B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 04/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:35Z","receivedAt":"2025-06-02T11:00:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 67077c2bd2..5f31dad3b0 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1072,8 +1072,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519432","messageId":"PN3PR01MB959713A2536AD407D15CE632B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:36Z","receivedAt":"2025-06-02T11:00:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 52 ++++++++++++++++++++++++++--------------------------\n 1 file changed, 26 insertions(+), 26 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 5f31dad3b0..879c72a606 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1008,6 +1008,24 @@ static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n \treturn 0;\n }\n \n+static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n+{\n+\tint ret;\n+\tchar *response;\n+\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n+\n+\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n+\t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n+\n+\tfree(response);\n+\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1050,38 +1068,13 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n #define auth_plain NULL\n+#define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n #endif\n \n-static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n-{\n-\tint ret;\n-\tchar *response;\n-\n-\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n-\n-\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response)) {\n-\t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n-\t}\n-\n-\tfree(response);\n-\n-\treturn 0;\n-}\n-\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1287,6 +1280,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"If you want to use CRAM-MD5 authentication mechanism, \"\n+\t\t\t\t\t\"you have to build git-imap-send with OpenSSL library.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n \t\t\t\t/* CRAM-MD5 */\n \n \t\t\t\tmemset(&cb, 0, sizeof(cb));\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519433","messageId":"PN3PR01MB95977FE5E93334FA30C704EBB862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 03/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:34Z","receivedAt":"2025-06-02T11:00:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 82 +++++++++++++++++++++++++++++++++-\n 2 files changed, 83 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 37a8b48ea2..67077c2bd2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1001,6 +1058,7 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_plain NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n@@ -1198,7 +1256,29 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use PLAIN authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519434","messageId":"PN3PR01MB9597041744A834D1794E9775B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 07/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:38Z","receivedAt":"2025-06-02T11:00:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 0e33baca7d..3a1940e4a4 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -1020,7 +1020,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1128,7 +1128,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1147,7 +1147,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1185,7 +1185,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1199,7 +1199,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1211,7 +1211,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1342,12 +1342,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1603,7 +1603,7 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1712,7 +1712,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1796,13 +1796,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1824,7 +1824,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519435","messageId":"PN3PR01MB9597F8EAACCE5E8879391536B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:37Z","receivedAt":"2025-06-02T11:00:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 879c72a606..0e33baca7d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1770,6 +1772,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519436","messageId":"PN3PR01MB9597A414C65979F459895172B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:39Z","receivedAt":"2025-06-02T11:00:43Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3a1940e4a4..61d52878c9 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1083,7 +1083,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519437","messageId":"PN3PR01MB95979FD5B527675DFC036B74B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:40Z","receivedAt":"2025-06-02T11:00:44Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 61d52878c9..39a42e6bc8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1603,7 +1603,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1712,7 +1713,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519438","messageId":"PN3PR01MB95973C7522A3E9EF20A2DACAB862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D3BADD7CDE568825A2D0B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v12 10/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-02T10:59:41Z","receivedAt":"2025-06-02T11:00:45Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 39a42e6bc8..f6049222fd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1626,6 +1632,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1654,11 +1680,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1689,10 +1717,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1711,6 +1735,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1757,6 +1785,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1797,11 +1850,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n@@ -1811,6 +1859,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.639.g36d50d01f0\n\n"},{"id":"519734","messageId":"20250605080002.GA2998537@coredump.intra.peff.net","threadId":"63502","inReplyTo":"PN3PR01MB9597D1C148578224A02B9773B862A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-06-05T08:00:02Z","receivedAt":"2025-06-05T08:00:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n\n> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n>  \n>  \tserver_fill_credential(srvc, cred);\n>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> +\n> +\tif (!srvc->auth_method ||\n> +\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n\nCoverity complains that this \"if\" will always be true, since one of the\nstrcmp() calls must return non-zero (srvc->auth_method cannot match both\nstrings!).\n\nI'm not sure what the logic is supposed to be here. If we are matching\neither string, it should be !strcmp() for both. If we want to match\nneither, then it should be &&, not ||.\n\n-Peff\n"},{"id":"519736","messageId":"PN3PR01MB9597EA7301052F34B6FE3E48B86FA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"20250605080002.GA2998537@coredump.intra.peff.net","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:12:53Z","receivedAt":"2025-06-05T08:12:58Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 5 Jun 2025, at 1:30 PM, Jeff King <peff@peff.net> wrote:\n> \n> ﻿On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n> \n>> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n>> \n>>    server_fill_credential(srvc, cred);\n>>    curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>> -    curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>> +\n>> +    if (!srvc->auth_method ||\n>> +        strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>> +        strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>> +        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> \n> Coverity complains that this \"if\" will always be true, since one of the\n> strcmp() calls must return non-zero (srvc->auth_method cannot match both\n> strings!).\n> \n> I'm not sure what the logic is supposed to be here. If we are matching\n> either string, it should be !strcmp() for both. If we want to match\n> neither, then it should be &&, not ||.\n\nGood catch. The aim was to not execute that statement if authentication is\nXOAUTH2 or OAUTHBEARER. I'll fix this logic.\n"},{"id":"519743","messageId":"cover.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v13 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:23Z","receivedAt":"2025-06-05T08:42:28Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix minor mistakes in the logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 425 +++++++++++++++++++++++++++----\n 3 files changed, 441 insertions(+), 63 deletions(-)\n\nRange-diff against v12:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  ab12f713d2 !  2:  0d28e337cf imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n     -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n     +\n     +\tif (!srvc->auth_method ||\n    -+\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n    -+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n    ++\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n    ++\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n     +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n      \n      \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n 2:  ba9c3fb756 =  3:  d934bdcb82 imap-send: add PLAIN authentication method to OpenSSL\n 3:  3d1a66da57 =  4:  f2773c646f imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  70bb9388b8 =  5:  c111ee6bc1 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n 5:  0d00a5e135 =  6:  f12713f24b imap-send: enable specifying the folder using the command line\n 6:  999c65438f =  7:  d38caeae5e imap-send: fix minor mistakes in the logs\n 7:  d0315aebd4 =  8:  3ba02f2b0c imap-send: display port alongwith host when git credential is invoked\n 8:  73352a18cf =  9:  6dbd0bf0bc imap-send: display the destination mailbox when sending a message\n 9:  36d50d01f0 = 10:  f77f2423e1 imap-send: add ability to list the available folders\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519744","messageId":"3e3ddf70772a9c319170064d77404ae858dac314.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:24Z","receivedAt":"2025-06-05T08:42:31Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519745","messageId":"f12713f24b00799c65546900ff501134ad24a807.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:28Z","receivedAt":"2025-06-05T08:42:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 6c7175ced0..0e51bf2b85 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1770,6 +1772,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519746","messageId":"0d28e337cfe7ce3c52490544875474b3502d2081.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:25Z","receivedAt":"2025-06-05T08:42:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++-\n imap-send.c                      | 182 +++++++++++++++++++++++++++++--\n 3 files changed, 221 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..829e957abd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,108 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +1001,9 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -1104,6 +1213,50 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* OAUTHBEARER */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_oauthbearer;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use XOAUTH2 authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* XOAUTH2 */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_xoauth2;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1580,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519747","messageId":"d38caeae5e24f5d173664db0a25003a516060959.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 07/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:29Z","receivedAt":"2025-06-05T08:42:35Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 0e51bf2b85..dcc12e5468 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n \treturn -1;\n }\n \n@@ -1020,7 +1020,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1128,7 +1128,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1147,7 +1147,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1185,7 +1185,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1199,7 +1199,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1211,7 +1211,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1342,12 +1342,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1603,7 +1603,7 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1712,7 +1712,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1796,13 +1796,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1824,7 +1824,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519748","messageId":"d934bdcb82df2c997b44f2ae5d4d5d9ba9cf066c.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 03/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:26Z","receivedAt":"2025-06-05T08:42:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +-\n imap-send.c                    | 82 +++++++++++++++++++++++++++++++++-\n 2 files changed, 83 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 829e957abd..38f09f1f02 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1001,6 +1058,7 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_plain NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n@@ -1198,7 +1256,29 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n+\t\t\t\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"You are trying to use PLAIN authentication mechanism \"\n+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n+\t\t\t\t/* PLAIN */\n+\n+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n+\t\t\t\tcb.cont = auth_plain;\n+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n+\t\t\t\t\tgoto bail;\n+\t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519749","messageId":"3ba02f2b0c09fc8a652ee043820801f1f3f3a6de.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:30Z","receivedAt":"2025-06-05T08:42:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex dcc12e5468..edc6b1ec25 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1083,7 +1083,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519750","messageId":"f2773c646f655e3d31b36aeb670bffd79c2c4fea.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 04/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:26Z","receivedAt":"2025-06-05T08:42:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 38f09f1f02..072c8f4e39 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1072,8 +1072,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519751","messageId":"6dbd0bf0bc3d8cdd0ba32ba39d765eb557c550f5.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:31Z","receivedAt":"2025-06-05T08:42:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex edc6b1ec25..3ad916c6da 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1603,7 +1603,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1712,7 +1713,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519752","messageId":"c111ee6bc16f1acfa2f4fb6ea51b58f822d33182.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:27Z","receivedAt":"2025-06-05T08:42:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 52 ++++++++++++++++++++++++++--------------------------\n 1 file changed, 26 insertions(+), 26 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 072c8f4e39..6c7175ced0 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1008,6 +1008,24 @@ static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n \treturn 0;\n }\n \n+static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n+{\n+\tint ret;\n+\tchar *response;\n+\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n+\n+\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n+\t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n+\n+\tfree(response);\n+\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1050,38 +1068,13 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n #define auth_plain NULL\n+#define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n #endif\n \n-static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n-{\n-\tint ret;\n-\tchar *response;\n-\n-\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n-\n-\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response)) {\n-\t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n-\t}\n-\n-\tfree(response);\n-\n-\treturn 0;\n-}\n-\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1287,6 +1280,13 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\n+\t\t\t\t#ifdef NO_OPENSSL\n+\t\t\t\tfprintf(stderr, \"If you want to use CRAM-MD5 authentication mechanism, \"\n+\t\t\t\t\t\"you have to build git-imap-send with OpenSSL library.\");\n+\t\t\t\tgoto bail;\n+\t\t\t\t#endif\n+\n \t\t\t\t/* CRAM-MD5 */\n \n \t\t\t\tmemset(&cb, 0, sizeof(cb));\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519753","messageId":"f77f2423e1153fe92e927df53bd12d983ec7fe3c.1749112640.git.gargaditya08@live.com","threadId":"63502","inReplyTo":"cover.1749112640.git.gargaditya08@live.com","subject":"[PATCH v13 10/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T08:42:31Z","receivedAt":"2025-06-05T08:42:42Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 3ad916c6da..910e0ea133 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1626,6 +1632,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1654,11 +1680,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1689,10 +1717,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1711,6 +1735,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1757,6 +1785,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1797,11 +1850,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n@@ -1811,6 +1859,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.639.gf77f2423e1\n\n"},{"id":"519790","messageId":"xmqq7c1q9nnm.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597EA7301052F34B6FE3E48B86FA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-05T16:08:29Z","receivedAt":"2025-06-05T16:08:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n>> On 5 Jun 2025, at 1:30 PM, Jeff King <peff@peff.net> wrote:\n>> \n>> ﻿On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n>> \n>>> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n>>> \n>>>    server_fill_credential(srvc, cred);\n>>>    curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>>> -    curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>> +\n>>> +    if (!srvc->auth_method ||\n>>> +        strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>>> +        strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>>> +        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>> \n>> Coverity complains that this \"if\" will always be true, since one of the\n>> strcmp() calls must return non-zero (srvc->auth_method cannot match both\n>> strings!).\n>> \n>> I'm not sure what the logic is supposed to be here. If we are matching\n>> either string, it should be !strcmp() for both. If we want to match\n>> neither, then it should be &&, not ||.\n>\n> Good catch. The aim was to not execute that statement if authentication is\n> XOAUTH2 or OAUTHBEARER. I'll fix this logic.\n\nYup.  I'll refrain from merging it down before the reroll.\n\n"},{"id":"519792","messageId":"PN3PR01MB9597690D45F0E539C35FF4F8B86FA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqq7c1q9nnm.fsf@gitster.g","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T16:17:31Z","receivedAt":"2025-06-05T16:17:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 5 Jun 2025, at 9:38 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>>>> On 5 Jun 2025, at 1:30 PM, Jeff King <peff@peff.net> wrote:\n>>> \n>>> ﻿On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n>>> \n>>>> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n>>>> \n>>>>   server_fill_credential(srvc, cred);\n>>>>   curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>>>> -    curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>>> +\n>>>> +    if (!srvc->auth_method ||\n>>>> +        strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>>>> +        strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>>>> +        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>> \n>>> Coverity complains that this \"if\" will always be true, since one of the\n>>> strcmp() calls must return non-zero (srvc->auth_method cannot match both\n>>> strings!).\n>>> \n>>> I'm not sure what the logic is supposed to be here. If we are matching\n>>> either string, it should be !strcmp() for both. If we want to match\n>>> neither, then it should be &&, not ||.\n>> \n>> Good catch. The aim was to not execute that statement if authentication is\n>> XOAUTH2 or OAUTHBEARER. I'll fix this logic.\n> \n> Yup.  I'll refrain from merging it down before the reroll.\n\nAlready sent a v13 :)\n"},{"id":"519793","messageId":"xmqq34ce9mql.fsf@gitster.g","threadId":"63502","inReplyTo":"20250605080002.GA2998537@coredump.intra.peff.net","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-05T16:28:18Z","receivedAt":"2025-06-05T16:28:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n>\n>> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n>>  \n>>  \tserver_fill_credential(srvc, cred);\n>>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>> +\n>> +\tif (!srvc->auth_method ||\n>> +\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>\n> Coverity complains that this \"if\" will always be true, since one of the\n> strcmp() calls must return non-zero (srvc->auth_method cannot match both\n> strings!).\n>\n> I'm not sure what the logic is supposed to be here. If we are matching\n> either string, it should be !strcmp() for both. If we want to match\n> neither, then it should be &&, not ||.\n\n\"If XOAUTH2 or OAUTHBEARER, use the password\" sounds somewhat\nstrange (unless the bearer token is stored in .pass and passed as if\nit is a password).\n\n\"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds even more\nstrange.  What about other methods that are not a plain simple\npassword authentication?  Will we remember extending this code when\nwe add yet another one to exclude it like XOAUTH2 and OAUTHBEARER\nare excluded with this patch?\n\n"},{"id":"519794","messageId":"xmqqy0u687ww.fsf@gitster.g","threadId":"63502","inReplyTo":"0d28e337cfe7ce3c52490544875474b3502d2081.1749112640.git.gargaditya08@live.com","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-05T16:33:51Z","receivedAt":"2025-06-05T16:33:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> +\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n> +\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n> +\t\t\t\t\tfprintf(stderr, \"You specified \"\n> +\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n> +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n> +\t\t\t\t\tgoto bail;\n> +\t\t\t\t}\n> +\n> +\t\t\t\t#ifdef NO_OPENSSL\n> +\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n> +\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n> +\t\t\t\tgoto bail;\n> +\t\t\t\t#endif\n\nUgly.  Can we avoid #ifdef/#endif in the middle of such a main flow\nof the logic?  Hiding such ugliness by indenting the #ifdef/#endif\ndirectives as if they are just one of the code lines is doubly ugly.\n\n>  \tserver_fill_credential(srvc, cred);\n>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> +\n> +\tif (!srvc->auth_method ||\n> +\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n\nCan we clarify this part, possibly with an in-code comment?\n\n\"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds a bit\nstrange.  What about methods other than these two that are not a\nplain simple password authentication?  Will we remember extending\nthis code when we add yet another one to exclude it like XOAUTH2 and\nOAUTHBEARER are excluded with this patch?\n\n"},{"id":"519800","messageId":"PN3PR01MB9597EA16029BEBBF4B966212B86FA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqy0u687ww.fsf@gitster.g","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-05T17:16:45Z","receivedAt":"2025-06-05T17:16:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 5 June 2025 10:03:51 pm IST, Junio C Hamano <gitster@pobox.com> wrote:\n>Aditya Garg <gargaditya08@live.com> writes:\n>\n>> +\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n>> +\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n>> +\t\t\t\t\tfprintf(stderr, \"You specified \"\n>> +\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n>> +\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n>> +\t\t\t\t\tgoto bail;\n>> +\t\t\t\t}\n>> +\n>> +\t\t\t\t#ifdef NO_OPENSSL\n>> +\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n>> +\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n>> +\t\t\t\tgoto bail;\n>> +\t\t\t\t#endif\n>\n>Ugly.  Can we avoid #ifdef/#endif in the middle of such a main flow\n>of the logic?  Hiding such ugliness by indenting the #ifdef/#endif\n>directives as if they are just one of the code lines is doubly ugly.\n>\n\nRESENDING AS PLAIN TEXT\n\n\nYour suggestion in a previous review said:\n\n           if (!auth_oauthbearer) {\n               ... we do not support ...\n               goto bail;\n           }\n\nMight look less ugly, but will result in a compiler warning that this will always\nbe true if compiled with NO_OPENSSL. If you are fine with that, good. Else tbh\nI am out of ideas :(.\n\n>>  \tserver_fill_credential(srvc, cred);\n>>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>> +\n>> +\tif (!srvc->auth_method ||\n>> +\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>\n>Can we clarify this part, possibly with an in-code comment?\n>\n>\"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds a bit\n>strange.  What about methods other than these two that are not a\n>plain simple password authentication?  Will we remember extending\n>this code when we add yet another one to exclude it like XOAUTH2 and\n>OAUTHBEARER are excluded with this patch?\n>\n\nLet me answer this first. CURLOPT_PASSWORD is for plain or login type\nauthentication, and if srvc->auth_method is not defined, curl's behaviour\ndefaults to them. OAUTHBEARER and XOAUTH2 use CURLOPT_XOAUTH2_BEARER\nin curl, which can use either of them based on what server says. Other auth methods\nare not supported yet in this code, and this is the reason CRAM_MD5 is supported\nby only OpenSSL.\n"},{"id":"519802","messageId":"xmqqy0u66n46.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597EA16029BEBBF4B966212B86FA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-05T18:48:25Z","receivedAt":"2025-06-05T18:48:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Might look less ugly, but will result in a compiler warning that this will always\n> be true if compiled with NO_OPENSSL. If you are fine with that, good. Else tbh\n> I am out of ideas :(.\n\nSounds like a good place to use NOT_CONSTANT(), it seems?\n\n\tif (NOT_CONSTANT(!auth_oauthbearer)) {\n\t\t... skip the thing ...\n\t}\n\n\n>>>  \tserver_fill_credential(srvc, cred);\n>>>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>>> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>> +\n>>> +\tif (!srvc->auth_method ||\n>>> +\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>>> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>>> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>\n>>Can we clarify this part, possibly with an in-code comment?\n>>\n>>\"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds a bit\n>>strange.  What about methods other than these two that are not a\n>>plain simple password authentication?  Will we remember extending\n>>this code when we add yet another one to exclude it like XOAUTH2 and\n>>OAUTHBEARER are excluded with this patch?\n\n> Let me answer this first. CURLOPT_PASSWORD is for plain or login type\n> authentication, and if srvc->auth_method is not defined, curl's behaviour\n> defaults to them.\n\nWhich makes it sound like if (!srvc->auth_method) is enough?\n\n> OAUTHBEARER and XOAUTH2 use CURLOPT_XOAUTH2_BEARER\n> in curl, which can use either of them based on what server says.\n\nThat is what we can read from the updated code.\n\nThe question is what happens when the user sets srvc->auth_method to\nsomething other than NULL (unused---use plain password), \"XOAUTH2\"\nor \"OAUTHBEARER\".\n\nIf the answer to that question is ...\n\n> Other auth methods\n> are not supported yet in this code, and this is the reason CRAM_MD5 is supported\n> by only OpenSSL.\n\n... \"with srvc->auth_method set to other methods like CRAM_MD5, the\ncontrol would never enter this codepath, as they are implemented\nelsewhere\", then I think it would make more sense to write the above\nlike this:\n\n\tif (!srvc->auth_method)\n\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n\telse if (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n\t\t strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n\t\tBUG(\"we only support XOAUTH2 and OAUTHBEARER in this codepath\");\n\nOr the code is not protecting this code path so control can reach\nwith auth_method set to CRAM_MD5 here (e.g. when built without\nOpenSSL)?  If so, replace BUG(\"message\") with die(_(\"message\"))\nabove.\n\nOn the other hand, if you are trying to fall back to plain password\nwhen other unhandled methods are specified, I would expect that the\ncode to read more like:\n\n\tif (srvc->auth_method &&\n            (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n             !strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n\t\t;\n\telse {\n\t\tif (srvc->auth_method)\n\t\t\twarning(\"auth method %s not supported,\n\t\t\t         falling back to plain password\",\n                                srvc->auth_method);\n\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n\t}\n\nI cannot quite tell which one you meant, but I am guessing that the\nformer is the case from your explanation.\n\nThanks.\n"},{"id":"519811","messageId":"20250605225019.GC3005733@coredump.intra.peff.net","threadId":"63502","inReplyTo":"xmqq34ce9mql.fsf@gitster.g","subject":"Re: [PATCH v12 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-06-05T22:50:19Z","receivedAt":"2025-06-05T22:50:20Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Jun 05, 2025 at 09:28:18AM -0700, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > On Mon, Jun 02, 2025 at 04:29:33PM +0530, Aditya Garg wrote:\n> >\n> >> @@ -1405,7 +1558,11 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n> >>  \n> >>  \tserver_fill_credential(srvc, cred);\n> >>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n> >> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> >> +\n> >> +\tif (!srvc->auth_method ||\n> >> +\t    strcmp(srvc->auth_method, \"XOAUTH2\") ||\n> >> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n> >> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> >\n> > Coverity complains that this \"if\" will always be true, since one of the\n> > strcmp() calls must return non-zero (srvc->auth_method cannot match both\n> > strings!).\n> >\n> > I'm not sure what the logic is supposed to be here. If we are matching\n> > either string, it should be !strcmp() for both. If we want to match\n> > neither, then it should be &&, not ||.\n> \n> \"If XOAUTH2 or OAUTHBEARER, use the password\" sounds somewhat\n> strange (unless the bearer token is stored in .pass and passed as if\n> it is a password).\n> \n> \"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds even more\n> strange.  What about other methods that are not a plain simple\n> password authentication?  Will we remember extending this code when\n> we add yet another one to exclude it like XOAUTH2 and OAUTHBEARER\n> are excluded with this patch?\n\nThat was my gut feeling, but I confess to not engaging my brain and was\njust relaying the coverity message before logging off for the day. ;)\n\n-Peff\n"},{"id":"519819","messageId":"PN3PR01MB9597D25829C2D0A4342DA311B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqy0u66n46.fsf@gitster.g","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T03:28:11Z","receivedAt":"2025-06-06T03:28:54Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 6 June 2025 12:18:25 am IST, Junio C Hamano <gitster@pobox.com> wrote:\n>Aditya Garg <gargaditya08@live.com> writes:\n>\n>> Might look less ugly, but will result in a compiler warning that this will always\n>> be true if compiled with NO_OPENSSL. If you are fine with that, good. Else tbh\n>> I am out of ideas :(.\n>\n>Sounds like a good place to use NOT_CONSTANT(), it seems?\n>\n>\tif (NOT_CONSTANT(!auth_oauthbearer)) {\n>\t\t... skip the thing ...\n>\t}\n>\n>\n\nOk\n\n>>>>  \tserver_fill_credential(srvc, cred);\n>>>>  \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>>>> -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>>> +\n>>>> +\tif (!srvc->auth_method ||\n>>>> +\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>>>> +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>>>> +\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>>\n>>>Can we clarify this part, possibly with an in-code comment?\n>>>\n>>>\"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds a bit\n>>>strange.  What about methods other than these two that are not a\n>>>plain simple password authentication?  Will we remember extending\n>>>this code when we add yet another one to exclude it like XOAUTH2 and\n>>>OAUTHBEARER are excluded with this patch?\n>\n>> Let me answer this first. CURLOPT_PASSWORD is for plain or login type\n>> authentication, and if srvc->auth_method is not defined, curl's behaviour\n>> defaults to them.\n>\n>Which makes it sound like if (!srvc->auth_method) is enough?\n>\n\nNo. If the user specifies PLAIN or LOGIN then it's not enough.\n\n>> OAUTHBEARER and XOAUTH2 use CURLOPT_XOAUTH2_BEARER\n>> in curl, which can use either of them based on what server says.\n>\n>That is what we can read from the updated code.\n>\n>The question is what happens when the user sets srvc->auth_method to\n>something other than NULL (unused---use plain password), \"XOAUTH2\"\n>or \"OAUTHBEARER\".\n>\n>If the answer to that question is ...\n>\n>> Other auth methods\n>> are not supported yet in this code, and this is the reason CRAM_MD5 is supported\n>> by only OpenSSL.\n>\n>... \"with srvc->auth_method set to other methods like CRAM_MD5, the\n>control would never enter this codepath, as they are implemented\n>elsewhere\", then I think it would make more sense to write the above\n>like this:\n>\n>\tif (!srvc->auth_method)\n>\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>\telse if (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>\t\t strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>\t\tBUG(\"we only support XOAUTH2 and OAUTHBEARER in this codepath\");\n>\n\nWe can implement this, but:\n\n1. It will fail if user specifies PLAIN or LOGIN as auth method.\n\n2. We have this in the code as well:\n\n\tif (srvc->auth_method) {\n\t\tstruct strbuf auth = STRBUF_INIT;\n\t\tstrbuf_addstr(&auth, \"AUTH=\");\n\t\tstrbuf_addstr(&auth, srvc->auth_method);\n\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n\t\tstrbuf_release(&auth);\n\t}\n\nWhich basically means that if a user specifies an auth method,\ncurl will try to use SMTP AUTH command with that method.\nSo ideally, this should have worked for OAUTHBEAER and XOAUTH2\n\nBut the problem with that would be a) we would need to format\nthe access token as per the specifications of these mechanisms.\nand b) curl simply says these methods are not supported when\nwe try with that.\n\nI filed a bug report regarding this and they were not really clear\non whether CURLOPT_LOGIN_OPTIONS is meant for PLAIN only or should work like this with other methods too.\n\nBut, the docs indicate it's for PLAIN auth only.\n\nSo, considering the fact that the original code for imap-send\nwas setting CURLOPT_LOGIN_OPTIONS\nunconditionally and\nwas running the AUTH command even if auth was set to CRAM-MD5\nor whatever, I just preferred to not change that behaviour since I\nmay cause some regression. There is a tiny possibility that CRAM-MD5\nmay work, but I don't really have any free SMTP server which uses\nthat method itself.\n\nIn short, just to be very safe here, I decided to not mingle with the\nlogic much and simple decided to use a seperate tested logic\nfor OAuth2.0 and let the same logic be used for rest cases.\n\nTherefore, the previous logic said:\n\n\"Set CURLOPT_LOGIN_OPTIONS irrespective of whether there is\nan auth method specified or not.\"\n\nNow it says\n\n\"Set CURLOPT_LOGIN_OPTIONS irrespective of whether there is\nan auth method specified or not, unless it's OAuth2.0, where we\nuse a different curl API\"\n\n\nThe bug report I filed with curl for reference:\n\nhttps://github.com/curl/curl/issues/17420\n\n\n>Or the code is not protecting this code path so control can reach\n>with auth_method set to CRAM_MD5 here (e.g. when built without\n>OpenSSL)?  If so, replace BUG(\"message\") with die(_(\"message\"))\n>above.\n>\n>On the other hand, if you are trying to fall back to plain password\n>when other unhandled methods are specified, I would expect that the\n>code to read more like:\n>\n>\tif (srvc->auth_method &&\n>            (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>             !strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>\t\t;\n>\telse {\n>\t\tif (srvc->auth_method)\n>\t\t\twarning(\"auth method %s not supported,\n>\t\t\t         falling back to plain password\",\n>                                srvc->auth_method);\n>\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>\t}\n>\n>I cannot quite tell which one you meant, but I am guessing that the\n>former is the case from your explanation.\n>\n>Thanks.\n"},{"id":"519820","messageId":"PN3PR01MB9597D29B9EB6F01C4834D653B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597D25829C2D0A4342DA311B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T04:04:59Z","receivedAt":"2025-06-06T04:05:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 6 Jun 2025, at 8:58 AM, Aditya Garg <gargaditya08@live.com> wrote:\n> \n> ﻿\n> \n>> On 6 June 2025 12:18:25 am IST, Junio C Hamano <gitster@pobox.com> wrote:\n>> Aditya Garg <gargaditya08@live.com> writes:\n>> \n>>> Might look less ugly, but will result in a compiler warning that this will always\n>>> be true if compiled with NO_OPENSSL. If you are fine with that, good. Else tbh\n>>> I am out of ideas :(.\n>> \n>> Sounds like a good place to use NOT_CONSTANT(), it seems?\n>> \n>>    if (NOT_CONSTANT(!auth_oauthbearer)) {\n>>        ... skip the thing ...\n>>    }\n>> \n>> \n> \n> Ok\n> \n>>>>>    server_fill_credential(srvc, cred);\n>>>>>    curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>>>>> -    curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>>>> +\n>>>>> +    if (!srvc->auth_method ||\n>>>>> +        (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>>>>> +        strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>>>>> +        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>>> \n>>>> Can we clarify this part, possibly with an in-code comment?\n>>>> \n>>>> \"Unless XOAUTH2 or OAUTHBEARER, use the password\" sounds a bit\n>>>> strange.  What about methods other than these two that are not a\n>>>> plain simple password authentication?  Will we remember extending\n>>>> this code when we add yet another one to exclude it like XOAUTH2 and\n>>>> OAUTHBEARER are excluded with this patch?\n>> \n>>> Let me answer this first. CURLOPT_PASSWORD is for plain or login type\n>>> authentication, and if srvc->auth_method is not defined, curl's behaviour\n>>> defaults to them.\n>> \n>> Which makes it sound like if (!srvc->auth_method) is enough?\n>> \n> \n> No. If the user specifies PLAIN or LOGIN then it's not enough.\n> \n>>> OAUTHBEARER and XOAUTH2 use CURLOPT_XOAUTH2_BEARER\n>>> in curl, which can use either of them based on what server says.\n>> \n>> That is what we can read from the updated code.\n>> \n>> The question is what happens when the user sets srvc->auth_method to\n>> something other than NULL (unused---use plain password), \"XOAUTH2\"\n>> or \"OAUTHBEARER\".\n>> \n>> If the answer to that question is ...\n>> \n>>> Other auth methods\n>>> are not supported yet in this code, and this is the reason CRAM_MD5 is supported\n>>> by only OpenSSL.\n>> \n>> ... \"with srvc->auth_method set to other methods like CRAM_MD5, the\n>> control would never enter this codepath, as they are implemented\n>> elsewhere\", then I think it would make more sense to write the above\n>> like this:\n>> \n>>    if (!srvc->auth_method)\n>>        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>>    else if (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>>         strcmp(srvc->auth_method, \"OAUTHBEARER\"))\n>>        BUG(\"we only support XOAUTH2 and OAUTHBEARER in this codepath\");\n>> \n> \n> We can implement this, but:\n> \n> 1. It will fail if user specifies PLAIN or LOGIN as auth method.\n> \n> 2. We have this in the code as well:\n> \n>    if (srvc->auth_method) {\n>        struct strbuf auth = STRBUF_INIT;\n>        strbuf_addstr(&auth, \"AUTH=\");\n>        strbuf_addstr(&auth, srvc->auth_method);\n>        curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n>        strbuf_release(&auth);\n>    }\n> \n> Which basically means that if a user specifies an auth method,\n> curl will try to use SMTP AUTH command with that method.\n> So ideally, this should have worked for OAUTHBEAER and XOAUTH2\n> \n> But the problem with that would be a) we would need to format\n> the access token as per the specifications of these mechanisms.\n> and b) curl simply says these methods are not supported when\n> we try with that.\n> \n> I filed a bug report regarding this and they were not really clear\n> on whether CURLOPT_LOGIN_OPTIONS is meant for PLAIN only or should work like this with other methods too.\n> \n> But, the docs indicate it's for PLAIN auth only.\n> \n> So, considering the fact that the original code for imap-send\n> was setting CURLOPT_LOGIN_OPTIONS\n> unconditionally and\n> was running the AUTH command even if auth was set to CRAM-MD5\n> or whatever, I just preferred to not change that behaviour since I\n> may cause some regression. There is a tiny possibility that CRAM-MD5\n> may work, but I don't really have any free SMTP server which uses\n\nSMTP was a typo. It's IMAP."},{"id":"519821","messageId":"xmqqldq55vxr.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597D25829C2D0A4342DA311B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-06T04:35:28Z","receivedAt":"2025-06-06T04:35:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n>>Which makes it sound like if (!srvc->auth_method) is enough?\n>>\n>\n> No. If the user specifies PLAIN or LOGIN then it's not enough.\n\nThat invites another question.  Why aren't we checking for PLAIN or\nLOGIN and when one of them is given use the password?  What is\nwritten in the patch looks backwards, in that we seem to assume that\na method that is not XOAUTH2 and OAUTHBEARER must be PLAIN or LOGIN\n(or anything that wants us to pass CURLOPT_PASSWORD).  IOW, why\nisn't the code more like\n\n\tif (/* not using the more advanced method interface? */\n\t    !srvc->auth_method ||\n\t    /* method interface that takes password? */\n\t    !strcmp(srvc->auth_method, \"PLAIN\")\t||\n\t    !strcmp(srvc->auth_method, \"LOGIN\"))\n\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n\nif, after all, PLAIN/LOGIN are what triggers password\nauthentication?\n\n> So, considering the fact that the original code for imap-send\n> was setting CURLOPT_LOGIN_OPTIONS\n> unconditionally and\n> was running the AUTH command even if auth was set to CRAM-MD5\n> or whatever, I just preferred to not change that behaviour since I\n> may cause some regression. There is a tiny possibility that CRAM-MD5\n> may work, but I don't really have any free SMTP server which uses\n> that method itself.\n>\n> In short, just to be very safe here, I decided to not mingle with the\n> logic much and simple decided to use a seperate tested logic\n> for OAuth2.0 and let the same logic be used for rest cases.\n\nDon't be short ;-) Be long in your log message to help future\ndevelopers.  In short, you want to make your proposed log message so\nclear that future developers who found this commit in \"git log -p\"\nto come asking you these questions---that is why reviewers are\nsupposed to ask questions and ask for clarifications.\n\n> \"Set CURLOPT_LOGIN_OPTIONS irrespective of whether there is\n> an auth method specified or not, unless it's OAuth2.0, where we\n> use a different curl API\"\n\nThat is a very good thing to write down either in-code comment\nand/or the log message to avoid future developers come bugging you\nwith the same questions as I did.\n\nThanks.\n"},{"id":"519822","messageId":"PN3PR01MB959787F6C64CDDD94BB6CA02B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqldq55vxr.fsf@gitster.g","subject":"Re: [PATCH v13 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T04:40:51Z","receivedAt":"2025-06-06T04:40:57Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 6 Jun 2025, at 10:05 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>>> Which makes it sound like if (!srvc->auth_method) is enough?\n>>> \n>> \n>> No. If the user specifies PLAIN or LOGIN then it's not enough.\n> \n> That invites another question.  Why aren't we checking for PLAIN or\n> LOGIN and when one of them is given use the password?  What is\n> written in the patch looks backwards, in that we seem to assume that\n> a method that is not XOAUTH2 and OAUTHBEARER must be PLAIN or LOGIN\n> (or anything that wants us to pass CURLOPT_PASSWORD).  IOW, why\n> isn't the code more like\n> \n>    if (/* not using the more advanced method interface? */\n>        !srvc->auth_method ||\n>        /* method interface that takes password? */\n>        !strcmp(srvc->auth_method, \"PLAIN\")    ||\n>        !strcmp(srvc->auth_method, \"LOGIN\"))\n>        curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> \n> if, after all, PLAIN/LOGIN are what triggers password\n> authentication?\n> \n>> So, considering the fact that the original code for imap-send\n>> was setting CURLOPT_LOGIN_OPTIONS\n>> unconditionally and\n>> was running the AUTH command even if auth was set to CRAM-MD5\n>> or whatever, I just preferred to not change that behaviour since I\n>> may cause some regression. There is a tiny possibility that CRAM-MD5\n>> may work, but I don't really have any free SMTP server which uses\n>> that method itself.\n>> \n>> In short, just to be very safe here, I decided to not mingle with the\n>> logic much and simple decided to use a seperate tested logic\n>> for OAuth2.0 and let the same logic be used for rest cases.\n> \n> Don't be short ;-) Be long in your log message to help future\n> developers.  In short, you want to make your proposed log message so\n> clear that future developers who found this commit in \"git log -p\"\n> to come asking you these questions---that is why reviewers are\n> supposed to ask questions and ask for clarifications.\n\nOk :). So, you want me to add checks for PLAIN and LOGIN, or the current\nlogic is fine. I'd prefer using the current logic to avoid potential regressions,\nbut its your call.\n> \n>> \"Set CURLOPT_LOGIN_OPTIONS irrespective of whether there is\n>> an auth method specified or not, unless it's OAuth2.0, where we\n>> use a different curl API\"\n> \n> That is a very good thing to write down either in-code comment\n> and/or the log message to avoid future developers come bugging you\n> with the same questions as I did.\n\nAlright, I'll add it as a comment in the code.\n"},{"id":"519865","messageId":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:22Z","receivedAt":"2025-06-06T20:08:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLast, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nP.S.: I am surprised this thing even exists xD.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix minor mistakes in the logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 412 ++++++++++++++++++++++++++-----\n 3 files changed, 414 insertions(+), 77 deletions(-)\n\nRange-diff against v13:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 1:  0d28e337cf !  2:  34d56c3b57 imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n      #endif\n      \n      static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    +@@ imap-send.c: static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n    + \t\tsrvc->pass = xstrdup(cred->password);\n    + }\n    + \n    ++static int try_auth_method(struct imap_server_conf *srvc,\n    ++\t\t\t   struct imap_store *ctx,\n    ++\t\t\t   struct imap *imap,\n    ++\t\t\t   const char *auth_method,\n    ++\t\t\t   enum CAPABILITY cap,\n    ++\t\t\t   int (*fn)(struct imap_store *, const char *))\n    ++{\n    ++\tstruct imap_cmd_cb cb = {0};\n    ++\n    ++\tif (!CAP(cap)) {\n    ++\t\tfprintf(stderr, \"You specified \"\n    ++\t\t\t\"%s as authentication method, \"\n    ++\t\t\t\"but %s doesn't support it.\\n\",\n    ++\t\t\tauth_method, srvc->host);\n    ++\t\treturn -1;\n    ++\t}\n    ++\tcb.cont = fn;\n    ++\n    ++\tif (NOT_CONSTANT(!cb.cont)) {\n    ++\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n    ++\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n    ++\t\t\tauth_method);\n    ++\t\treturn -1;\n    ++\t}\n    ++\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n    ++\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n    ++\t\t\tauth_method);\n    ++\t\treturn -1;\n    ++\t}\n    ++\treturn 0;\n    ++}\n    ++\n    + static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n    + {\n    + \tstruct credential cred = CREDENTIAL_INIT;\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n      \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n      \t\t\t\t\tgoto bail;\n      \t\t\t\t}\n     +\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    -+\t\t\t\tif (!CAP(AUTH_OAUTHBEARER)) {\n    -+\t\t\t\t\tfprintf(stderr, \"You specified \"\n    -+\t\t\t\t\t\t\"OAUTHBEARER as authentication method, \"\n    -+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n     +\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n    -+\n    -+\t\t\t\t#ifdef NO_OPENSSL\n    -+\t\t\t\tfprintf(stderr, \"You are trying to use OAUTHBEARER authentication mechanism \"\n    -+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    -+\t\t\t\tgoto bail;\n    -+\t\t\t\t#endif\n    -+\n    -+\t\t\t\t/* OAUTHBEARER */\n    -+\n    -+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    -+\t\t\t\tcb.cont = auth_oauthbearer;\n    -+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE OAUTHBEARER\") != RESP_OK) {\n    -+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE OAUTHBEARER failed\\n\");\n    -+\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n     +\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n    -+\t\t\t\tif (!CAP(AUTH_XOAUTH2)) {\n    -+\t\t\t\t\tfprintf(stderr, \"You specified \"\n    -+\t\t\t\t\t\t\"XOAUTH2 as authentication method, \"\n    -+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    -+\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n    -+\n    -+\t\t\t\t#ifdef NO_OPENSSL\n    -+\t\t\t\tfprintf(stderr, \"You are trying to use XOAUTH2 authentication mechanism \"\n    -+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    -+\t\t\t\tgoto bail;\n    -+\t\t\t\t#endif\n    -+\n    -+\t\t\t\t/* XOAUTH2 */\n    -+\n    -+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    -+\t\t\t\tcb.cont = auth_xoauth2;\n    -+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE XOAUTH2\") != RESP_OK) {\n    -+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE XOAUTH2 failed\\n\");\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n     +\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n      \t\t\t} else {\n      \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n      \t\t\t\tgoto bail;\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n      \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n     -\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n     +\n    ++\t/*\n    ++\t * Use CURLOPT_PASSWORD irrespective of whether there is\n    ++\t * an auth method specified or not, unless it's OAuth2.0,\n    ++\t * where we use CURLOPT_XOAUTH2_BEARER.\n    ++\t */\n     +\tif (!srvc->auth_method ||\n     +\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n     +\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n 2:  d934bdcb82 !  3:  69fb8f63f1 imap-send: add PLAIN authentication method to OpenSSL\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n      \n     -\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n     +\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n    -+\t\t\t\tif (!CAP(AUTH_PLAIN)) {\n    -+\t\t\t\t\tfprintf(stderr, \"You specified \"\n    -+\t\t\t\t\t\t\"PLAIN as authentication method, \"\n    -+\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n     +\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n    -+\n    -+\t\t\t\t#ifdef NO_OPENSSL\n    -+\t\t\t\tfprintf(stderr, \"You are trying to use PLAIN authentication mechanism \"\n    -+\t\t\t\t\t\"with OpenSSL library, but its support has not been compiled in.\");\n    -+\t\t\t\tgoto bail;\n    -+\t\t\t\t#endif\n    -+\n    -+\t\t\t\t/* PLAIN */\n    -+\n    -+\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    -+\t\t\t\tcb.cont = auth_plain;\n    -+\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE PLAIN\") != RESP_OK) {\n    -+\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE PLAIN failed\\n\");\n    -+\t\t\t\t\tgoto bail;\n    -+\t\t\t\t}\n     +\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n      \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n      \t\t\t\t\tfprintf(stderr, \"You specified \"\n 3:  f2773c646f =  4:  1510127888 imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  c111ee6bc1 !  5:  731fcbb602 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n    @@ imap-send.c: static int auth_xoauth2(struct imap_store *ctx, const char *prompt\n      {\n      \tif (srvc->user && srvc->pass)\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    - \t\t\t\t\tgoto bail;\n    - \t\t\t\t}\n    -+\n    -+\t\t\t\t#ifdef NO_OPENSSL\n    -+\t\t\t\tfprintf(stderr, \"If you want to use CRAM-MD5 authentication mechanism, \"\n    -+\t\t\t\t\t\"you have to build git-imap-send with OpenSSL library.\");\n    -+\t\t\t\tgoto bail;\n    -+\t\t\t\t#endif\n    -+\n    - \t\t\t\t/* CRAM-MD5 */\n    + \t\tserver_fill_credential(srvc, &cred);\n      \n    - \t\t\t\tmemset(&cb, 0, sizeof(cb));\n    + \t\tif (srvc->auth_method) {\n    +-\t\t\tstruct imap_cmd_cb cb;\n    +-\n    + \t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n    + \t\t\t\t\tgoto bail;\n    + \t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    +-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n    +-\t\t\t\t\tfprintf(stderr, \"You specified \"\n    +-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n    +-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n    +-\t\t\t\t\tgoto bail;\n    +-\t\t\t\t}\n    +-\t\t\t\t/* CRAM-MD5 */\n    +-\n    +-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n    +-\t\t\t\tcb.cont = auth_cram_md5;\n    +-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n    +-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n    + \t\t\t\t\tgoto bail;\n    +-\t\t\t\t}\n    + \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n    + \t\t\t\t\tgoto bail;\n 5:  f12713f24b =  6:  36154d3276 imap-send: enable specifying the folder using the command line\n 6:  d38caeae5e !  7:  85ce1205ca imap-send: fix minor mistakes in the logs\n    @@ imap-send.c: static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n      \t\t\t      int use_tls_only UNUSED)\n      {\n     -\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n    -+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in.\\n\");\n    ++\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n      \treturn -1;\n      }\n      \n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n      \n      \t/* read the greeting string */\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n      \t\t\t\t\tgoto bail;\n    - \t\t\t\t}\n      \t\t\t} else {\n     -\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n     +\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n 7:  3ba02f2b0c =  8:  8dd19a4613 imap-send: display port alongwith host when git credential is invoked\n 8:  6dbd0bf0bc =  9:  cc1398bb7c imap-send: display the destination mailbox when sending a message\n 9:  f77f2423e1 = 10:  0975df9fc0 imap-send: add ability to list the available folders\n-- \n2.49.0\n\n"},{"id":"519866","messageId":"PN3PR01MB9597CC4CF79C3316ABC6F51EB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:23Z","receivedAt":"2025-06-06T20:08:56Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"519867","messageId":"PN3PR01MB9597243869CB8CF4D50CF505B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 03/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:25Z","receivedAt":"2025-06-06T20:08:58Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 63 +++++++++++++++++++++++++++++++++-\n 2 files changed, 64 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 9df4519fa3..f7e59397d0 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1001,6 +1058,7 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_plain NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n@@ -1230,7 +1288,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tif (srvc->auth_method) {\n \t\t\tstruct imap_cmd_cb cb;\n \n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n \t\t\t\t\tfprintf(stderr, \"You specified \"\n \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-- \n2.49.0\n\n"},{"id":"519868","messageId":"PN3PR01MB95973CD16B82F61B6CEC7A1FB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 02/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:24Z","receivedAt":"2025-06-06T20:08:58Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++-\n imap-send.c                      | 181 +++++++++++++++++++++++++++++--\n 3 files changed, 220 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..9df4519fa3 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,108 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n static char *cram(const char *challenge_64 UNUSED,\n@@ -895,6 +1001,9 @@ static char *cram(const char *challenge_64 UNUSED,\n \t    \"you have to build git-imap-send with OpenSSL library.\");\n }\n \n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n+\n #endif\n \n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n@@ -932,6 +1041,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1104,6 +1245,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n \t\t\t\t\tgoto bail;\n \t\t\t\t}\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1405,7 +1552,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1423,11 +1579,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"519869","messageId":"PN3PR01MB9597997B73874E410B64EE62B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 04/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:26Z","receivedAt":"2025-06-06T20:09:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex f7e59397d0..6522f80964 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1072,8 +1072,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"519870","messageId":"PN3PR01MB959732DB7232FFE1ED7D38F3B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:27Z","receivedAt":"2025-06-06T20:09:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 61 ++++++++++++++++++-----------------------------------\n 1 file changed, 20 insertions(+), 41 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 6522f80964..c6e47ddc42 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1008,6 +1008,24 @@ static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n \treturn 0;\n }\n \n+static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n+{\n+\tint ret;\n+\tchar *response;\n+\n+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n+\n+\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n+\t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n+\n+\tfree(response);\n+\n+\treturn 0;\n+}\n+\n static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n {\n \tint ret;\n@@ -1050,38 +1068,13 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n #define auth_plain NULL\n+#define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n \n #endif\n \n-static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n-{\n-\tint ret;\n-\tchar *response;\n-\n-\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n-\n-\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response)) {\n-\t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n-\t}\n-\n-\tfree(response);\n-\n-\treturn 0;\n-}\n-\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -1288,26 +1281,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n \t\t\t\t\tgoto bail;\n-- \n2.49.0\n\n"},{"id":"519871","messageId":"PN3PR01MB9597F000B3515EC0A11357CFB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:28Z","receivedAt":"2025-06-06T20:09:03Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex c6e47ddc42..a4cccb9110 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0\n\n"},{"id":"519872","messageId":"PN3PR01MB9597DB8D8E40A2428DE345EBB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 07/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:29Z","receivedAt":"2025-06-06T20:09:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex a4cccb9110..a9dc6cfad6 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1020,7 +1020,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1160,7 +1160,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1179,7 +1179,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1217,7 +1217,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1231,7 +1231,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1243,7 +1243,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1296,12 +1296,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1557,7 +1557,7 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1671,7 +1671,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1755,13 +1755,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1783,7 +1783,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0\n\n"},{"id":"519873","messageId":"PN3PR01MB9597D20BB80F413A7E4FAC7BB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:30Z","receivedAt":"2025-06-06T20:09:05Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex a9dc6cfad6..e3068ef1fe 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1083,7 +1083,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0\n\n"},{"id":"519874","messageId":"PN3PR01MB95972B1FA7082FE85A6EAC26B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:31Z","receivedAt":"2025-06-06T20:09:06Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex e3068ef1fe..9281112bea 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1557,7 +1557,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1671,7 +1672,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0\n\n"},{"id":"519875","messageId":"PN3PR01MB9597C1380DD16748C886984DB86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597BB453E9980CC50AA6703B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v14 10/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-06T20:06:32Z","receivedAt":"2025-06-06T20:09:07Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 9281112bea..16c2e641ac 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1580,6 +1586,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1613,11 +1639,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1648,10 +1676,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1670,6 +1694,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1716,6 +1744,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1756,11 +1809,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n@@ -1770,6 +1818,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0\n\n"},{"id":"519891","messageId":"xmqqy0u3zhxl.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB959732DB7232FFE1ED7D38F3B86EA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-07T15:32:06Z","receivedAt":"2025-06-07T15:32:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\n> supported by libcurl and requires OpenSSL. If the user tries to use\n> CRAM-MD5 authentication without OpenSSL, the previous behaviour was to\n> attempt to authenticate and fail with a die(error). Handle this in a\n> better way by first checking if OpenSSL is available and then attempting\n> to authenticate. If OpenSSL is not available, print an error message and\n> exit gracefully.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  imap-send.c | 61 ++++++++++++++++++-----------------------------------\n>  1 file changed, 20 insertions(+), 41 deletions(-)\n\nThis is a good thing to do, but I would have expected that it would\ncome a lot earlier in the series, perhaps immediately after 01/10\nfixes the copy-and-paste bug.  If this is moved earlier in the\nseries, it would need to introduce the try_auth_method() helper at\nthe same time.  Since there is no new authentication methods\nintroduced at that stage in the series yet, it would be quite\nstraight-forward to read and understand the patch, and on top of\nsuch a solidified ground, the series can add OAuth2.0 and PLAIN\nsupport on top.\n\nThanks.\n"},{"id":"519892","messageId":"PN3PR01MB9597C03C260EC7F5F9237A5FB869A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqy0u3zhxl.fsf@gitster.g","subject":"Re: [PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-07T17:13:08Z","receivedAt":"2025-06-07T17:13:15Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 7 Jun 2025, at 9:02 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\n>> supported by libcurl and requires OpenSSL. If the user tries to use\n>> CRAM-MD5 authentication without OpenSSL, the previous behaviour was to\n>> attempt to authenticate and fail with a die(error). Handle this in a\n>> better way by first checking if OpenSSL is available and then attempting\n>> to authenticate. If OpenSSL is not available, print an error message and\n>> exit gracefully.\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> imap-send.c | 61 ++++++++++++++++++-----------------------------------\n>> 1 file changed, 20 insertions(+), 41 deletions(-)\n> \n> This is a good thing to do, but I would have expected that it would\n> come a lot earlier in the series, perhaps immediately after 01/10\n> fixes the copy-and-paste bug.  If this is moved earlier in the\n> series, it would need to introduce the try_auth_method() helper at\n> the same time.  Since there is no new authentication methods\n> introduced at that stage in the series yet, it would be quite\n> straight-forward to read and understand the patch, and on top of\n> such a solidified ground, the series can add OAuth2.0 and PLAIN\n> support on top.\n\nI understand what you said is the ideal way to do, but since the cram\nmd5 patch came up much later, I found it easier to place it at this place.\nI usually try to avoid as much conflicts as possible while rebasing, since\nI fear breaking something. But if I *have* to move it above, please let me\nknow.\n\nThanks"},{"id":"519908","messageId":"xmqqikl6yid8.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597C03C260EC7F5F9237A5FB869A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-08T04:20:19Z","receivedAt":"2025-06-08T04:20:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> ... if I *have* to move it above, please let me\n> know.\n\nThis is the second time after I told you what needs to be done you\ntold me to tell you to do it, isn't it?\n"},{"id":"519911","messageId":"PN3PR01MB9597F2063272A3D7E572443EB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqikl6yid8.fsf@gitster.g","subject":"Re: [PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T07:54:43Z","receivedAt":"2025-06-08T07:54:49Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 8 Jun 2025, at 9:50 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> ... if I *have* to move it above, please let me\n>> know.\n> \n> This is the second time after I told you what needs to be done you\n> told me to tell you to do it, isn't it?\n\nWhat I understand every time is you suggest me rather than tell me.\nAnyways, English is not my native language so I do misunderstand\nquite often."},{"id":"519922","messageId":"PN3PR01MB9597F4AB1711886FC47131D6B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqy0u3zhxl.fsf@gitster.g","subject":"Re: [PATCH v14 05/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:56:34Z","receivedAt":"2025-06-08T10:56:41Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 7 Jun 2025, at 9:02 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\n>> supported by libcurl and requires OpenSSL. If the user tries to use\n>> CRAM-MD5 authentication without OpenSSL, the previous behaviour was to\n>> attempt to authenticate and fail with a die(error). Handle this in a\n>> better way by first checking if OpenSSL is available and then attempting\n>> to authenticate. If OpenSSL is not available, print an error message and\n>> exit gracefully.\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> imap-send.c | 61 ++++++++++++++++++-----------------------------------\n>> 1 file changed, 20 insertions(+), 41 deletions(-)\n> \n> This is a good thing to do, but I would have expected that it would\n> come a lot earlier in the series, perhaps immediately after 01/10\n> fixes the copy-and-paste bug.  If this is moved earlier in the\n> series, it would need to introduce the try_auth_method() helper at\n> the same time.  Since there is no new authentication methods\n> introduced at that stage in the series yet, it would be quite\n> straight-forward to read and understand the patch, and on top of\n> such a solidified ground, the series can add OAuth2.0 and PLAIN\n> support on top.\n\nSent a v15 with the patches rearranged."},{"id":"519923","messageId":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:09Z","receivedAt":"2025-06-08T10:57:13Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLastly, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\nv15: - Simply rearrange the patches to make the cram md5 patches come\n       before adding OAuth2.0 and PLAIN authentication methods. No \n       change has been done to the code itself.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: fix minor mistakes in the logs\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: add ability to list the available folders\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 412 ++++++++++++++++++++++++++-----\n 3 files changed, 414 insertions(+), 77 deletions(-)\n\nRange-diff against v14:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n 3:  1510127888 =  2:  417b3b8e38 imap-send: fix memory leak in case auth_cram_md5 fails\n 4:  731fcbb602 !  3:  c4216528e7 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n    @@ Commit message\n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n      ## imap-send.c ##\n    -@@ imap-send.c: static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n    - \treturn 0;\n    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    + \treturn (char *)response_64;\n      }\n      \n    -+static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    -+{\n    -+\tint ret;\n    -+\tchar *response;\n    -+\n    -+\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n    -+\n    -+\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n    -+\tif (ret != strlen(response)) {\n    -+\t\tfree(response);\n    -+\t\treturn error(\"IMAP error: sending response failed\");\n    -+\t}\n    -+\n    -+\tfree(response);\n    -+\n    -+\treturn 0;\n    -+}\n    -+\n    - static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n    - {\n    - \tint ret;\n    -@@ imap-send.c: static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n    - \n    - #else\n    - \n    +-#else\n    +-\n     -static char *cram(const char *challenge_64 UNUSED,\n     -\t\t  const char *user UNUSED,\n     -\t\t  const char *pass UNUSED)\n    @@ imap-send.c: static int auth_xoauth2(struct imap_store *ctx, const char *prompt\n     -\t    \"you have to build git-imap-send with OpenSSL library.\");\n     -}\n     -\n    - #define auth_plain NULL\n    -+#define auth_cram_md5 NULL\n    - #define auth_oauthbearer NULL\n    - #define auth_xoauth2 NULL\n    - \n    - #endif\n    - \n    --static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    --{\n    --\tint ret;\n    --\tchar *response;\n    --\n    --\tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n    --\n    --\tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n    --\tif (ret != strlen(response)) {\n    --\t\tfree(response);\n    --\t\treturn error(\"IMAP error: sending response failed\");\n    --\t}\n    --\n    --\tfree(response);\n    --\n    --\treturn 0;\n    --}\n    +-#endif\n     -\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + {\n    + \tint ret;\n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \treturn 0;\n    + }\n    + \n    ++#else\n    ++\n    ++#define auth_cram_md5 NULL\n    ++\n    ++#endif\n    ++\n      static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n      {\n      \tif (srvc->user && srvc->pass)\n    +@@ imap-send.c: static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n    + \t\tsrvc->pass = xstrdup(cred->password);\n    + }\n    + \n    ++static int try_auth_method(struct imap_server_conf *srvc,\n    ++\t\t\t   struct imap_store *ctx,\n    ++\t\t\t   struct imap *imap,\n    ++\t\t\t   const char *auth_method,\n    ++\t\t\t   enum CAPABILITY cap,\n    ++\t\t\t   int (*fn)(struct imap_store *, const char *))\n    ++{\n    ++\tstruct imap_cmd_cb cb = {0};\n    ++\n    ++\tif (!CAP(cap)) {\n    ++\t\tfprintf(stderr, \"You specified \"\n    ++\t\t\t\"%s as authentication method, \"\n    ++\t\t\t\"but %s doesn't support it.\\n\",\n    ++\t\t\tauth_method, srvc->host);\n    ++\t\treturn -1;\n    ++\t}\n    ++\tcb.cont = fn;\n    ++\n    ++\tif (NOT_CONSTANT(!cb.cont)) {\n    ++\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n    ++\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n    ++\t\t\tauth_method);\n    ++\t\treturn -1;\n    ++\t}\n    ++\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n    ++\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n    ++\t\t\tauth_method);\n    ++\t\treturn -1;\n    ++\t}\n    ++\treturn 0;\n    ++}\n    ++\n    + static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n    + {\n    + \tstruct credential cred = CREDENTIAL_INIT;\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n      \t\tserver_fill_credential(srvc, &cred);\n      \n      \t\tif (srvc->auth_method) {\n     -\t\t\tstruct imap_cmd_cb cb;\n     -\n    - \t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n    - \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n    - \t\t\t\t\tgoto bail;\n    - \t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    + \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n     -\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n     -\t\t\t\t\tfprintf(stderr, \"You specified \"\n     -\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n     +\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n      \t\t\t\t\tgoto bail;\n     -\t\t\t\t}\n    - \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    - \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n    - \t\t\t\t\tgoto bail;\n    + \t\t\t} else {\n    + \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n    + \t\t\t\tgoto bail;\n 1:  34d56c3b57 !  4:  b38fca0e6a imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\treturn b64;\n     +}\n     +\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + {\n    + \tint ret;\n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \treturn 0;\n    + }\n    + \n     +static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n     +{\n     +\tint ret;\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\n      #else\n      \n    - static char *cram(const char *challenge_64 UNUSED,\n    -@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    - \t    \"you have to build git-imap-send with OpenSSL library.\");\n    - }\n    - \n    + #define auth_cram_md5 NULL\n     +#define auth_oauthbearer NULL\n     +#define auth_xoauth2 NULL\n    -+\n    - #endif\n      \n    - static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    -@@ imap-send.c: static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n    - \t\tsrvc->pass = xstrdup(cred->password);\n    - }\n    + #endif\n      \n    -+static int try_auth_method(struct imap_server_conf *srvc,\n    -+\t\t\t   struct imap_store *ctx,\n    -+\t\t\t   struct imap *imap,\n    -+\t\t\t   const char *auth_method,\n    -+\t\t\t   enum CAPABILITY cap,\n    -+\t\t\t   int (*fn)(struct imap_store *, const char *))\n    -+{\n    -+\tstruct imap_cmd_cb cb = {0};\n    -+\n    -+\tif (!CAP(cap)) {\n    -+\t\tfprintf(stderr, \"You specified \"\n    -+\t\t\t\"%s as authentication method, \"\n    -+\t\t\t\"but %s doesn't support it.\\n\",\n    -+\t\t\tauth_method, srvc->host);\n    -+\t\treturn -1;\n    -+\t}\n    -+\tcb.cont = fn;\n    -+\n    -+\tif (NOT_CONSTANT(!cb.cont)) {\n    -+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n    -+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n    -+\t\t\tauth_method);\n    -+\t\treturn -1;\n    -+\t}\n    -+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n    -+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n    -+\t\t\tauth_method);\n    -+\t\treturn -1;\n    -+\t}\n    -+\treturn 0;\n    -+}\n    -+\n    - static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n    - {\n    - \tstruct credential cred = CREDENTIAL_INIT;\n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n    + \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n      \t\t\t\t\tgoto bail;\n    - \t\t\t\t}\n     +\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n     +\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n     +\t\t\t\t\tgoto bail;\n 2:  69fb8f63f1 !  5:  86d3d2c54d imap-send: add PLAIN authentication method to OpenSSL\n    @@ imap-send.c: static char *xoauth2_base64(const char *user, const char *access_to\n     +\treturn 0;\n     +}\n     +\n    - static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n      {\n      \tint ret;\n    -@@ imap-send.c: static char *cram(const char *challenge_64 UNUSED,\n    - \t    \"you have to build git-imap-send with OpenSSL library.\");\n    - }\n    +@@ imap-send.c: static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n    + \n    + #else\n      \n     +#define auth_plain NULL\n    + #define auth_cram_md5 NULL\n      #define auth_oauthbearer NULL\n      #define auth_xoauth2 NULL\n    - \n     @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    - \t\tif (srvc->auth_method) {\n    - \t\t\tstruct imap_cmd_cb cb;\n    + \t\tserver_fill_credential(srvc, &cred);\n      \n    + \t\tif (srvc->auth_method) {\n     -\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n     +\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n     +\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n     +\t\t\t\t\tgoto bail;\n     +\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    - \t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n    - \t\t\t\t\tfprintf(stderr, \"You specified \"\n    - \t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n    + \t\t\t\t\tgoto bail;\n    + \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n 5:  36154d3276 =  6:  7674e749c8 imap-send: enable specifying the folder using the command line\n 6:  85ce1205ca =  7:  a67322ce06 imap-send: fix minor mistakes in the logs\n 7:  8dd19a4613 =  8:  b2e7ef35ed imap-send: display port alongwith host when git credential is invoked\n 8:  cc1398bb7c =  9:  668e62c0e0 imap-send: display the destination mailbox when sending a message\n 9:  0975df9fc0 = 10:  4d9a3b5661 imap-send: add ability to list the available folders\n-- \n2.49.0\n\n"},{"id":"519924","messageId":"PN3PR01MB959745C097CFF0A50FA4A05EB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:10Z","receivedAt":"2025-06-08T10:57:15Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"519926","messageId":"PN3PR01MB95972C1EB5E3595CF8096F02B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 05/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:14Z","receivedAt":"2025-06-08T10:57:17Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 63 +++++++++++++++++++++++++++++++++-\n 2 files changed, 64 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 5373f18b94..c6e47ddc42 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1011,6 +1068,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n+#define auth_plain NULL\n #define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n@@ -1223,7 +1281,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n-- \n2.49.0\n\n"},{"id":"519925","messageId":"PN3PR01MB9597F3C8B35EE5FCAC5DDCF4B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 02/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:11Z","receivedAt":"2025-06-08T10:57:18Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..1a582c8443 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -905,8 +905,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"519927","messageId":"PN3PR01MB9597580DCFE961DADD493C7CB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:12Z","receivedAt":"2025-06-08T10:57:20Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 66 +++++++++++++++++++++++++++++++----------------------\n 1 file changed, 39 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 1a582c8443..f55399cd9e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -885,18 +885,6 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n-#else\n-\n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n-#endif\n-\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -915,6 +903,12 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+#else\n+\n+#define auth_cram_md5 NULL\n+\n+#endif\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -934,6 +928,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1089,23 +1115,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0\n\n"},{"id":"519928","messageId":"PN3PR01MB95979FCCDB31EB3927DECFE8B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 07/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:16Z","receivedAt":"2025-06-08T10:57:20Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex a4cccb9110..a9dc6cfad6 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -205,7 +205,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1020,7 +1020,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1160,7 +1160,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1179,7 +1179,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1217,7 +1217,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1231,7 +1231,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1243,7 +1243,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1296,12 +1296,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1557,7 +1557,7 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1671,7 +1671,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n@@ -1755,13 +1755,13 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1783,7 +1783,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0\n\n"},{"id":"519929","messageId":"PN3PR01MB9597B4D69ACF1E64AEF70634B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:15Z","receivedAt":"2025-06-08T10:57:21Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex c6e47ddc42..a4cccb9110 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0\n\n"},{"id":"519930","messageId":"PN3PR01MB959787CC7175E2296A3AF710B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:13Z","receivedAt":"2025-06-08T10:57:22Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++++-\n imap-send.c                      | 148 +++++++++++++++++++++++++++++--\n 3 files changed, 187 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex f55399cd9e..5373f18b94 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n #define auth_cram_md5 NULL\n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n \n #endif\n \n@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"519931","messageId":"PN3PR01MB959770615068AD5047DD7728B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:17Z","receivedAt":"2025-06-08T10:57:23Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex a9dc6cfad6..e3068ef1fe 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1083,7 +1083,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0\n\n"},{"id":"519932","messageId":"PN3PR01MB95979829E7BBDC5D49E3A023B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:18Z","receivedAt":"2025-06-08T10:57:24Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    Sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex e3068ef1fe..9281112bea 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1557,7 +1557,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1671,7 +1672,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tcurl = setup_curl(server, &cred);\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0\n\n"},{"id":"519933","messageId":"PN3PR01MB95971D49AE9AD546D13F1398B868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v15 10/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-08T10:55:19Z","receivedAt":"2025-06-08T10:57:25Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 9281112bea..16c2e641ac 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1580,6 +1586,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1613,11 +1639,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1648,10 +1676,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1670,6 +1694,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n@@ -1716,6 +1744,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1756,11 +1809,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n@@ -1770,6 +1818,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0\n\n"},{"id":"519944","messageId":"xmqqcybevtxw.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB95975598E2CF61CA5DE050BAB868A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v15 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-08T20:50:51Z","receivedAt":"2025-06-08T20:50:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Aditya Garg (10):\n>   imap-send: fix bug causing cfg->folder being set to NULL\n>   imap-send: fix memory leak in case auth_cram_md5 fails\n>   imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>     without OpenSSL\n>   imap-send: add support for OAuth2.0 authentication\n>   imap-send: add PLAIN authentication method to OpenSSL\n>   imap-send: enable specifying the folder using the command line\n>   imap-send: fix minor mistakes in the logs\n>   imap-send: display port alongwith host when git credential is invoked\n>   imap-send: display the destination mailbox when sending a message\n>   imap-send: add ability to list the available folders\n\nVery nicely organized, starting from obvious fixes and usability\npolishing at the beginning of the series, followed by feature\nenhancements to add new variants to the existing framework, followed\nby three new features.  The \"ok\"->\"OK\" change seems somewhat out of\nplace (I would have done it early if the changes are not controversial,\nor very late after the series if the changes looked merely subjective,\nand not in the middle either case), but other than that the series is\nnow organized perfectly.\n\nI think the \"fixes\" and \"auth method enhancements\" in the earlier\npart are the same as before and I was happy with the resulting code.\n\nI didn't seriously read the last three or four patches during the\nprevious round, so I would say they still need reviews, but the\nearly part of the series now looks very well.\n\nThanks.\n\n"},{"id":"519950","messageId":"PN3PR01MB9597BEDC25A1A5C2C37CC9F1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqcybevtxw.fsf@gitster.g","subject":"Re: [PATCH v15 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T04:31:18Z","receivedAt":"2025-06-09T04:33:27Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 9 June 2025 2:20:51 am IST, Junio C Hamano <gitster@pobox.com> wrote:\n>Aditya Garg <gargaditya08@live.com> writes:\n>\n>> Aditya Garg (10):\n>>   imap-send: fix bug causing cfg->folder being set to NULL\n>>   imap-send: fix memory leak in case auth_cram_md5 fails\n>>   imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>>     without OpenSSL\n>>   imap-send: add support for OAuth2.0 authentication\n>>   imap-send: add PLAIN authentication method to OpenSSL\n>>   imap-send: enable specifying the folder using the command line\n>>   imap-send: fix minor mistakes in the logs\n>>   imap-send: display port alongwith host when git credential is invoked\n>>   imap-send: display the destination mailbox when sending a message\n>>   imap-send: add ability to list the available folders\n>\n>Very nicely organized, starting from obvious fixes and usability\n>polishing at the beginning of the series, followed by feature\n>enhancements to add new variants to the existing framework, followed\n>by three new features.  The \"ok\"->\"OK\" change seems somewhat out of\n>place (I would have done it early if the changes are not controversial,\n>or very late after the series if the changes looked merely subjective,\n>and not in the middle either case), but other than that the series is\n>now organized perfectly.\n\nLet's shift it at the last then.\n\n>\n>I think the \"fixes\" and \"auth method enhancements\" in the earlier\n>part are the same as before and I was happy with the resulting code.\n>\n>I didn't seriously read the last three or four patches during the\n>previous round, so I would say they still need reviews, but the\n>early part of the series now looks very well.\n>\n>Thanks.\n>\n"},{"id":"519953","messageId":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:31Z","receivedAt":"2025-06-09T07:21:45Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch series does the following things:\nFirstly it basically makes the imap-send command usable again since it\nwas broken because of not being able to correctly parse the config file.\n\nFurther it adds support for OAuth2.0 and PLAIN authentication to git\nimap-send.\n\nLastly, it does some minor improvements including adding the ability to\nspecify the folder using the command line and ability to list the\navailable folders by adding a `--list` option.\n\nv2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\nv15: - Simply rearrange the patches to make the cram md5 patches come\n       before adding OAuth2.0 and PLAIN authentication methods. No \n       change has been done to the code itself.\nv16: - Rearrage some more patches so that the two new features, i.e.,\n       --folder and --list come just after the new authentication\n       methods. Then the two patches with minor improvements of displaying\n       the destination mailbox and displaying port alongwith host have\n       been added. The patch fixing other minor mistakes in the logs has\n       been moved to the end. Just like v15, no change has been done\n       to the code itself.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: add ability to list the available folders\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: fix minor mistakes in the logs\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 412 ++++++++++++++++++++++++++-----\n 3 files changed, 414 insertions(+), 77 deletions(-)\n\nRange-diff against v15:\n -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL\n -:  ---------- >  2:  417b3b8e38 imap-send: fix memory leak in case auth_cram_md5 fails\n -:  ---------- >  3:  c4216528e7 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n 3:  668e62c0e0 !  4:  b38fca0e6a imap-send: display the destination mailbox when sending a message\n    @@ Metadata\n     Author: Aditya Garg <gargaditya08@live.com>\n     \n      ## Commit message ##\n    -    imap-send: display the destination mailbox when sending a message\n    +    imap-send: add support for OAuth2.0 authentication\n     \n    -    Whenever we sent a message using the `imap-send` command, it would\n    -    display a log showing the number of messages which are to be sent.\n    -    For example:\n    +    OAuth2.0 is a new way of authentication supported by various email providers\n    +    these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\n    +    for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\n    +    XOAUTH2 is Google's proprietary mechanism (See [3]).\n     \n    -        Sending 1 message\n    -         100% (1/1) done\n    +    [1]: https://datatracker.ietf.org/doc/html/rfc5801\n    +    [2]: https://datatracker.ietf.org/doc/html/rfc7628\n    +    [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n     \n    -    This had been made more informative by adding the name of the destination\n    -    folder as well:\n    +    Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n    -        Sending 1 message to Drafts folder...\n    -         100% (1/1) done\n    + ## Documentation/config/imap.adoc ##\n    +@@ Documentation/config/imap.adoc: imap.authMethod::\n    + \tSpecify the authentication method for authenticating with the IMAP server.\n    + \tIf Git was built with the NO_CURL option, or if your curl version is older\n    + \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n    +-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n    +-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n    ++\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n    ++\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n    ++\tplaintext `LOGIN` command.\n     \n    -    Signed-off-by: Aditya Garg <gargaditya08@live.com>\n    + ## Documentation/git-imap-send.adoc ##\n    +@@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n    + \n    + ---------\n    + [imap]\n    +-\tfolder = \"[Gmail]/Drafts\"\n    +-\thost = imaps://imap.gmail.com\n    +-\tuser = user@gmail.com\n    +-\tport = 993\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    + ---------\n    + \n    ++Gmail does not allow using your regular password for `git imap-send`.\n    ++If you have multi-factor authentication set up on your Gmail account, you\n    ++can generate an app-specific password for use with `git imap-send`.\n    ++Visit https://security.google.com/settings/security/apppasswords to create\n    ++it. Alternatively, use OAuth2.0 authentication as described below.\n    ++\n    + [NOTE]\n    + You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    + that the \"Folder doesn't exist\".\n    +@@ Documentation/git-imap-send.adoc: that the \"Folder doesn't exist\".\n    + If your Gmail account is set to another language than English, the name of the \"Drafts\"\n    + folder will be localized.\n    + \n    ++If you want to use OAuth2.0 based authentication, you can specify\n    ++`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n    ++than using app-specific passwords, and also does not enforce the need of\n    ++having multi-factor authentication. You will have to use an OAuth2.0\n    ++access token in place of your password when using this authentication.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    ++    authmethod = OAUTHBEARER\n    ++---------\n    ++\n    ++Using Outlook's IMAP interface:\n    ++\n    ++Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n    ++supports only `XOAUTH2` as the mechanism.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"Drafts\"\n    ++    host = imaps://outlook.office365.com\n    ++    user = user@outlook.com\n    ++    port = 993\n    ++    authmethod = XOAUTH2\n    ++---------\n    ++\n    + Once the commits are ready to be sent, run the following command:\n    + \n    +   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n    +@@ Documentation/git-imap-send.adoc: Just make sure to disable line wrapping in the email client (Gmail's web\n    + interface will wrap lines no matter what, so you need to use a real\n    + IMAP client).\n    + \n    ++In case you are using OAuth2.0 authentication, it is easier to use credential\n    ++helpers to generate tokens. Credential helpers suggested in\n    ++linkgit:git-send-email[1] can be used for `git imap-send` as well.\n    ++\n    + CAUTION\n    + -------\n    + It is still your responsibility to make sure that the email message\n     \n      ## imap-send.c ##\n    -@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n    +@@ imap-send.c: enum CAPABILITY {\n    + \tLITERALPLUS,\n    + \tNAMESPACE,\n    + \tSTARTTLS,\n    +-\tAUTH_CRAM_MD5\n    ++\tAUTH_CRAM_MD5,\n    ++\tAUTH_OAUTHBEARER,\n    ++\tAUTH_XOAUTH2,\n    + };\n    + \n    + static const char *cap_list[] = {\n    +@@ imap-send.c: static const char *cap_list[] = {\n    + \t\"NAMESPACE\",\n    + \t\"STARTTLS\",\n    + \t\"AUTH=CRAM-MD5\",\n    ++\t\"AUTH=OAUTHBEARER\",\n    ++\t\"AUTH=XOAUTH2\",\n    + };\n    + \n    + #define RESP_OK    0\n    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    + \treturn (char *)response_64;\n    + }\n    + \n    ++static char *oauthbearer_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the OAUTHBEARER string\n    ++\t *\n    ++\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n    ++\t *\n    ++\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n    ++\t * * gs2-cb-flag `n` -> client does not support CB\n    ++\t * * gs2-authzid `a=\" {User} \"`\n    ++\t *\n    ++\t * The second part are key value pairs containing host, port and auth as\n    ++\t * described in RFC7628.\n    ++\t *\n    ++\t * https://datatracker.ietf.org/doc/html/rfc5801\n    ++\t * https://datatracker.ietf.org/doc/html/rfc7628\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    ++static char *xoauth2_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the XOAUTH2 string\n    ++\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n    ++\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + {\n    + \tint ret;\n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \treturn 0;\n    + }\n    + \n    ++static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n    ++\t}\n    ++\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n    ++\t}\n    ++\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    + #else\n    + \n    + #define auth_cram_md5 NULL\n    ++#define auth_oauthbearer NULL\n    ++#define auth_xoauth2 NULL\n    + \n    + #endif\n    + \n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n    + \t\t\t\t\tgoto bail;\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n    ++\t\t\t\t\tgoto bail;\n    + \t\t\t} else {\n    + \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n    + \t\t\t\tgoto bail;\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \n    + \tserver_fill_credential(srvc, cred);\n    + \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n    +-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n    ++\n    ++\t/*\n    ++\t * Use CURLOPT_PASSWORD irrespective of whether there is\n    ++\t * an auth method specified or not, unless it's OAuth2.0,\n    ++\t * where we use CURLOPT_XOAUTH2_BEARER.\n    ++\t */\n    ++\tif (!srvc->auth_method ||\n    ++\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n    ++\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n    ++\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n    + \n    + \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n    + \tstrbuf_addstr(&path, srvc->host);\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n    + \n    + \tif (srvc->auth_method) {\n    +-\t\tstruct strbuf auth = STRBUF_INIT;\n    +-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    +-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    +-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    +-\t\tstrbuf_release(&auth);\n    ++\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n    ++\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\n    ++\t\t\t/*\n    ++\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n    ++\t\t\t * upon debugging, it has been found that it is capable of detecting\n    ++\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n    ++\t\t\t */\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n    ++\t\t} else {\n    ++\t\t\tstruct strbuf auth = STRBUF_INIT;\n    ++\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    ++\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    ++\t\t\tstrbuf_release(&auth);\n    ++\t\t}\n      \t}\n    - \tctx->name = server->folder;\n    - \n    --\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n    -+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \n    -@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    - \tcurl = setup_curl(server, &cred);\n    - \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n    - \n    --\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n    -+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \t\tint prev_len;\n    + \n    + \tif (!srvc->use_ssl)\n -:  ---------- >  5:  86d3d2c54d imap-send: add PLAIN authentication method to OpenSSL\n -:  ---------- >  6:  7674e749c8 imap-send: enable specifying the folder using the command line\n 4:  4d9a3b5661 !  7:  90ce3a63f3 imap-send: add ability to list the available folders\n    @@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server\n     +\n      \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n      \n    - \tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n    + \tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n     @@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n      \n      \treturn res != CURLE_OK;\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\tserver.port = server.use_ssl ? 993 : 143;\n      \n     -\tif (!server.folder) {\n    --\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n    +-\t\tfprintf(stderr, \"no imap store specified\\n\");\n     -\t\tret = 1;\n     -\t\tgoto out;\n     -\t}\n      \tif (!server.host) {\n      \t\tif (!server.tunnel) {\n    - \t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n    + \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\tserver.host = xstrdup(\"tunnel\");\n      \t}\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n     +\t}\n     +\n     +\tif (!server.folder) {\n    -+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n    ++\t\tfprintf(stderr, \"no imap store specified\\n\");\n     +\t\tret = 1;\n     +\t\tgoto out;\n     +\t}\n 2:  b2e7ef35ed =  8:  1bdd054908 imap-send: display port alongwith host when git credential is invoked\n -:  ---------- >  9:  e381120ab5 imap-send: display the destination mailbox when sending a message\n 1:  a67322ce06 ! 10:  6561d45bee imap-send: fix minor mistakes in the logs\n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n      \t\t\t\t\tsrvc->user, srvc->host);\n      \t\t\t\tgoto bail;\n      \t\t\t}\n    -@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,\n    - \t}\n    - \tctx->name = server->folder;\n    - \n    --\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \n    -@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n    - \tcurl = setup_curl(server, &cred);\n    - \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n    - \n    --\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    -+\tfprintf(stderr, \"Sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n    - \twhile (1) {\n    - \t\tunsigned percent = n * 100 / total;\n    - \t\tint prev_len;\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n    - \t\tserver.port = server.use_ssl ? 993 : 143;\n      \n    - \tif (!server.folder) {\n    --\t\tfprintf(stderr, \"no imap store specified\\n\");\n    -+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n    - \t\tret = 1;\n    - \t\tgoto out;\n    - \t}\n      \tif (!server.host) {\n      \t\tif (!server.tunnel) {\n     -\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n    @@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \t\t\tgoto out;\n      \t\t}\n     @@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + \t}\n    + \n    + \tif (!server.folder) {\n    +-\t\tfprintf(stderr, \"no imap store specified\\n\");\n    ++\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n    + \t\tret = 1;\n    + \t\tgoto out;\n    + \t}\n    +@@ imap-send.c: int cmd_main(int argc, const char **argv)\n      \n      \ttotal = count_messages(&all_msgs);\n      \tif (!total) {\n-- \n2.49.0\n\n"},{"id":"519954","messageId":"PN3PR01MB9597ACDBA3FF42D348336C42B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:32Z","receivedAt":"2025-06-09T07:21:49Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 27dc033c7f..37f94a37e8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"519955","messageId":"PN3PR01MB95976D757945B4984A73E406B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 02/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:33Z","receivedAt":"2025-06-09T07:21:52Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 37f94a37e8..1a582c8443 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -905,8 +905,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"519956","messageId":"PN3PR01MB9597E68428E7483061E39A63B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:34Z","receivedAt":"2025-06-09T07:21:54Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 66 +++++++++++++++++++++++++++++++----------------------\n 1 file changed, 39 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 1a582c8443..f55399cd9e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -885,18 +885,6 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n-#else\n-\n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n-#endif\n-\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -915,6 +903,12 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+#else\n+\n+#define auth_cram_md5 NULL\n+\n+#endif\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -934,6 +928,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1089,23 +1115,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0\n\n"},{"id":"519957","messageId":"PN3PR01MB9597F9E0E8C6E333C023721BB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 05/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:36Z","receivedAt":"2025-06-09T07:21:57Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 63 +++++++++++++++++++++++++++++++++-\n 2 files changed, 64 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 5373f18b94..c6e47ddc42 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1011,6 +1068,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n+#define auth_plain NULL\n #define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n@@ -1223,7 +1281,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n-- \n2.49.0\n\n"},{"id":"519958","messageId":"PN3PR01MB95975D9E41AF28D2C67BF0C0B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:35Z","receivedAt":"2025-06-09T07:21:57Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++++-\n imap-send.c                      | 148 +++++++++++++++++++++++++++++--\n 3 files changed, 187 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex f55399cd9e..5373f18b94 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n #define auth_cram_md5 NULL\n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n \n #endif\n \n@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"519959","messageId":"PN3PR01MB9597AA90D615E2DEBF62220DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:37Z","receivedAt":"2025-06-09T07:21:59Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex c6e47ddc42..a4cccb9110 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0\n\n"},{"id":"519960","messageId":"PN3PR01MB9597440624DB4F9871F069FAB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 07/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:38Z","receivedAt":"2025-06-09T07:22:00Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex a4cccb9110..f03a92a2fb 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1579,6 +1585,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1612,11 +1638,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1647,10 +1675,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1669,6 +1693,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n@@ -1714,6 +1742,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1754,11 +1807,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n@@ -1768,6 +1816,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0\n\n"},{"id":"519961","messageId":"PN3PR01MB9597AF90BA3D4B3295ECC278B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:39Z","receivedAt":"2025-06-09T07:22:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex f03a92a2fb..9807012169 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1089,7 +1089,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0\n\n"},{"id":"519962","messageId":"PN3PR01MB9597647A1FE9451BF9EB1C6DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:40Z","receivedAt":"2025-06-09T07:22:02Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 9807012169..3d6bcd7e88 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1563,7 +1563,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1699,7 +1700,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0\n\n"},{"id":"519963","messageId":"PN3PR01MB9597BFCC2F5B5E8247C23A7AB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95976572C3B14C983802ECC1B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v16 10/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:20:41Z","receivedAt":"2025-06-09T07:22:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 24 ++++++++++++------------\n 1 file changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3d6bcd7e88..16c2e641ac 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -211,7 +211,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1026,7 +1026,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1166,7 +1166,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1185,7 +1185,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1237,7 +1237,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1249,7 +1249,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1302,12 +1302,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1811,7 +1811,7 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1831,7 +1831,7 @@ int cmd_main(int argc, const char **argv)\n \t}\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n@@ -1851,7 +1851,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0\n\n"},{"id":"519964","messageId":"PN3PR01MB9597614BED5F4940D1B807DDB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqcybevtxw.fsf@gitster.g","subject":"Re: [PATCH v15 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T07:23:34Z","receivedAt":"2025-06-09T07:23:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 9 Jun 2025, at 2:21 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Aditya Garg (10):\n>>  imap-send: fix bug causing cfg->folder being set to NULL\n>>  imap-send: fix memory leak in case auth_cram_md5 fails\n>>  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>>    without OpenSSL\n>>  imap-send: add support for OAuth2.0 authentication\n>>  imap-send: add PLAIN authentication method to OpenSSL\n>>  imap-send: enable specifying the folder using the command line\n>>  imap-send: fix minor mistakes in the logs\n>>  imap-send: display port alongwith host when git credential is invoked\n>>  imap-send: display the destination mailbox when sending a message\n>>  imap-send: add ability to list the available folders\n> \n> Very nicely organized, starting from obvious fixes and usability\n> polishing at the beginning of the series, followed by feature\n> enhancements to add new variants to the existing framework, followed\n> by three new features.  The \"ok\"->\"OK\" change seems somewhat out of\n> place (I would have done it early if the changes are not controversial,\n> or very late after the series if the changes looked merely subjective,\n> and not in the middle either case), but other than that the series is\n> now organized perfectly.\n> \n\nFixed in v16\n\n> I think the \"fixes\" and \"auth method enhancements\" in the earlier\n> part are the same as before and I was happy with the resulting code.\n> \n> I didn't seriously read the last three or four patches during the\n> previous round, so I would say they still need reviews, but the\n> early part of the series now looks very well.\n> \n> Thanks.\n> \n"},{"id":"519971","messageId":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:19Z","receivedAt":"2025-06-09T15:42:35Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"v2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\nv15: - Simply rearrange the patches to make the cram md5 patches come\n       before adding OAuth2.0 and PLAIN authentication methods. No \n       change has been done to the code itself.\nv16: - Rearrage some more patches so that the two new features, i.e.,\n       --folder and --list come just after the new authentication\n       methods. Then the two patches with minor improvements of displaying\n       the destination mailbox and displaying port alongwith host have\n       been added. The patch fixing other minor mistakes in the logs has\n       been moved to the end. Just like v15, no change has been done\n       to the code itself.\nv17: - Rebase on top of master where 30325e2 was causing a conflict.\n       (Sorry for the bad range diff, but I think its easy to understand)\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: add ability to list the available folders\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: fix minor mistakes in the logs\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 412 ++++++++++++++++++++++++++-----\n 3 files changed, 414 insertions(+), 77 deletions(-)\n\nRange-diff against v16:\n 1:  194d108e15 <  -:  ---------- builtin/am: fix memory leak in `split_mail_stgit_series`\n 2:  798369e8ce <  -:  ---------- t1001: replace 'test -f' with 'test_path_is_file'\n 3:  dce2b90fb1 <  -:  ---------- oidmap: rename oidmap_free() to oidmap_clear()\n 4:  9369c83cce <  -:  ---------- oidmap: add size function\n 5:  f0a73c8578 <  -:  ---------- raw_object_store: drop extra pointer to replace_map\n 6:  c4b2850438 <  -:  ---------- reftable/writer: fix memory leak when `padded_write()` fails\n 7:  a795acc6ed <  -:  ---------- reftable/writer: fix memory leak when `writer_index_hash()` fails\n 8:  8fdb6df271 <  -:  ---------- reftable: fix perf regression when reading blocks of unwanted type\n 9:  ce0b8c96b9 <  -:  ---------- The sixteenth batch\n10:  3e3ddf7077 =  1:  4accbe6ecf imap-send: fix bug causing cfg->folder being set to NULL\n11:  417b3b8e38 =  2:  1cfd66ccea imap-send: fix memory leak in case auth_cram_md5 fails\n12:  c4216528e7 =  3:  12ff5135be imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n13:  b38fca0e6a !  4:  43b18dbfb0 imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n      \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n      \tstrbuf_addstr(&path, srvc->host);\n     @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    - \tcurl_easy_setopt(curl, CURLOPT_PORT, srvc->port);\n    + \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n      \n      \tif (srvc->auth_method) {\n     -\t\tstruct strbuf auth = STRBUF_INIT;\n14:  86d3d2c54d =  5:  1ebf9f935f imap-send: add PLAIN authentication method to OpenSSL\n15:  7674e749c8 =  6:  0c6283407c imap-send: enable specifying the folder using the command line\n16:  90ce3a63f3 !  7:  f59cb1dca1 imap-send: add ability to list the available folders\n    @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct crede\n      \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n      \tstrbuf_release(&path);\n     @@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    - \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify);\n    - \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify);\n    + \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify);\n    + \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)srvc->ssl_verify);\n      \n     -\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n     -\n17:  1bdd054908 =  8:  1247afbe78 imap-send: display port alongwith host when git credential is invoked\n18:  e381120ab5 =  9:  c30ecbf508 imap-send: display the destination mailbox when sending a message\n19:  6561d45bee = 10:  eaff4db692 imap-send: fix minor mistakes in the logs\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519972","messageId":"PN3PR01MB9597BFD435427C72DB2CCFEBB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:20Z","receivedAt":"2025-06-09T15:42:42Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 2e812f5a6e..3eed2360fd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519973","messageId":"PN3PR01MB95973CB65DC0DC3164D933BEB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 02/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:21Z","receivedAt":"2025-06-09T15:42:44Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3eed2360fd..cee8f5690d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -905,8 +905,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519974","messageId":"PN3PR01MB959726E51CACC5CCD4B57A4BB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:22Z","receivedAt":"2025-06-09T15:42:46Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 66 +++++++++++++++++++++++++++++++----------------------\n 1 file changed, 39 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex cee8f5690d..39013330a7 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -885,18 +885,6 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n-#else\n-\n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n-#endif\n-\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -915,6 +903,12 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+#else\n+\n+#define auth_cram_md5 NULL\n+\n+#endif\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -934,6 +928,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1089,23 +1115,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519975","messageId":"PN3PR01MB959774BAFEE6933B8FD9A607B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:23Z","receivedAt":"2025-06-09T15:42:46Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++++-\n imap-send.c                      | 148 +++++++++++++++++++++++++++++--\n 3 files changed, 187 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 39013330a7..24eab86a1a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n #define auth_cram_md5 NULL\n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n \n #endif\n \n@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519976","messageId":"PN3PR01MB9597EF544FEE5D3C744948C9B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:25Z","receivedAt":"2025-06-09T15:42:49Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 64f66ec67d..522d01c88e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519977","messageId":"PN3PR01MB95978AD187B9B205BCEADF44B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 05/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:24Z","receivedAt":"2025-06-09T15:42:49Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 63 +++++++++++++++++++++++++++++++++-\n 2 files changed, 64 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 24eab86a1a..64f66ec67d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1011,6 +1068,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n+#define auth_plain NULL\n #define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n@@ -1223,7 +1281,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519978","messageId":"PN3PR01MB9597EA21705B87F9F5B83A9AB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:27Z","receivedAt":"2025-06-09T15:42:51Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nFWIW, if no port is specified by the user, the default port, 993 for\nIMAPS and 143 for IMAP is used by the code. So, the case of no port\ndefined for the helper is not possible, and therefore is not added.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 4ac0ba606c..da85a6ee9e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1089,7 +1089,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519979","messageId":"PN3PR01MB9597374F0302B72B71574CB8B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 07/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:26Z","receivedAt":"2025-06-09T15:42:51Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 522d01c88e..4ac0ba606c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders = 0;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder = NULL;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1579,6 +1585,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1612,11 +1638,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1647,10 +1675,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1669,6 +1693,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n@@ -1714,6 +1742,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1754,11 +1807,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n@@ -1768,6 +1816,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519980","messageId":"PN3PR01MB9597440B6C555EBB31A5069AB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:28Z","receivedAt":"2025-06-09T15:42:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex da85a6ee9e..7d5df3d049 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1563,7 +1563,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1699,7 +1700,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519981","messageId":"PN3PR01MB959755A74E971E58C916E3FFB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB95979FBB320861CEE35C7F3DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v17 10/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T15:41:29Z","receivedAt":"2025-06-09T15:42:53Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAlso, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 24 ++++++++++++------------\n 1 file changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 7d5df3d049..f465a51213 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -211,7 +211,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1026,7 +1026,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1166,7 +1166,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1185,7 +1185,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1237,7 +1237,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1249,7 +1249,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1302,12 +1302,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1811,7 +1811,7 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1831,7 +1831,7 @@ int cmd_main(int argc, const char **argv)\n \t}\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n@@ -1851,7 +1851,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.824.geaff4db692\n\n"},{"id":"519987","messageId":"xmqqwm9krchz.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597AA90D615E2DEBF62220DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 06/10] imap-send: enable specifying the folder using the command line","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T18:33:28Z","receivedAt":"2025-06-09T18:33:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Some users may very often want to imap-send messages to a folder\n> other than the default set in the config. Add a command line\n> argument for the same.\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  Documentation/config/imap.adoc   |  6 ++++--\n>  Documentation/git-imap-send.adoc | 15 +++++++++++----\n>  imap-send.c                      |  9 ++++++++-\n>  3 files changed, 23 insertions(+), 7 deletions(-)\n>\n> diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\n> index 7c8b2dcce4..4682a6bd03 100644\n> --- a/Documentation/config/imap.adoc\n> +++ b/Documentation/config/imap.adoc\n> @@ -1,7 +1,9 @@\n>  imap.folder::\n>  \tThe folder to drop the mails into, which is typically the Drafts\n> -\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n> -\t\"[Gmail]/Drafts\". Required.\n> +\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n> +\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n> +\tthe value of this configuration variable is used as the fallback\n> +\tdefault value when the `--folder` option is not given.\n>  \n>  imap.tunnel::\n>  \tCommand used to set up a tunnel to the IMAP server through which\n> diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\n> index 8adf0e5aac..4a0487b66e 100644\n> --- a/Documentation/git-imap-send.adoc\n> +++ b/Documentation/git-imap-send.adoc\n> @@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git imap-send' [-v] [-q] [--[no-]curl]\n> +'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n\nThis matches the _usage[] string.  Excellent.\n\n>  DESCRIPTION\n>  -----------\n> -This command uploads a mailbox generated with 'git format-patch'\n> +This command uploads a mailbox generated with `git format-patch`\n>  into an IMAP drafts folder.  This allows patches to be sent as\n>  other email is when using mail clients that cannot read mailbox\n>  files directly. The command also works with any general mailbox\n> -in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n> +in which emails have the fields `From`, `Date`, and `Subject` in\n>  that order.\n>  \n>  Typical usage is something like:\n>  \n> -git format-patch --signoff --stdout --attach origin | git imap-send\n> +------\n> +$ git format-patch --signoff --stdout --attach origin | git imap-send\n> +------\n\nThe above is small enough that it is OK to make the change\nwhile-at-it, but it deserves a brief mention in the proposed log\nmessage (e.g. \"While at it, fix minor mark-up inconsistencies in the\nexisting documentation text\").\n\n> @@ -37,6 +39,11 @@ OPTIONS\n>  --quiet::\n>  \tBe quiet.\n>  \n> +-f <folder>::\n> +--folder=<folder>::\n> +\tSpecify the folder in which the emails have to saved.\n> +\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n\nGood.\n\n> diff --git a/imap-send.c b/imap-send.c\n> index c6e47ddc42..a4cccb9110 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -46,12 +46,14 @@\n>  \n>  static int verbosity;\n>  static int use_curl = USE_CURL_DEFAULT;\n> +static char *opt_folder = NULL;\n\nLet's lose \"= NULL\" here.\n\nDo not explicitly initialize globals to 0 or NULL; let BSS take care\nof the zero initialization, instead.\n\n> -static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n> +static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n>  \n>  static struct option imap_send_options[] = {\n>  \tOPT__VERBOSITY(&verbosity),\n>  \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n> +\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n>  \tOPT_END()\n>  };\n>  \n> @@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n>  \n>  \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n>  \n> +\tif (opt_folder) {\n> +\t\tfree(server.folder);\n> +\t\tserver.folder = xstrdup(opt_folder);\n> +\t}\n\nGood.  This matches the same care taken on the configuration side\nthat avoids leaking the value previously given.\n\n"},{"id":"519991","messageId":"xmqqh60orc2x.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597440624DB4F9871F069FAB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 07/10] imap-send: add ability to list the available folders","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T18:42:30Z","receivedAt":"2025-06-09T18:42:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> diff --git a/imap-send.c b/imap-send.c\n> index a4cccb9110..f03a92a2fb 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -45,15 +45,21 @@\n>  #endif\n>  \n>  static int verbosity;\n> +static int list_folders = 0;\n\nLet's lose \" = 0\" here.\n\nDo not explicitly initialize globals to 0 or NULL; let BSS take care\nof the zero initialization, instead.\n\n> -static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n> +static char const * const imap_send_usage[] = {\n> +\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n> +\t\"git imap-send --list\",\n> +\tNULL\n> +};\n\nGood.\n"},{"id":"519994","messageId":"xmqq8qm0rbgo.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597AF90BA3D4B3295ECC278B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T18:55:51Z","receivedAt":"2025-06-09T18:55:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> FWIW, if no port is specified by the user, the default port, 993 for\n> IMAPS and 143 for IMAP is used by the code. So, the case of no port\n> defined for the helper is not possible, and therefore is not added.\n\nShouldn't we do a bit better than being so pessimistic?\n\nIf the user left the port unspecified, or if the more knowledgeable\nuser redundantly specified the default port explicitly, showing to\nsuch a user :993 for imaps at the end adds no useful information.\n\n>  \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n> -\tcred->host = xstrdup(srvc->host);\n\nPerhaps something like\n\n\tif ((srvc->use_ssl ? 993 : 143) == srvc->port)\n        \tcred->host = xstrdup(srvc->host);\n\telse\n\nhere?\n\n> +\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n\n\n>  \n>  \tcred->username = xstrdup_or_null(srvc->user);\n>  \tcred->password = xstrdup_or_null(srvc->pass);\n"},{"id":"519995","messageId":"xmqq4iworbef.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597647A1FE9451BF9EB1C6DB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T18:57:12Z","receivedAt":"2025-06-09T18:57:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> Whenever we sent a message using the `imap-send` command, it would\n> display a log showing the number of messages which are to be sent.\n> For example:\n>\n>     sending 1 message\n>      100% (1/1) done\n>\n> This had been made more informative by adding the name of the destination\n> folder as well:\n>\n>     Sending 1 message to Drafts folder...\n>      100% (1/1) done\n>\n> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n> ---\n>  imap-send.c | 6 ++++--\n>  1 file changed, 4 insertions(+), 2 deletions(-)\n\nHmph, I have to wonder how much value this adds.  It is not like we\nextended imap-send to allow it to stuff messages to multiple imap\nfolders during the same session (in which case, \"sending ... to A\"\nfollowed by \"sending ... to B\" may give a good feel of progress).\n\nBut that is minor, not an objection strong enough to shoot down a\npiece of code that has already been written.  Capitalizing \"Sending\"\ncertainly is a vast cosmetic improvement ;-).\n\n> diff --git a/imap-send.c b/imap-send.c\n> index 9807012169..3d6bcd7e88 100644\n> --- a/imap-send.c\n> +++ b/imap-send.c\n> @@ -1563,7 +1563,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n>  \t}\n>  \tctx->name = server->folder;\n>  \n> -\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n> +\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n> +\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n\nTotally outside the topic, but as #leftoverbits we may want to i18n/l10n\nthe messages from this program after the dust settles from this series.\n"},{"id":"519996","messageId":"PN3PR01MB959765D8A6621F155F7C756FB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqq8qm0rbgo.fsf@gitster.g","subject":"Re: [PATCH v16 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T19:02:43Z","receivedAt":"2025-06-09T19:02:48Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 10 Jun 2025, at 12:26 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> FWIW, if no port is specified by the user, the default port, 993 for\n>> IMAPS and 143 for IMAP is used by the code. So, the case of no port\n>> defined for the helper is not possible, and therefore is not added.\n> \n> Shouldn't we do a bit better than being so pessimistic?\n> \n> If the user left the port unspecified, or if the more knowledgeable\n> user redundantly specified the default port explicitly, showing to\n> such a user :993 for imaps at the end adds no useful information.\n\nMaybe you misunderstood me? I want to show the port explicitly\njust like send-email. I think the FWIW line could be excluded, since\nit's more confusing the useful.\n\n> \n>>    cred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n>> -    cred->host = xstrdup(srvc->host);\n> \n> Perhaps something like\n> \n>    if ((srvc->use_ssl ? 993 : 143) == srvc->port)\n>            cred->host = xstrdup(srvc->host);\n>    else\n> \n> here?\n\nThat will not show the port if we specify the port as 993 as well then.\n\n> \n>> +    cred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n> \n> \n>> \n>>    cred->username = xstrdup_or_null(srvc->user);\n>>    cred->password = xstrdup_or_null(srvc->pass);\n"},{"id":"519997","messageId":"PN3PR01MB95970507E31975C5E9F0AC4AB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqq4iworbef.fsf@gitster.g","subject":"Re: [PATCH v16 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T19:05:36Z","receivedAt":"2025-06-09T19:05:42Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 10 Jun 2025, at 12:27 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> Whenever we sent a message using the `imap-send` command, it would\n>> display a log showing the number of messages which are to be sent.\n>> For example:\n>> \n>>    sending 1 message\n>>     100% (1/1) done\n>> \n>> This had been made more informative by adding the name of the destination\n>> folder as well:\n>> \n>>    Sending 1 message to Drafts folder...\n>>     100% (1/1) done\n>> \n>> Signed-off-by: Aditya Garg <gargaditya08@live.com>\n>> ---\n>> imap-send.c | 6 ++++--\n>> 1 file changed, 4 insertions(+), 2 deletions(-)\n> \n> Hmph, I have to wonder how much value this adds.  It is not like we\n> extended imap-send to allow it to stuff messages to multiple imap\n> folders during the same session (in which case, \"sending ... to A\"\n> followed by \"sending ... to B\" may give a good feel of progress).\n\nBut does give the information in cases with and without the -f argument.\n\n> \n> But that is minor, not an objection strong enough to shoot down a\n> piece of code that has already been written.  Capitalizing \"Sending\"\n> certainly is a vast cosmetic improvement ;-).\n\nPhew ;)\n> \n>> diff --git a/imap-send.c b/imap-send.c\n>> index 9807012169..3d6bcd7e88 100644\n>> --- a/imap-send.c\n>> +++ b/imap-send.c\n>> @@ -1563,7 +1563,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n>>    }\n>>    ctx->name = server->folder;\n>> \n>> -    fprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n>> +    fprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n>> +        total, (total != 1) ? \"s\" : \"\", server->folder);\n> \n> Totally outside the topic, but as #leftoverbits we may want to i18n/l10n\n> the messages from this program after the dust settles from this series.\n"},{"id":"519998","messageId":"xmqqzfegpvyu.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597E68428E7483061E39A63B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T19:15:53Z","receivedAt":"2025-06-09T19:15:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> +\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n>  \t\t\t\t\tgoto bail;\n> -\t\t\t\t}\n>  \t\t\t} else {\n>  \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n>  \t\t\t\tgoto bail;\n\nOutside the theme of this step (read: I am only leaving a mental\nnote as potential #leftoverbits; I do not want to see this fixed as\npart of this step) and probably outside the theme of this series,\nbut srvc->host is probably copy-and-paste-bug for srvc->auth_method\nI would think.\n"},{"id":"519999","messageId":"PN3PR01MB959747D02A97954CE46F4F27B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"xmqqzfegpvyu.fsf@gitster.g","subject":"Re: [PATCH v16 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T19:20:19Z","receivedAt":"2025-06-09T19:20:24Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\n> On 10 Jun 2025, at 12:46 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> \n> ﻿Aditya Garg <gargaditya08@live.com> writes:\n> \n>> +                if (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n>>                    goto bail;\n>> -                }\n>>            } else {\n>>                fprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n>>                goto bail;\n> \n> Outside the theme of this step (read: I am only leaving a mental\n> note as potential #leftoverbits; I do not want to see this fixed as\n> part of this step) and probably outside the theme of this series,\n> but srvc->host is probably copy-and-paste-bug for srvc->auth_method\n> I would think.\n\nGood catch. I can make this minor change a part of v18."},{"id":"520001","messageId":"xmqqv7p4pt95.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB959765D8A6621F155F7C756FB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v16 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-09T20:14:30Z","receivedAt":"2025-06-09T20:14:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n>> On 10 Jun 2025, at 12:26 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>> \n>> ﻿Aditya Garg <gargaditya08@live.com> writes:\n>> \n>>> FWIW, if no port is specified by the user, the default port, 993 for\n>>> IMAPS and 143 for IMAP is used by the code. So, the case of no port\n>>> defined for the helper is not possible, and therefore is not added.\n>> \n>> Shouldn't we do a bit better than being so pessimistic?\n>> \n>> If the user left the port unspecified, or if the more knowledgeable\n>> user redundantly specified the default port explicitly, showing to\n>> such a user :993 for imaps at the end adds no useful information.\n>\n> Maybe you misunderstood me? I want to show the port explicitly\n> just like send-email. I think the FWIW line could be excluded, since\n> it's more confusing the useful.\n\nYeah, I read your FWIW line to be saying \"even if we wanted to tell\ncases where the user left it unspecified and the user set it to a\nvalue that happens to be the same as the default, we have no way to\ntell (unless we add some code to record one more bit, that is), so\nwe punt and show port regardless.\"\n\n>> Perhaps something like\n>> \n>>    if ((srvc->use_ssl ? 993 : 143) == srvc->port)\n>>            cred->host = xstrdup(srvc->host);\n>>    else\n>> \n>> here?\n>\n> That will not show the port if we specify the port as 993 as well then.\n\nYes, that is exactly what I was saying---if the user set it to the\nport that is the default anyway, or more importantly, if the user\ndid not set, it is unnecessary and/or confusing to start showing the\nport number.\n\nIf the ISP uses something non-standard and the user explicitly sets\nit to that port, it may make sense to show it that the user is using\nsomething non-standard.\n\nHaving said that, I do not care too much either way---if we prefer\nto always show port, that's fine, but then the FWIW part definitely\nneeds to be rephrased to explain why it makes sense to show even the\ndefault port.\n\nThanks.\n\n"},{"id":"520004","messageId":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:45Z","receivedAt":"2025-06-09T20:23:30Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"v2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\nv15: - Simply rearrange the patches to make the cram md5 patches come\n       before adding OAuth2.0 and PLAIN authentication methods. No \n       change has been done to the code itself.\nv16: - Rearrage some more patches so that the two new features, i.e.,\n       --folder and --list come just after the new authentication\n       methods. Then the two patches with minor improvements of displaying\n       the destination mailbox and displaying port alongwith host have\n       been added. The patch fixing other minor mistakes in the logs has\n       been moved to the end. Just like v15, no change has been done\n       to the code itself.\nv17: - Rebase on top of master where 30325e2 was causing a conflict.\n       (Sorry for the bad range diff, but I think its easy to understand)\nv18: - Avoid initialising variables with 0 or NULL. Let them remain\n       uninitialised\n     - Add a white at it note to the commit message of the patch that\n       adds support to specify the folder.\n     - Add another minor fix to the log that displays the unknown auth\n       mechanism used. It was displaying the host rather than the mechanism.\n     - Remove unecessary and pessimistic lines from the patch that enabled\n       showing the host alongwith the port.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: add ability to list the available folders\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: fix minor mistakes in the logs\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 ++++-\n imap-send.c                      | 412 ++++++++++++++++++++++++++-----\n 3 files changed, 414 insertions(+), 77 deletions(-)\n\nRange-diff against v17:\n -:  ---------- >  1:  4accbe6ecf imap-send: fix bug causing cfg->folder being set to NULL\n -:  ---------- >  2:  1cfd66ccea imap-send: fix memory leak in case auth_cram_md5 fails\n -:  ---------- >  3:  12ff5135be imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n 1:  0c6283407c !  4:  43b18dbfb0 imap-send: enable specifying the folder using the command line\n    @@ Metadata\n     Author: Aditya Garg <gargaditya08@live.com>\n     \n      ## Commit message ##\n    -    imap-send: enable specifying the folder using the command line\n    +    imap-send: add support for OAuth2.0 authentication\n     \n    -    Some users may very often want to imap-send messages to a folder\n    -    other than the default set in the config. Add a command line\n    -    argument for the same.\n    +    OAuth2.0 is a new way of authentication supported by various email providers\n    +    these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\n    +    for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\n    +    XOAUTH2 is Google's proprietary mechanism (See [3]).\n    +\n    +    [1]: https://datatracker.ietf.org/doc/html/rfc5801\n    +    [2]: https://datatracker.ietf.org/doc/html/rfc7628\n    +    [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n     \n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n      ## Documentation/config/imap.adoc ##\n    -@@\n    - imap.folder::\n    - \tThe folder to drop the mails into, which is typically the Drafts\n    --\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n    --\t\"[Gmail]/Drafts\". Required.\n    -+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n    -+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n    -+\tthe value of this configuration variable is used as the fallback\n    -+\tdefault value when the `--folder` option is not given.\n    - \n    - imap.tunnel::\n    - \tCommand used to set up a tunnel to the IMAP server through which\n    +@@ Documentation/config/imap.adoc: imap.authMethod::\n    + \tSpecify the authentication method for authenticating with the IMAP server.\n    + \tIf Git was built with the NO_CURL option, or if your curl version is older\n    + \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n    +-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n    +-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n    ++\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n    ++\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n    ++\tplaintext `LOGIN` command.\n     \n      ## Documentation/git-imap-send.adoc ##\n    -@@ Documentation/git-imap-send.adoc: git-imap-send - Send a collection of patches from stdin to an IMAP folder\n    - SYNOPSIS\n    - --------\n    - [verse]\n    --'git imap-send' [-v] [-q] [--[no-]curl]\n    -+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n    - \n    - \n    - DESCRIPTION\n    - -----------\n    --This command uploads a mailbox generated with 'git format-patch'\n    -+This command uploads a mailbox generated with `git format-patch`\n    - into an IMAP drafts folder.  This allows patches to be sent as\n    - other email is when using mail clients that cannot read mailbox\n    - files directly. The command also works with any general mailbox\n    --in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n    -+in which emails have the fields `From`, `Date`, and `Subject` in\n    - that order.\n    - \n    - Typical usage is something like:\n    - \n    --git format-patch --signoff --stdout --attach origin | git imap-send\n    -+------\n    -+$ git format-patch --signoff --stdout --attach origin | git imap-send\n    -+------\n    - \n    - \n    - OPTIONS\n    -@@ Documentation/git-imap-send.adoc: OPTIONS\n    - --quiet::\n    - \tBe quiet.\n    - \n    -+-f <folder>::\n    -+--folder=<folder>::\n    -+\tSpecify the folder in which the emails have to saved.\n    -+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n    -+\n    - --curl::\n    - \tUse libcurl to communicate with the IMAP server, unless tunneling\n    - \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\n    -\n    - ## imap-send.c ##\n    -@@\n    +@@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:\n    + \n    + ---------\n    + [imap]\n    +-\tfolder = \"[Gmail]/Drafts\"\n    +-\thost = imaps://imap.gmail.com\n    +-\tuser = user@gmail.com\n    +-\tport = 993\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    + ---------\n    + \n    ++Gmail does not allow using your regular password for `git imap-send`.\n    ++If you have multi-factor authentication set up on your Gmail account, you\n    ++can generate an app-specific password for use with `git imap-send`.\n    ++Visit https://security.google.com/settings/security/apppasswords to create\n    ++it. Alternatively, use OAuth2.0 authentication as described below.\n    ++\n    + [NOTE]\n    + You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n    + that the \"Folder doesn't exist\".\n    +@@ Documentation/git-imap-send.adoc: that the \"Folder doesn't exist\".\n    + If your Gmail account is set to another language than English, the name of the \"Drafts\"\n    + folder will be localized.\n      \n    - static int verbosity;\n    - static int use_curl = USE_CURL_DEFAULT;\n    -+static char *opt_folder = NULL;\n    ++If you want to use OAuth2.0 based authentication, you can specify\n    ++`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n    ++than using app-specific passwords, and also does not enforce the need of\n    ++having multi-factor authentication. You will have to use an OAuth2.0\n    ++access token in place of your password when using this authentication.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"[Gmail]/Drafts\"\n    ++    host = imaps://imap.gmail.com\n    ++    user = user@gmail.com\n    ++    port = 993\n    ++    authmethod = OAUTHBEARER\n    ++---------\n    ++\n    ++Using Outlook's IMAP interface:\n    ++\n    ++Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n    ++supports only `XOAUTH2` as the mechanism.\n    ++\n    ++---------\n    ++[imap]\n    ++    folder = \"Drafts\"\n    ++    host = imaps://outlook.office365.com\n    ++    user = user@outlook.com\n    ++    port = 993\n    ++    authmethod = XOAUTH2\n    ++---------\n    ++\n    + Once the commits are ready to be sent, run the following command:\n      \n    --static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n    -+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n    +   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n    +@@ Documentation/git-imap-send.adoc: Just make sure to disable line wrapping in the email client (Gmail's web\n    + interface will wrap lines no matter what, so you need to use a real\n    + IMAP client).\n      \n    - static struct option imap_send_options[] = {\n    - \tOPT__VERBOSITY(&verbosity),\n    - \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n    -+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n    - \tOPT_END()\n    ++In case you are using OAuth2.0 authentication, it is easier to use credential\n    ++helpers to generate tokens. Credential helpers suggested in\n    ++linkgit:git-send-email[1] can be used for `git imap-send` as well.\n    ++\n    + CAUTION\n    + -------\n    + It is still your responsibility to make sure that the email message\n    +\n    + ## imap-send.c ##\n    +@@ imap-send.c: enum CAPABILITY {\n    + \tLITERALPLUS,\n    + \tNAMESPACE,\n    + \tSTARTTLS,\n    +-\tAUTH_CRAM_MD5\n    ++\tAUTH_CRAM_MD5,\n    ++\tAUTH_OAUTHBEARER,\n    ++\tAUTH_XOAUTH2,\n    + };\n    + \n    + static const char *cap_list[] = {\n    +@@ imap-send.c: static const char *cap_list[] = {\n    + \t\"NAMESPACE\",\n    + \t\"STARTTLS\",\n    + \t\"AUTH=CRAM-MD5\",\n    ++\t\"AUTH=OAUTHBEARER\",\n    ++\t\"AUTH=XOAUTH2\",\n      };\n      \n    -@@ imap-send.c: int cmd_main(int argc, const char **argv)\n    + #define RESP_OK    0\n    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)\n    + \treturn (char *)response_64;\n    + }\n      \n    - \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n    ++static char *oauthbearer_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the OAUTHBEARER string\n    ++\t *\n    ++\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n    ++\t *\n    ++\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n    ++\t * * gs2-cb-flag `n` -> client does not support CB\n    ++\t * * gs2-authzid `a=\" {User} \"`\n    ++\t *\n    ++\t * The second part are key value pairs containing host, port and auth as\n    ++\t * described in RFC7628.\n    ++\t *\n    ++\t * https://datatracker.ietf.org/doc/html/rfc5801\n    ++\t * https://datatracker.ietf.org/doc/html/rfc7628\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    ++static char *xoauth2_base64(const char *user, const char *access_token)\n    ++{\n    ++\tint raw_len, b64_len;\n    ++\tchar *raw, *b64;\n    ++\n    ++\t/*\n    ++\t * Compose the XOAUTH2 string\n    ++\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n    ++\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n    ++\t */\n    ++\traw_len = strlen(user) + strlen(access_token) + 20;\n    ++\traw = xmallocz(raw_len + 1);\n    ++\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\n    ++\t/* Base64 encode */\n    ++\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n    ++\tfree(raw);\n    ++\n    ++\tif (b64_len < 0) {\n    ++\t\tfree(b64);\n    ++\t\treturn NULL;\n    ++\t}\n    ++\treturn b64;\n    ++}\n    ++\n    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + {\n    + \tint ret;\n    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n    + \treturn 0;\n    + }\n      \n    -+\tif (opt_folder) {\n    -+\t\tfree(server.folder);\n    -+\t\tserver.folder = xstrdup(opt_folder);\n    ++static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n     +\t}\n     +\n    - \tif (argc)\n    - \t\tusage_with_options(imap_send_usage, imap_send_options);\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n    ++{\n    ++\tint ret;\n    ++\tchar *b64;\n    ++\n    ++\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n    ++\tif (!b64)\n    ++\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n    ++\n    ++\t/* Send the base64-encoded response */\n    ++\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n    ++\tif (ret != (int)strlen(b64)) {\n    ++\t\tfree(b64);\n    ++\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n    ++\t}\n    ++\n    ++\tfree(b64);\n    ++\treturn 0;\n    ++}\n    ++\n    + #else\n    + \n    + #define auth_cram_md5 NULL\n    ++#define auth_oauthbearer NULL\n    ++#define auth_xoauth2 NULL\n    + \n    + #endif\n    + \n    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n    + \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n    + \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n    + \t\t\t\t\tgoto bail;\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n    ++\t\t\t\t\tgoto bail;\n    ++\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n    ++\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n    ++\t\t\t\t\tgoto bail;\n    + \t\t\t} else {\n    + \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n    + \t\t\t\tgoto bail;\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \n    + \tserver_fill_credential(srvc, cred);\n    + \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n    +-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n    ++\n    ++\t/*\n    ++\t * Use CURLOPT_PASSWORD irrespective of whether there is\n    ++\t * an auth method specified or not, unless it's OAuth2.0,\n    ++\t * where we use CURLOPT_XOAUTH2_BEARER.\n    ++\t */\n    ++\tif (!srvc->auth_method ||\n    ++\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n    ++\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n    ++\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n    + \n    + \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n    + \tstrbuf_addstr(&path, srvc->host);\n    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n    + \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n    + \n    + \tif (srvc->auth_method) {\n    +-\t\tstruct strbuf auth = STRBUF_INIT;\n    +-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    +-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    +-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    +-\t\tstrbuf_release(&auth);\n    ++\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n    ++\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n    ++\n    ++\t\t\t/*\n    ++\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n    ++\t\t\t * upon debugging, it has been found that it is capable of detecting\n    ++\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n    ++\t\t\t */\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n    ++\t\t} else {\n    ++\t\t\tstruct strbuf auth = STRBUF_INIT;\n    ++\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n    ++\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n    ++\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n    ++\t\t\tstrbuf_release(&auth);\n    ++\t\t}\n    + \t}\n      \n    + \tif (!srvc->use_ssl)\n -:  ---------- >  5:  1ebf9f935f imap-send: add PLAIN authentication method to OpenSSL\n -:  ---------- >  6:  ce2cfa34cf imap-send: enable specifying the folder using the command line\n 2:  f59cb1dca1 !  7:  5c36e68493 imap-send: add ability to list the available folders\n    @@ imap-send.c\n      #endif\n      \n      static int verbosity;\n    -+static int list_folders = 0;\n    ++static int list_folders;\n      static int use_curl = USE_CURL_DEFAULT;\n    - static char *opt_folder = NULL;\n    + static char *opt_folder;\n      \n     -static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n     +static char const * const imap_send_usage[] = {\n 3:  1247afbe78 !  8:  cc4f88791f imap-send: display port alongwith host when git credential is invoked\n    @@ Commit message\n         Also, this behaviour will also mimic git send-email, which displays\n         the port along with the host name when requesting for a password.\n     \n    -    FWIW, if no port is specified by the user, the default port, 993 for\n    -    IMAPS and 143 for IMAP is used by the code. So, the case of no port\n    -    defined for the helper is not possible, and therefore is not added.\n    -\n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n      ## imap-send.c ##\n 4:  c30ecbf508 =  9:  82432c7b21 imap-send: display the destination mailbox when sending a message\n 5:  eaff4db692 ! 10:  d780afc026 imap-send: fix minor mistakes in the logs\n    @@ Commit message\n     \n         Some minor mistakes have been found in the logs. Most of them include\n         error messages starting with a capital letter, and ending with a period.\n    -    Also, abbreviations like \"IMAP\" and \"OK\" should be in uppercase. Fix them.\n    +    Abbreviations like \"IMAP\" and \"OK\" should also be in uppercase. Another\n    +    mistake was that the error message showing unknown authentication\n    +    mechanism used was displaying the host rather than the mechanism in the\n    +    logs. Fix them.\n     \n         Signed-off-by: Aditya Garg <gargaditya08@live.com>\n     \n    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *\n      \t\t\t\t\tgoto bail;\n      \t\t\t} else {\n     -\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n    -+\t\t\t\tfprintf(stderr, \"unknown authentication method:%s\\n\", srvc->host);\n    ++\t\t\t\tfprintf(stderr, \"unknown authentication mechanism: %s\\n\", srvc->auth_method);\n      \t\t\t\tgoto bail;\n      \t\t\t}\n      \t\t} else {\n-- \n2.49.0\n\n"},{"id":"520005","messageId":"PN3PR01MB95971E7AE1C3C2CBCEA5743EB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:46Z","receivedAt":"2025-06-09T20:23:33Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 2e812f5a6e..3eed2360fd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0\n\n"},{"id":"520006","messageId":"PN3PR01MB95971F19E9D30FAD7AF1A3B3B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:48Z","receivedAt":"2025-06-09T20:23:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 66 +++++++++++++++++++++++++++++++----------------------\n 1 file changed, 39 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex cee8f5690d..39013330a7 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -885,18 +885,6 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n-#else\n-\n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n-#endif\n-\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -915,6 +903,12 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+#else\n+\n+#define auth_cram_md5 NULL\n+\n+#endif\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -934,6 +928,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1089,23 +1115,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0\n\n"},{"id":"520007","messageId":"PN3PR01MB9597FAE32CD2C5EFFFE02F52B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 02/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:47Z","receivedAt":"2025-06-09T20:23:36Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3eed2360fd..cee8f5690d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -905,8 +905,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0\n\n"},{"id":"520008","messageId":"PN3PR01MB9597607108917195B9690F67B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:49Z","receivedAt":"2025-06-09T20:23:38Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++++-\n imap-send.c                      | 148 +++++++++++++++++++++++++++++--\n 3 files changed, 187 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 39013330a7..24eab86a1a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,68 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint raw_len, b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw_len = strlen(user) + strlen(access_token) + 20;\n+\traw = xmallocz(raw_len + 1);\n+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n #define auth_cram_md5 NULL\n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n \n #endif\n \n@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0\n\n"},{"id":"520009","messageId":"PN3PR01MB9597FE80DA7E0B4AC8928A08B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:51Z","receivedAt":"2025-06-09T20:23:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nWhile at it, fix minor mark-up inconsistencies in the existing\ndocumentation text.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex 64f66ec67d..be6412fe2d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1729,6 +1731,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0\n\n"},{"id":"520010","messageId":"PN3PR01MB959718E5A5AD5F9A577DA66FB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 05/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:50Z","receivedAt":"2025-06-09T20:23:40Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 63 +++++++++++++++++++++++++++++++++-\n 2 files changed, 64 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 24eab86a1a..64f66ec67d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,41 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tint user_len = strlen(user);\n+\tint pass_len = strlen(pass);\n+\tint raw_len = 1 + user_len + 1 + pass_len;\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\traw = xmallocz(raw_len);\n+\traw[0] = '\\0';\n+\tmemcpy(raw + 1, user, user_len);\n+\traw[1 + user_len] = '\\0';\n+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -951,6 +988,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1011,6 +1068,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n+#define auth_plain NULL\n #define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n@@ -1223,7 +1281,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n-- \n2.49.0\n\n"},{"id":"520011","messageId":"PN3PR01MB95979FE054BDCB5452DB446BB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:53Z","receivedAt":"2025-06-09T20:23:43Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 77cf2b3da2..a79e7c7da7 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1089,7 +1089,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0\n\n"},{"id":"520012","messageId":"PN3PR01MB9597427F9AE36ECE4C7FEF98B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 07/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:52Z","receivedAt":"2025-06-09T20:23:43Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex be6412fe2d..77cf2b3da2 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1579,6 +1585,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1612,11 +1638,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1647,10 +1675,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1669,6 +1693,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n@@ -1714,6 +1742,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1754,11 +1807,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n@@ -1768,6 +1816,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0\n\n"},{"id":"520013","messageId":"PN3PR01MB9597FF7B6893EF278EBE6F7AB86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:54Z","receivedAt":"2025-06-09T20:23:45Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex a79e7c7da7..fe4e2fbeb8 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1563,7 +1563,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1699,7 +1700,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0\n\n"},{"id":"520014","messageId":"PN3PR01MB9597DBBB7C5DDF403A627E96B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597929CF956CBB1B8B7D909B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v18 10/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-09T20:22:55Z","receivedAt":"2025-06-09T20:23:45Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAbbreviations like \"IMAP\" and \"OK\" should also be in uppercase. Another\nmistake was that the error message showing unknown authentication\nmechanism used was displaying the host rather than the mechanism in the\nlogs. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 24 ++++++++++++------------\n 1 file changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex fe4e2fbeb8..ed4c34dadd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -211,7 +211,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1026,7 +1026,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1166,7 +1166,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1185,7 +1185,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1223,7 +1223,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1237,7 +1237,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1249,7 +1249,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1302,12 +1302,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication mechanism: %s\\n\", srvc->auth_method);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1811,7 +1811,7 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1831,7 +1831,7 @@ int cmd_main(int argc, const char **argv)\n \t}\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n@@ -1851,7 +1851,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0\n\n"},{"id":"520310","messageId":"F0A06034-99B8-4BD1-9CDE-515A3EA430DA@gmail.com","threadId":"63502","inReplyTo":"PN3PR01MB9597607108917195B9690F67B86BA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v18 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-06-17T10:27:09Z","receivedAt":"2025-06-17T10:27:13Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"I'm not really on the list at the moment but I saw this was slated for next in what's cooking. Apologies if the formatting is off, I'm in my phone. \n\nOn 9 June 2025 21:22:49 BST, Aditya Garg <gargaditya08@live.com> wrote:\n> \n>+static char *oauthbearer_base64(const char *user, const char *access_token)\n>+{\n>+\tint raw_len, b64_len;\n>+\tchar *raw, *b64;\n>+\n>+\t/*\n>+\t * Compose the OAUTHBEARER string\n>+\t *\n>+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n>+\t *\n>+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n>+\t * * gs2-cb-flag `n` -> client does not support CB\n>+\t * * gs2-authzid `a=\" {User} \"`\n>+\t *\n>+\t * The second part are key value pairs containing host, port and auth as\n>+\t * described in RFC7628.\n>+\t *\n>+\t * https://datatracker.ietf.org/doc/html/rfc5801\n>+\t * https://datatracker.ietf.org/doc/html/rfc7628\n>+\t */\n>+\traw_len = strlen(user) + strlen(access_token) + 20;\n>+\traw = xmallocz(raw_len + 1);\n>+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n\nThis looks very fragile. It would be safer to use an strbuf or if there are no embedded nul bytes xstrfmt() and strlen(). This applies to the next patch as well and any others that are building strings with snprintf() or memcpy(). \n\nAlso the comment above mentions the host and port but I don't see them here.\n\nThanks\n\nPhillip\n\n>+\n>+\t/* Base64 encode */\n>+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n>+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n>+\tfree(raw);\n>+\n>+\tif (b64_len < 0) {\n>+\t\tfree(b64);\n>+\t\treturn NULL;\n>+\t}\n>+\treturn b64;\n>+}\n>+\n>+static char *xoauth2_base64(const char *user, const char *access_token)\n>+{\n>+\tint raw_len, b64_len;\n>+\tchar *raw, *b64;\n>+\n>+\t/*\n>+\t * Compose the XOAUTH2 string\n>+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n>+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n>+\t */\n>+\traw_len = strlen(user) + strlen(access_token) + 20;\n>+\traw = xmallocz(raw_len + 1);\n>+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n>+\n>+\t/* Base64 encode */\n>+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n>+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n>+\tfree(raw);\n>+\n>+\tif (b64_len < 0) {\n>+\t\tfree(b64);\n>+\t\treturn NULL;\n>+\t}\n>+\treturn b64;\n>+}\n>+\n> static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> {\n> \tint ret;\n>@@ -903,9 +969,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n> \treturn 0;\n> }\n> \n>+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n>+{\n>+\tint ret;\n>+\tchar *b64;\n>+\n>+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n>+\tif (!b64)\n>+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n>+\n>+\t/* Send the base64-encoded response */\n>+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n>+\tif (ret != (int)strlen(b64)) {\n>+\t\tfree(b64);\n>+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n>+\t}\n>+\n>+\tfree(b64);\n>+\treturn 0;\n>+}\n>+\n>+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n>+{\n>+\tint ret;\n>+\tchar *b64;\n>+\n>+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n>+\tif (!b64)\n>+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n>+\n>+\t/* Send the base64-encoded response */\n>+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n>+\tif (ret != (int)strlen(b64)) {\n>+\t\tfree(b64);\n>+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n>+\t}\n>+\n>+\tfree(b64);\n>+\treturn 0;\n>+}\n>+\n> #else\n> \n> #define auth_cram_md5 NULL\n>+#define auth_oauthbearer NULL\n>+#define auth_xoauth2 NULL\n> \n> #endif\n> \n>@@ -1118,6 +1226,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n> \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n> \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n> \t\t\t\t\tgoto bail;\n>+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n>+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n>+\t\t\t\t\tgoto bail;\n>+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n>+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n>+\t\t\t\t\tgoto bail;\n> \t\t\t} else {\n> \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n> \t\t\t\tgoto bail;\n>@@ -1419,7 +1533,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n> \n> \tserver_fill_credential(srvc, cred);\n> \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n>-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n>+\n>+\t/*\n>+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n>+\t * an auth method specified or not, unless it's OAuth2.0,\n>+\t * where we use CURLOPT_XOAUTH2_BEARER.\n>+\t */\n>+\tif (!srvc->auth_method ||\n>+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n>+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n>+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n> \n> \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n> \tstrbuf_addstr(&path, srvc->host);\n>@@ -1437,11 +1560,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n> \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n> \n> \tif (srvc->auth_method) {\n>-\t\tstruct strbuf auth = STRBUF_INIT;\n>-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n>-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n>-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n>-\t\tstrbuf_release(&auth);\n>+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n>+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n>+\n>+\t\t\t/*\n>+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n>+\t\t\t * upon debugging, it has been found that it is capable of detecting\n>+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n>+\t\t\t */\n>+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n>+\t\t} else {\n>+\t\t\tstruct strbuf auth = STRBUF_INIT;\n>+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n>+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n>+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n>+\t\t\tstrbuf_release(&auth);\n>+\t\t}\n> \t}\n> \n> \tif (!srvc->use_ssl)\n"},{"id":"520465","messageId":"PN3PR01MB9597DB18FF5C6C2C92EBDEE8B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"F0A06034-99B8-4BD1-9CDE-515A3EA430DA@gmail.com","subject":"Re: [PATCH v18 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T05:16:19Z","receivedAt":"2025-06-20T05:16:27Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"\n\nOn 17-06-2025 03:57 pm, Phillip Wood wrote:\n> I'm not really on the list at the moment but I saw this was slated for next in what's cooking. Apologies if the formatting is off, I'm in my phone. \n> \n> On 9 June 2025 21:22:49 BST, Aditya Garg <gargaditya08@live.com> wrote:\n>>\n>> +static char *oauthbearer_base64(const char *user, const char *access_token)\n>> +{\n>> +\tint raw_len, b64_len;\n>> +\tchar *raw, *b64;\n>> +\n>> +\t/*\n>> +\t * Compose the OAUTHBEARER string\n>> +\t *\n>> +\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n>> +\t *\n>> +\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n>> +\t * * gs2-cb-flag `n` -> client does not support CB\n>> +\t * * gs2-authzid `a=\" {User} \"`\n>> +\t *\n>> +\t * The second part are key value pairs containing host, port and auth as\n>> +\t * described in RFC7628.\n>> +\t *\n>> +\t * https://datatracker.ietf.org/doc/html/rfc5801\n>> +\t * https://datatracker.ietf.org/doc/html/rfc7628\n>> +\t */\n>> +\traw_len = strlen(user) + strlen(access_token) + 20;\n>> +\traw = xmallocz(raw_len + 1);\n>> +\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n> \n> This looks very fragile. It would be safer to use an strbuf or if there are no embedded nul bytes xstrfmt() and strlen(). This applies to the next patch as well and any others that are building strings with snprintf() or memcpy().\n\nOk\n \n> \n> Also the comment above mentions the host and port but I don't see them here.\n\nHost and port are optional. See section 3.1 here:\n\nhttps://datatracker.ietf.org/doc/html/rfc7628#section-3.1\n\n\nAlso, please add me to the Cc list. I do not read the mailing list quite often, and saw this in what's cooking.\n\nThanks\nAditya\n"},{"id":"520469","messageId":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597C5BC8528C0E068DDDA18B899A@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:23Z","receivedAt":"2025-06-20T06:40:52Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"v2:  - Added support for OAuth2.0 with curl.\n     - Fixed the memory leak in case auth_cram_md5 fails.\nv3:  - Improve wording in first patch\n     - Change misleading message if OAuth2.0 is used without OpenSSL\nv4:  - Add PLAIN authentication mechanism for OpenSSL\n     - Improved wording in the first patch a bit more\nv5:  - Add ability to specify destination folder using the command line\n     - Add ability to set a default between curl and openssl using the config\nv6:  - Fix minor mistakes in --folder documentation\nv7:  - Fix spelling and grammar mistakes in logs shown to the user when running imap-send\n     - Display port alongwith host when git credential is invoked and asks for a password\n     - Display the destination mailbox when sending a message\nv8:  - Drop the patch that enabled user to choose between libcurl and openssl using the config\n     - Add ability to list the available folders by adding a `--list` option\nv9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).\nv10: - Fix comment styles\n     - Fix failing tests\nv11: - Use lower case letters for the first word of a sendtence in an error message\n       and avoid using full stops at the end of a sentence.\nv12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods\n       if OpenSSL support is not compiled in, but is requested by the user.\n     - Use backticks for string literals.\n     - Wrap documentation text to 75 columns.\n     - End the last member of enum CAPABILITY with a trailing comma.\nv13: - Fix logic error which was using || instead of && when checking if\n       the authentication method is neither XOAUTH2 nor OAUTHBEARER.\nv14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0\n       methods using a comment.\n     - Add a function try_auth_method() to reduce code duplication\n       when trying to authenticate using a specific method.\nv15: - Simply rearrange the patches to make the cram md5 patches come\n       before adding OAuth2.0 and PLAIN authentication methods. No \n       change has been done to the code itself.\nv16: - Rearrage some more patches so that the two new features, i.e.,\n       --folder and --list come just after the new authentication\n       methods. Then the two patches with minor improvements of displaying\n       the destination mailbox and displaying port alongwith host have\n       been added. The patch fixing other minor mistakes in the logs has\n       been moved to the end. Just like v15, no change has been done\n       to the code itself.\nv17: - Rebase on top of master where 30325e2 was causing a conflict.\n       (Sorry for the bad range diff, but I think its easy to understand)\nv18: - Avoid initialising variables with 0 or NULL. Let them remain\n       uninitialised\n     - Add a white at it note to the commit message of the patch that\n       adds support to specify the folder.\n     - Add another minor fix to the log that displays the unknown auth\n       mechanism used. It was displaying the host rather than the mechanism.\n     - Remove unecessary and pessimistic lines from the patch that enabled\n       showing the host alongwith the port.\nv19: - Use xstrfmt() for OAuth2 strings and strbuf for PLAIN.\n\nAditya Garg (10):\n  imap-send: fix bug causing cfg->folder being set to NULL\n  imap-send: fix memory leak in case auth_cram_md5 fails\n  imap-send: gracefully fail if CRAM-MD5 authentication is requested\n    without OpenSSL\n  imap-send: add support for OAuth2.0 authentication\n  imap-send: add PLAIN authentication method to OpenSSL\n  imap-send: enable specifying the folder using the command line\n  imap-send: add ability to list the available folders\n  imap-send: display port alongwith host when git credential is invoked\n  imap-send: display the destination mailbox when sending a message\n  imap-send: fix minor mistakes in the logs\n\n Documentation/config/imap.adoc   |  11 +-\n Documentation/git-imap-send.adoc |  68 +++++-\n imap-send.c                      | 405 ++++++++++++++++++++++++++-----\n 3 files changed, 407 insertions(+), 77 deletions(-)\n\nRange-diff against v18:\n -:  ---------- >  1:  4accbe6ecf imap-send: fix bug causing cfg->folder being set to NULL\n -:  ---------- >  2:  1cfd66ccea imap-send: fix memory leak in case auth_cram_md5 fails\n -:  ---------- >  3:  12ff5135be imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL\n 1:  43b18dbfb0 !  4:  6461607abc imap-send: add support for OAuth2.0 authentication\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n      \n     +static char *oauthbearer_base64(const char *user, const char *access_token)\n     +{\n    -+\tint raw_len, b64_len;\n    ++\tint b64_len;\n     +\tchar *raw, *b64;\n     +\n     +\t/*\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\t * https://datatracker.ietf.org/doc/html/rfc5801\n     +\t * https://datatracker.ietf.org/doc/html/rfc7628\n     +\t */\n    -+\traw_len = strlen(user) + strlen(access_token) + 20;\n    -+\traw = xmallocz(raw_len + 1);\n    -+\tsnprintf(raw, raw_len + 1, \"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\traw = xstrfmt(\"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n     +\n     +\t/* Base64 encode */\n     +\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\n     +static char *xoauth2_base64(const char *user, const char *access_token)\n     +{\n    -+\tint raw_len, b64_len;\n    ++\tint b64_len;\n     +\tchar *raw, *b64;\n     +\n     +\t/*\n    @@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const\n     +\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n     +\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n     +\t */\n    -+\traw_len = strlen(user) + strlen(access_token) + 20;\n    -+\traw = xmallocz(raw_len + 1);\n    -+\tsnprintf(raw, raw_len + 1, \"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n    ++\traw = xstrfmt(\"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n     +\n     +\t/* Base64 encode */\n     +\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n 2:  1ebf9f935f !  5:  76745861e8 imap-send: add PLAIN authentication method to OpenSSL\n    @@ imap-send.c: static char hexchar(unsigned int b)\n      #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n     +static char *plain_base64(const char *user, const char *pass)\n     +{\n    -+\tint user_len = strlen(user);\n    -+\tint pass_len = strlen(pass);\n    -+\tint raw_len = 1 + user_len + 1 + pass_len;\n    ++\tstruct strbuf raw = STRBUF_INIT;\n     +\tint b64_len;\n    -+\tchar *raw, *b64;\n    ++\tchar *b64;\n     +\n     +\t/*\n     +\t * Compose the PLAIN string\n    @@ imap-send.c: static char hexchar(unsigned int b)\n     +\t *\n     +\t * https://datatracker.ietf.org/doc/html/rfc4616\n     +\t */\n    -+\traw = xmallocz(raw_len);\n    -+\traw[0] = '\\0';\n    -+\tmemcpy(raw + 1, user, user_len);\n    -+\traw[1 + user_len] = '\\0';\n    -+\tmemcpy(raw + 2 + user_len, pass, pass_len);\n    ++\tstrbuf_addch(&raw, '\\0');\n    ++\tstrbuf_addstr(&raw, user);\n    ++\tstrbuf_addch(&raw, '\\0');\n    ++\tstrbuf_addstr(&raw, pass);\n     +\n    -+\tb64 = xmallocz(ENCODED_SIZE(raw_len));\n    -+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len);\n    -+\tfree(raw);\n    ++\tb64 = xmallocz(ENCODED_SIZE(raw.len));\n    ++\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw.buf, raw.len);\n    ++\tstrbuf_release(&raw);\n     +\n     +\tif (b64_len < 0) {\n     +\t\tfree(b64);\n 3:  ce2cfa34cf =  6:  cb0857e36e imap-send: enable specifying the folder using the command line\n 4:  5c36e68493 =  7:  360aa72808 imap-send: add ability to list the available folders\n 5:  cc4f88791f =  8:  422db5f0f0 imap-send: display port alongwith host when git credential is invoked\n 6:  82432c7b21 =  9:  eaef39e6f1 imap-send: display the destination mailbox when sending a message\n 7:  d780afc026 = 10:  cc76007b2f imap-send: fix minor mistakes in the logs\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520470","messageId":"PN3PR01MB9597B7FCD8155FB6F5B39CD6B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 01/10] imap-send: fix bug causing cfg->folder being set to NULL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:24Z","receivedAt":"2025-06-20T06:40:55Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"6d1f198f34 (imap-send: fix leaking memory in `imap_server_conf`, 2024-06-07)\nresulted a change in static int git_imap_config which resulted in cfg->folder\nbeing incorrectly set to NULL in case imap.user, imap.pass, imap.tunnel and\nimap.authmethod were defined. Because of this, since Git 2.46.0,\ngit-imap-send is not usable at all. The bug seems to have been unnoticed for\na long time, likely due to better options like git-send-email.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 2e812f5a6e..3eed2360fd 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1316,16 +1316,16 @@ static int git_imap_config(const char *var, const char *val,\n \t\tFREE_AND_NULL(cfg->folder);\n \t\treturn git_config_string(&cfg->folder, var, val);\n \t} else if (!strcmp(\"imap.user\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->user);\n \t\treturn git_config_string(&cfg->user, var, val);\n \t} else if (!strcmp(\"imap.pass\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->pass);\n \t\treturn git_config_string(&cfg->pass, var, val);\n \t} else if (!strcmp(\"imap.tunnel\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->tunnel);\n \t\treturn git_config_string(&cfg->tunnel, var, val);\n \t} else if (!strcmp(\"imap.authmethod\", var)) {\n-\t\tFREE_AND_NULL(cfg->folder);\n+\t\tFREE_AND_NULL(cfg->auth_method);\n \t\treturn git_config_string(&cfg->auth_method, var, val);\n \t} else if (!strcmp(\"imap.port\", var)) {\n \t\tcfg->port = git_config_int(var, val, ctx->kvi);\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520471","messageId":"PN3PR01MB95972F977380A5125B0DAC8FB87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 02/10] imap-send: fix memory leak in case auth_cram_md5 fails","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:25Z","receivedAt":"2025-06-20T06:40:57Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"This patch fixes a memory leak by running free(response) in case\nauth_cram_md5 fails.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 3eed2360fd..cee8f5690d 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -905,8 +905,10 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tresponse = cram(prompt, ctx->cfg->user, ctx->cfg->pass);\n \n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n-\tif (ret != strlen(response))\n+\tif (ret != strlen(response)) {\n+\t\tfree(response);\n \t\treturn error(\"IMAP error: sending response failed\");\n+\t}\n \n \tfree(response);\n \n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520472","messageId":"PN3PR01MB9597CE7842A672B8FB1393F9B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 03/10] imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:26Z","receivedAt":"2025-06-20T06:40:59Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Unlike PLAIN, XOAUTH2 and OAUTHBEARER, CRAM-MD5 authentication is not\nsupported by libcurl and requires OpenSSL. If the user tries to use\nCRAM-MD5 authentication without OpenSSL, the previous behaviour was to\nattempt to authenticate and fail with a die(error). Handle this in a\nbetter way by first checking if OpenSSL is available and then attempting\nto authenticate. If OpenSSL is not available, print an error message and\nexit gracefully.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 66 +++++++++++++++++++++++++++++++----------------------\n 1 file changed, 39 insertions(+), 27 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex cee8f5690d..39013330a7 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -885,18 +885,6 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n-#else\n-\n-static char *cram(const char *challenge_64 UNUSED,\n-\t\t  const char *user UNUSED,\n-\t\t  const char *pass UNUSED)\n-{\n-\tdie(\"If you want to use CRAM-MD5 authenticate method, \"\n-\t    \"you have to build git-imap-send with OpenSSL library.\");\n-}\n-\n-#endif\n-\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -915,6 +903,12 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+#else\n+\n+#define auth_cram_md5 NULL\n+\n+#endif\n+\n static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)\n {\n \tif (srvc->user && srvc->pass)\n@@ -934,6 +928,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\tsrvc->pass = xstrdup(cred->password);\n }\n \n+static int try_auth_method(struct imap_server_conf *srvc,\n+\t\t\t   struct imap_store *ctx,\n+\t\t\t   struct imap *imap,\n+\t\t\t   const char *auth_method,\n+\t\t\t   enum CAPABILITY cap,\n+\t\t\t   int (*fn)(struct imap_store *, const char *))\n+{\n+\tstruct imap_cmd_cb cb = {0};\n+\n+\tif (!CAP(cap)) {\n+\t\tfprintf(stderr, \"You specified \"\n+\t\t\t\"%s as authentication method, \"\n+\t\t\t\"but %s doesn't support it.\\n\",\n+\t\t\tauth_method, srvc->host);\n+\t\treturn -1;\n+\t}\n+\tcb.cont = fn;\n+\n+\tif (NOT_CONSTANT(!cb.cont)) {\n+\t\tfprintf(stderr, \"If you want to use %s authentication mechanism, \"\n+\t\t\t\"you have to build git-imap-send with OpenSSL library.\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\tif (imap_exec(ctx, &cb, \"AUTHENTICATE %s\", auth_method) != RESP_OK) {\n+\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE %s failed\\n\",\n+\t\t\tauth_method);\n+\t\treturn -1;\n+\t}\n+\treturn 0;\n+}\n+\n static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)\n {\n \tstruct credential cred = CREDENTIAL_INIT;\n@@ -1089,23 +1115,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tstruct imap_cmd_cb cb;\n-\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n-\t\t\t\tif (!CAP(AUTH_CRAM_MD5)) {\n-\t\t\t\t\tfprintf(stderr, \"You specified \"\n-\t\t\t\t\t\t\"CRAM-MD5 as authentication method, \"\n-\t\t\t\t\t\t\"but %s doesn't support it.\\n\", srvc->host);\n-\t\t\t\t\tgoto bail;\n-\t\t\t\t}\n-\t\t\t\t/* CRAM-MD5 */\n-\n-\t\t\t\tmemset(&cb, 0, sizeof(cb));\n-\t\t\t\tcb.cont = auth_cram_md5;\n-\t\t\t\tif (imap_exec(ctx, &cb, \"AUTHENTICATE CRAM-MD5\") != RESP_OK) {\n-\t\t\t\t\tfprintf(stderr, \"IMAP error: AUTHENTICATE CRAM-MD5 failed\\n\");\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n-\t\t\t\t}\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520473","messageId":"PN3PR01MB95972DF1D2B4E1B74A027F94B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:27Z","receivedAt":"2025-06-20T06:41:01Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"OAuth2.0 is a new way of authentication supported by various email providers\nthese days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used\nfor OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas\nXOAUTH2 is Google's proprietary mechanism (See [3]).\n\n[1]: https://datatracker.ietf.org/doc/html/rfc5801\n[2]: https://datatracker.ietf.org/doc/html/rfc7628\n[3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |   5 +-\n Documentation/git-imap-send.adoc |  47 +++++++++-\n imap-send.c                      | 144 +++++++++++++++++++++++++++++--\n 3 files changed, 183 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 3d28f72643..29b998d5ff 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,5 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported method is 'CRAM-MD5'. If this is not set\n-\tthen 'git imap-send' uses the basic IMAP plaintext LOGIN command.\n+\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n+\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\tplaintext `LOGIN` command.\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 26ccf4e433..8adf0e5aac 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -102,12 +102,18 @@ Using Gmail's IMAP interface:\n \n ---------\n [imap]\n-\tfolder = \"[Gmail]/Drafts\"\n-\thost = imaps://imap.gmail.com\n-\tuser = user@gmail.com\n-\tport = 993\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n ---------\n \n+Gmail does not allow using your regular password for `git imap-send`.\n+If you have multi-factor authentication set up on your Gmail account, you\n+can generate an app-specific password for use with `git imap-send`.\n+Visit https://security.google.com/settings/security/apppasswords to create\n+it. Alternatively, use OAuth2.0 authentication as described below.\n+\n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n that the \"Folder doesn't exist\".\n@@ -116,6 +122,35 @@ that the \"Folder doesn't exist\".\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\n folder will be localized.\n \n+If you want to use OAuth2.0 based authentication, you can specify\n+`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure\n+than using app-specific passwords, and also does not enforce the need of\n+having multi-factor authentication. You will have to use an OAuth2.0\n+access token in place of your password when using this authentication.\n+\n+---------\n+[imap]\n+    folder = \"[Gmail]/Drafts\"\n+    host = imaps://imap.gmail.com\n+    user = user@gmail.com\n+    port = 993\n+    authmethod = OAUTHBEARER\n+---------\n+\n+Using Outlook's IMAP interface:\n+\n+Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it\n+supports only `XOAUTH2` as the mechanism.\n+\n+---------\n+[imap]\n+    folder = \"Drafts\"\n+    host = imaps://outlook.office365.com\n+    user = user@outlook.com\n+    port = 993\n+    authmethod = XOAUTH2\n+---------\n+\n Once the commits are ready to be sent, run the following command:\n \n   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send\n@@ -124,6 +159,10 @@ Just make sure to disable line wrapping in the email client (Gmail's web\n interface will wrap lines no matter what, so you need to use a real\n IMAP client).\n \n+In case you are using OAuth2.0 authentication, it is easier to use credential\n+helpers to generate tokens. Credential helpers suggested in\n+linkgit:git-send-email[1] can be used for `git imap-send` as well.\n+\n CAUTION\n -------\n It is still your responsibility to make sure that the email message\ndiff --git a/imap-send.c b/imap-send.c\nindex 39013330a7..5a83ea80e1 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,7 +139,9 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n-\tAUTH_CRAM_MD5\n+\tAUTH_CRAM_MD5,\n+\tAUTH_OAUTHBEARER,\n+\tAUTH_XOAUTH2,\n };\n \n static const char *cap_list[] = {\n@@ -149,6 +151,8 @@ static const char *cap_list[] = {\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n \t\"AUTH=CRAM-MD5\",\n+\t\"AUTH=OAUTHBEARER\",\n+\t\"AUTH=XOAUTH2\",\n };\n \n #define RESP_OK    0\n@@ -885,6 +889,64 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)\n \treturn (char *)response_64;\n }\n \n+static char *oauthbearer_base64(const char *user, const char *access_token)\n+{\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the OAUTHBEARER string\n+\t *\n+\t * \"n,a=\" {User} \",^Ahost=\" {Host} \"^Aport=\" {Port} \"^Aauth=Bearer \" {Access Token} \"^A^A\n+\t *\n+\t * The first part `n,a=\" {User} \",` is the gs2 header described in RFC5801.\n+\t * * gs2-cb-flag `n` -> client does not support CB\n+\t * * gs2-authzid `a=\" {User} \"`\n+\t *\n+\t * The second part are key value pairs containing host, port and auth as\n+\t * described in RFC7628.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc5801\n+\t * https://datatracker.ietf.org/doc/html/rfc7628\n+\t */\n+\traw = xstrfmt(\"n,a=%s,\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n+static char *xoauth2_base64(const char *user, const char *access_token)\n+{\n+\tint b64_len;\n+\tchar *raw, *b64;\n+\n+\t/*\n+\t * Compose the XOAUTH2 string\n+\t * \"user=\" {User} \"^Aauth=Bearer \" {Access Token} \"^A^A\"\n+\t * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response\n+\t */\n+\traw = xstrfmt(\"user=%s\\001auth=Bearer %s\\001\\001\", user, access_token);\n+\n+\t/* Base64 encode */\n+\tb64 = xmallocz(ENCODED_SIZE(strlen(raw)));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));\n+\tfree(raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -903,9 +965,51 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \treturn 0;\n }\n \n+static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"OAUTHBEARER: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending OAUTHBEARER response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n+static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"XOAUTH2: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending XOAUTH2 response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n #else\n \n #define auth_cram_md5 NULL\n+#define auth_oauthbearer NULL\n+#define auth_xoauth2 NULL\n \n #endif\n \n@@ -1118,6 +1222,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"OAUTHBEARER\", AUTH_OAUTHBEARER, auth_oauthbearer))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"XOAUTH2\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n+\t\t\t\t\tgoto bail;\n \t\t\t} else {\n \t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n \t\t\t\tgoto bail;\n@@ -1419,7 +1529,16 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \n \tserver_fill_credential(srvc, cred);\n \tcurl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);\n-\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n+\n+\t/*\n+\t * Use CURLOPT_PASSWORD irrespective of whether there is\n+\t * an auth method specified or not, unless it's OAuth2.0,\n+\t * where we use CURLOPT_XOAUTH2_BEARER.\n+\t */\n+\tif (!srvc->auth_method ||\n+\t    (strcmp(srvc->auth_method, \"XOAUTH2\") &&\n+\t    strcmp(srvc->auth_method, \"OAUTHBEARER\")))\n+\t\tcurl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);\n \n \tstrbuf_addstr(&path, srvc->use_ssl ? \"imaps://\" : \"imap://\");\n \tstrbuf_addstr(&path, srvc->host);\n@@ -1437,11 +1556,22 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);\n \n \tif (srvc->auth_method) {\n-\t\tstruct strbuf auth = STRBUF_INIT;\n-\t\tstrbuf_addstr(&auth, \"AUTH=\");\n-\t\tstrbuf_addstr(&auth, srvc->auth_method);\n-\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n-\t\tstrbuf_release(&auth);\n+\t\tif (!strcmp(srvc->auth_method, \"XOAUTH2\") ||\n+\t\t    !strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n+\n+\t\t\t/*\n+\t\t\t * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,\n+\t\t\t * upon debugging, it has been found that it is capable of detecting\n+\t\t\t * the best option out of OAUTHBEARER and XOAUTH2.\n+\t\t\t */\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);\n+\t\t} else {\n+\t\t\tstruct strbuf auth = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&auth, \"AUTH=\");\n+\t\t\tstrbuf_addstr(&auth, srvc->auth_method);\n+\t\t\tcurl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);\n+\t\t\tstrbuf_release(&auth);\n+\t\t}\n \t}\n \n \tif (!srvc->use_ssl)\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520475","messageId":"PN3PR01MB95979DF96F8B65497A9CAF81B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 07/10] imap-send: add ability to list the available folders","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:30Z","receivedAt":"2025-06-20T06:41:02Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Various IMAP servers have different ways to name common folders.\nFor example, the folder where all deleted messages are stored is often\nnamed \"[Gmail]/Trash\" on Gmail servers, and \"Deleted\" on Outlook.\nSimilarly, the Drafts folder is simply named \"Drafts\" on Outlook, but\non Gmail it is named \"[Gmail]/Drafts\".\n\nThis commit adds a `--list` command to the `imap-send` tool that lists\nthe available folders on the IMAP server, allowing users to see\nwhich folders are available and how they are named. A sample output\nlooks like this when run against a Gmail server:\n\n    Fetching the list of available folders...\n    * LIST (\\HasNoChildren) \"/\" \"INBOX\"\n    * LIST (\\HasChildren \\Noselect) \"/\" \"[Gmail]\"\n    * LIST (\\All \\HasNoChildren) \"/\" \"[Gmail]/All Mail\"\n    * LIST (\\Drafts \\HasNoChildren) \"/\" \"[Gmail]/Drafts\"\n    * LIST (\\HasNoChildren \\Important) \"/\" \"[Gmail]/Important\"\n    * LIST (\\HasNoChildren \\Sent) \"/\" \"[Gmail]/Sent Mail\"\n    * LIST (\\HasNoChildren \\Junk) \"/\" \"[Gmail]/Spam\"\n    * LIST (\\Flagged \\HasNoChildren) \"/\" \"[Gmail]/Starred\"\n    * LIST (\\HasNoChildren \\Trash) \"/\" \"[Gmail]/Trash\"\n\nFor OpenSSL, this is achived by running the 'IMAP LIST' command and\nparsing the response. This command is specified in RFC6154:\nhttps://datatracker.ietf.org/doc/html/rfc6154#section-5.1\n\nFor libcurl, the example code published in the libcurl documentation\nis used to implement this functionality:\nhttps://curl.se/libcurl/c/imap-list.html\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/git-imap-send.adoc |  6 +-\n imap-send.c                      | 98 ++++++++++++++++++++++++++------\n 2 files changed, 87 insertions(+), 17 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 4a0487b66e..17147f93c3 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -10,6 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n+'git imap-send' --list\n \n \n DESCRIPTION\n@@ -54,6 +55,8 @@ OPTIONS\n \tusing libcurl.  Ignored if Git was built with the NO_OPENSSL option\n \tset.\n \n+--list::\n+\tRun the IMAP LIST command to output a list of all the folders present.\n \n CONFIGURATION\n -------------\n@@ -123,7 +126,8 @@ it. Alternatively, use OAuth2.0 authentication as described below.\n \n [NOTE]\n You might need to instead use: `folder = \"[Google Mail]/Drafts\"` if you get an error\n-that the \"Folder doesn't exist\".\n+that the \"Folder doesn't exist\". You can also run `git imap-send --list` to get a\n+list of available folders.\n \n [NOTE]\n If your Gmail account is set to another language than English, the name of the \"Drafts\"\ndiff --git a/imap-send.c b/imap-send.c\nindex 7e021c8392..b1dddaff3e 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -45,15 +45,21 @@\n #endif\n \n static int verbosity;\n+static int list_folders;\n static int use_curl = USE_CURL_DEFAULT;\n static char *opt_folder;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n+static char const * const imap_send_usage[] = {\n+\tN_(\"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\"),\n+\t\"git imap-send --list\",\n+\tNULL\n+};\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n \tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n+\tOPT_BOOL(0, \"list\", &list_folders, \"list all folders on the IMAP server\"),\n \tOPT_END()\n };\n \n@@ -429,7 +435,7 @@ static int buffer_gets(struct imap_buffer *b, char **s)\n \t\t\tif (b->buf[b->offset + 1] == '\\n') {\n \t\t\t\tb->buf[b->offset] = 0;  /* terminate the string */\n \t\t\t\tb->offset += 2; /* next line */\n-\t\t\t\tif (0 < verbosity)\n+\t\t\t\tif ((0 < verbosity) || (list_folders && strstr(*s, \"* LIST\")))\n \t\t\t\t\tputs(*s);\n \t\t\t\treturn 0;\n \t\t\t}\n@@ -1572,6 +1578,26 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \treturn 0;\n }\n \n+static int list_imap_folders(struct imap_server_conf *server)\n+{\n+\tstruct imap_store *ctx = imap_open_store(server, \"INBOX\");\n+\tif (!ctx) {\n+\t\tfprintf(stderr, \"failed to connect to IMAP server\\n\");\n+\t\treturn 1;\n+\t}\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\t/* Issue the LIST command and print the results */\n+\tif (imap_exec(ctx, NULL, \"LIST \\\"\\\" \\\"*\\\"\") != RESP_OK) {\n+\t\tfprintf(stderr, \"failed to list folders\\n\");\n+\t\timap_close_store(ctx);\n+\t\treturn 1;\n+\t}\n+\n+\timap_close_store(ctx);\n+\treturn 0;\n+}\n+\n #ifdef USE_CURL_FOR_IMAP_SEND\n static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n {\n@@ -1605,11 +1631,13 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tif (!path.len || path.buf[path.len - 1] != '/')\n \t\tstrbuf_addch(&path, '/');\n \n-\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n-\tif (!uri_encoded_folder)\n-\t\tdie(\"failed to encode server folder\");\n-\tstrbuf_addstr(&path, uri_encoded_folder);\n-\tcurl_free(uri_encoded_folder);\n+\tif (!list_folders) {\n+\t\turi_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);\n+\t\tif (!uri_encoded_folder)\n+\t\t\tdie(\"failed to encode server folder\");\n+\t\tstrbuf_addstr(&path, uri_encoded_folder);\n+\t\tcurl_free(uri_encoded_folder);\n+\t}\n \n \tcurl_easy_setopt(curl, CURLOPT_URL, path.buf);\n \tstrbuf_release(&path);\n@@ -1640,10 +1668,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify);\n \tcurl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)srvc->ssl_verify);\n \n-\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n-\n-\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n-\n \tif (0 < verbosity || getenv(\"GIT_CURL_VERBOSE\"))\n \t\thttp_trace_curl_no_data();\n \tsetup_curl_trace(curl);\n@@ -1662,6 +1686,10 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \tstruct credential cred = CREDENTIAL_INIT;\n \n \tcurl = setup_curl(server, &cred);\n+\n+\tcurl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);\n+\tcurl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);\n+\n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n \tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n@@ -1707,6 +1735,31 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \treturn res != CURLE_OK;\n }\n+\n+static int curl_list_imap_folders(struct imap_server_conf *server)\n+{\n+\tCURL *curl;\n+\tCURLcode res = CURLE_OK;\n+\tstruct credential cred = CREDENTIAL_INIT;\n+\n+\tfprintf(stderr, \"Fetching the list of available folders...\\n\");\n+\tcurl = setup_curl(server, &cred);\n+\tres = curl_easy_perform(curl);\n+\n+\tcurl_easy_cleanup(curl);\n+\tcurl_global_cleanup();\n+\n+\tif (cred.username) {\n+\t\tif (res == CURLE_OK)\n+\t\t\tcredential_approve(the_repository, &cred);\n+\t\telse if (res == CURLE_LOGIN_DENIED)\n+\t\t\tcredential_reject(the_repository, &cred);\n+\t}\n+\n+\tcredential_clear(&cred);\n+\n+\treturn res != CURLE_OK;\n+}\n #endif\n \n int cmd_main(int argc, const char **argv)\n@@ -1747,11 +1800,6 @@ int cmd_main(int argc, const char **argv)\n \tif (!server.port)\n \t\tserver.port = server.use_ssl ? 993 : 143;\n \n-\tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n-\t\tret = 1;\n-\t\tgoto out;\n-\t}\n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n \t\t\tfprintf(stderr, \"no imap host specified\\n\");\n@@ -1761,6 +1809,24 @@ int cmd_main(int argc, const char **argv)\n \t\tserver.host = xstrdup(\"tunnel\");\n \t}\n \n+\tif (list_folders) {\n+\t\tif (server.tunnel)\n+\t\t\tret = list_imap_folders(&server);\n+#ifdef USE_CURL_FOR_IMAP_SEND\n+\t\telse if (use_curl)\n+\t\t\tret = curl_list_imap_folders(&server);\n+#endif\n+\t\telse\n+\t\t\tret = list_imap_folders(&server);\n+\t\tgoto out;\n+\t}\n+\n+\tif (!server.folder) {\n+\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tret = 1;\n+\t\tgoto out;\n+\t}\n+\n \t/* read the messages */\n \tif (strbuf_read(&all_msgs, 0, 0) < 0) {\n \t\terror_errno(_(\"could not read from stdin\"));\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520474","messageId":"PN3PR01MB9597CB16EB1C59A217615376B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 05/10] imap-send: add PLAIN authentication method to OpenSSL","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:28Z","receivedAt":"2025-06-20T06:41:04Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"The current implementation for PLAIN in imap-send works just fine\nif using curl, but if attempted to use for OpenSSL, it is treated\nas an invalid mechanism. The default implementation for OpenSSL is\nIMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is\nstill used today by many email providers in form of app passwords,\nlets add an implementation that can use AUTH PLAIN if specified.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc |  4 +--\n imap-send.c                    | 60 +++++++++++++++++++++++++++++++++-\n 2 files changed, 61 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 29b998d5ff..7c8b2dcce4 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -40,6 +40,6 @@ imap.authMethod::\n \tSpecify the authentication method for authenticating with the IMAP server.\n \tIf Git was built with the NO_CURL option, or if your curl version is older\n \tthan 7.34.0, or if you're running git-imap-send with the `--no-curl`\n-\toption, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and\n-\t`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n+\toption, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`\n+\tand `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP\n \tplaintext `LOGIN` command.\ndiff --git a/imap-send.c b/imap-send.c\nindex 5a83ea80e1..f3ba5eeb5b 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -139,6 +139,7 @@ enum CAPABILITY {\n \tLITERALPLUS,\n \tNAMESPACE,\n \tSTARTTLS,\n+\tAUTH_PLAIN,\n \tAUTH_CRAM_MD5,\n \tAUTH_OAUTHBEARER,\n \tAUTH_XOAUTH2,\n@@ -150,6 +151,7 @@ static const char *cap_list[] = {\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n \t\"STARTTLS\",\n+\t\"AUTH=PLAIN\",\n \t\"AUTH=CRAM-MD5\",\n \t\"AUTH=OAUTHBEARER\",\n \t\"AUTH=XOAUTH2\",\n@@ -851,6 +853,38 @@ static char hexchar(unsigned int b)\n }\n \n #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))\n+static char *plain_base64(const char *user, const char *pass)\n+{\n+\tstruct strbuf raw = STRBUF_INIT;\n+\tint b64_len;\n+\tchar *b64;\n+\n+\t/*\n+\t * Compose the PLAIN string\n+\t *\n+\t * The username and password are combined to one string and base64 encoded.\n+\t * \"\\0user\\0pass\"\n+\t *\n+\t * The method has been described in RFC4616.\n+\t *\n+\t * https://datatracker.ietf.org/doc/html/rfc4616\n+\t */\n+\tstrbuf_addch(&raw, '\\0');\n+\tstrbuf_addstr(&raw, user);\n+\tstrbuf_addch(&raw, '\\0');\n+\tstrbuf_addstr(&raw, pass);\n+\n+\tb64 = xmallocz(ENCODED_SIZE(raw.len));\n+\tb64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw.buf, raw.len);\n+\tstrbuf_release(&raw);\n+\n+\tif (b64_len < 0) {\n+\t\tfree(b64);\n+\t\treturn NULL;\n+\t}\n+\treturn b64;\n+}\n+\n static char *cram(const char *challenge_64, const char *user, const char *pass)\n {\n \tint i, resp_len, encoded_len, decoded_len;\n@@ -947,6 +981,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)\n \treturn b64;\n }\n \n+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)\n+{\n+\tint ret;\n+\tchar *b64;\n+\n+\tb64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);\n+\tif (!b64)\n+\t\treturn error(\"PLAIN: base64 encoding failed\");\n+\n+\t/* Send the base64-encoded response */\n+\tret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));\n+\tif (ret != (int)strlen(b64)) {\n+\t\tfree(b64);\n+\t\treturn error(\"IMAP error: sending PLAIN response failed\");\n+\t}\n+\n+\tfree(b64);\n+\treturn 0;\n+}\n+\n static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n {\n \tint ret;\n@@ -1007,6 +1061,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)\n \n #else\n \n+#define auth_plain NULL\n #define auth_cram_md5 NULL\n #define auth_oauthbearer NULL\n #define auth_xoauth2 NULL\n@@ -1219,7 +1274,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\tserver_fill_credential(srvc, &cred);\n \n \t\tif (srvc->auth_method) {\n-\t\t\tif (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n+\t\t\tif (!strcmp(srvc->auth_method, \"PLAIN\")) {\n+\t\t\t\tif (try_auth_method(srvc, ctx, imap, \"PLAIN\", AUTH_PLAIN, auth_plain))\n+\t\t\t\t\tgoto bail;\n+\t\t\t} else if (!strcmp(srvc->auth_method, \"CRAM-MD5\")) {\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"CRAM-MD5\", AUTH_CRAM_MD5, auth_cram_md5))\n \t\t\t\t\tgoto bail;\n \t\t\t} else if (!strcmp(srvc->auth_method, \"OAUTHBEARER\")) {\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520476","messageId":"PN3PR01MB95973C003222DB86833C599CB87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 06/10] imap-send: enable specifying the folder using the command line","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:29Z","receivedAt":"2025-06-20T06:41:07Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some users may very often want to imap-send messages to a folder\nother than the default set in the config. Add a command line\nargument for the same.\n\nWhile at it, fix minor mark-up inconsistencies in the existing\ndocumentation text.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n Documentation/config/imap.adoc   |  6 ++++--\n Documentation/git-imap-send.adoc | 15 +++++++++++----\n imap-send.c                      |  9 ++++++++-\n 3 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc\nindex 7c8b2dcce4..4682a6bd03 100644\n--- a/Documentation/config/imap.adoc\n+++ b/Documentation/config/imap.adoc\n@@ -1,7 +1,9 @@\n imap.folder::\n \tThe folder to drop the mails into, which is typically the Drafts\n-\tfolder. For example: \"INBOX.Drafts\", \"INBOX/Drafts\" or\n-\t\"[Gmail]/Drafts\". Required.\n+\tfolder. For example: `INBOX.Drafts`, `INBOX/Drafts` or\n+\t`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;\n+\tthe value of this configuration variable is used as the fallback\n+\tdefault value when the `--folder` option is not given.\n \n imap.tunnel::\n \tCommand used to set up a tunnel to the IMAP server through which\ndiff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc\nindex 8adf0e5aac..4a0487b66e 100644\n--- a/Documentation/git-imap-send.adoc\n+++ b/Documentation/git-imap-send.adoc\n@@ -9,21 +9,23 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder\n SYNOPSIS\n --------\n [verse]\n-'git imap-send' [-v] [-q] [--[no-]curl]\n+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]\n \n \n DESCRIPTION\n -----------\n-This command uploads a mailbox generated with 'git format-patch'\n+This command uploads a mailbox generated with `git format-patch`\n into an IMAP drafts folder.  This allows patches to be sent as\n other email is when using mail clients that cannot read mailbox\n files directly. The command also works with any general mailbox\n-in which emails have the fields \"From\", \"Date\", and \"Subject\" in\n+in which emails have the fields `From`, `Date`, and `Subject` in\n that order.\n \n Typical usage is something like:\n \n-git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n+$ git format-patch --signoff --stdout --attach origin | git imap-send\n+------\n \n \n OPTIONS\n@@ -37,6 +39,11 @@ OPTIONS\n --quiet::\n \tBe quiet.\n \n+-f <folder>::\n+--folder=<folder>::\n+\tSpecify the folder in which the emails have to saved.\n+\tFor example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.\n+\n --curl::\n \tUse libcurl to communicate with the IMAP server, unless tunneling\n \tinto it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND\ndiff --git a/imap-send.c b/imap-send.c\nindex f3ba5eeb5b..7e021c8392 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -46,12 +46,14 @@\n \n static int verbosity;\n static int use_curl = USE_CURL_DEFAULT;\n+static char *opt_folder;\n \n-static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] < <mbox>\", NULL };\n+static const char * const imap_send_usage[] = { \"git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>\", NULL };\n \n static struct option imap_send_options[] = {\n \tOPT__VERBOSITY(&verbosity),\n \tOPT_BOOL(0, \"curl\", &use_curl, \"use libcurl to communicate with the IMAP server\"),\n+\tOPT_STRING('f', \"folder\", &opt_folder, \"folder\", \"specify the IMAP folder\"),\n \tOPT_END()\n };\n \n@@ -1722,6 +1724,11 @@ int cmd_main(int argc, const char **argv)\n \n \targc = parse_options(argc, (const char **)argv, \"\", imap_send_options, imap_send_usage, 0);\n \n+\tif (opt_folder) {\n+\t\tfree(server.folder);\n+\t\tserver.folder = xstrdup(opt_folder);\n+\t}\n+\n \tif (argc)\n \t\tusage_with_options(imap_send_usage, imap_send_options);\n \n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520477","messageId":"PN3PR01MB959780281881514C0A6FCB55B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 08/10] imap-send: display port alongwith host when git credential is invoked","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:31Z","receivedAt":"2025-06-20T06:41:08Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"When requesting for passsword, git credential helper used to display\nonly the host name. For example:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com':\n\nNow, it will display the port along with the host name:\n\n    Password for 'imaps://gargaditya08%40live.com@outlook.office365.com:993':\n\nThis has been done to make credential helpers more specific for ports.\nAlso, this behaviour will also mimic git send-email, which displays\nthe port along with the host name when requesting for a password.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex b1dddaff3e..ef5cf0a395 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1082,7 +1082,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent\n \t\treturn;\n \n \tcred->protocol = xstrdup(srvc->use_ssl ? \"imaps\" : \"imap\");\n-\tcred->host = xstrdup(srvc->host);\n+\tcred->host = xstrfmt(\"%s:%d\", srvc->host, srvc->port);\n \n \tcred->username = xstrdup_or_null(srvc->user);\n \tcred->password = xstrdup_or_null(srvc->pass);\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520478","messageId":"PN3PR01MB9597478042C9DBACE0323796B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 09/10] imap-send: display the destination mailbox when sending a message","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:32Z","receivedAt":"2025-06-20T06:41:10Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Whenever we sent a message using the `imap-send` command, it would\ndisplay a log showing the number of messages which are to be sent.\nFor example:\n\n    sending 1 message\n     100% (1/1) done\n\nThis had been made more informative by adding the name of the destination\nfolder as well:\n\n    Sending 1 message to Drafts folder...\n     100% (1/1) done\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex ef5cf0a395..11a19ffeec 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1556,7 +1556,8 @@ static int append_msgs_to_imap(struct imap_server_conf *server,\n \t}\n \tctx->name = server->folder;\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \n@@ -1692,7 +1693,8 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server,\n \n \tcurl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);\n \n-\tfprintf(stderr, \"sending %d message%s\\n\", total, (total != 1) ? \"s\" : \"\");\n+\tfprintf(stderr, \"Sending %d message%s to %s folder...\\n\",\n+\t\ttotal, (total != 1) ? \"s\" : \"\", server->folder);\n \twhile (1) {\n \t\tunsigned percent = n * 100 / total;\n \t\tint prev_len;\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520479","messageId":"PN3PR01MB9597C69D246A7D32200DCCF5B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"[PATCH v19 10/10] imap-send: fix minor mistakes in the logs","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T06:40:33Z","receivedAt":"2025-06-20T06:41:12Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":"Some minor mistakes have been found in the logs. Most of them include\nerror messages starting with a capital letter, and ending with a period.\nAbbreviations like \"IMAP\" and \"OK\" should also be in uppercase. Another\nmistake was that the error message showing unknown authentication\nmechanism used was displaying the host rather than the mechanism in the\nlogs. Fix them.\n\nSigned-off-by: Aditya Garg <gargaditya08@live.com>\n---\n imap-send.c | 24 ++++++++++++------------\n 1 file changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 11a19ffeec..603e3d6fbc 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -211,7 +211,7 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED,\n \t\t\t      const struct imap_server_conf *cfg UNUSED,\n \t\t\t      int use_tls_only UNUSED)\n {\n-\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\tfprintf(stderr, \"SSL requested, but SSL support is not compiled in\\n\");\n \treturn -1;\n }\n \n@@ -1019,7 +1019,7 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt)\n \tret = socket_write(&ctx->imap->buf.sock, response, strlen(response));\n \tif (ret != strlen(response)) {\n \t\tfree(response);\n-\t\treturn error(\"IMAP error: sending response failed\");\n+\t\treturn error(\"IMAP error: sending CRAM-MD5 response failed\");\n \t}\n \n \tfree(response);\n@@ -1159,7 +1159,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\timap->buf.sock.fd[0] = tunnel.out;\n \t\timap->buf.sock.fd[1] = tunnel.in;\n \n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t} else {\n #ifndef NO_IPV6\n \t\tstruct addrinfo hints, *ai0, *ai;\n@@ -1178,7 +1178,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tfprintf(stderr, \"getaddrinfo: %s\\n\", gai_strerror(gai));\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\tfor (ai0 = ai; ai; ai = ai->ai_next) {\n \t\t\tchar addr[NI_MAXHOST];\n@@ -1216,7 +1216,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tperror(\"gethostbyname\");\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \n \t\taddr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);\n \n@@ -1230,7 +1230,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t}\n #endif\n \t\tif (s < 0) {\n-\t\t\tfputs(\"Error: unable to connect to server.\\n\", stderr);\n+\t\t\tfputs(\"error: unable to connect to server\\n\", stderr);\n \t\t\tgoto bail;\n \t\t}\n \n@@ -1242,7 +1242,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\tclose(s);\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info(\"ok\\n\");\n+\t\timap_info(\"OK\\n\");\n \t}\n \n \t/* read the greeting string */\n@@ -1295,12 +1295,12 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c\n \t\t\t\tif (try_auth_method(srvc, ctx, imap, \"XOAUTH2\", AUTH_XOAUTH2, auth_xoauth2))\n \t\t\t\t\tgoto bail;\n \t\t\t} else {\n-\t\t\t\tfprintf(stderr, \"Unknown authentication method:%s\\n\", srvc->host);\n+\t\t\t\tfprintf(stderr, \"unknown authentication mechanism: %s\\n\", srvc->auth_method);\n \t\t\t\tgoto bail;\n \t\t\t}\n \t\t} else {\n \t\t\tif (CAP(NOLOGIN)) {\n-\t\t\t\tfprintf(stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\n+\t\t\t\tfprintf(stderr, \"skipping account %s@%s, server forbids LOGIN\\n\",\n \t\t\t\t\tsrvc->user, srvc->host);\n \t\t\t\tgoto bail;\n \t\t\t}\n@@ -1804,7 +1804,7 @@ int cmd_main(int argc, const char **argv)\n \n \tif (!server.host) {\n \t\tif (!server.tunnel) {\n-\t\t\tfprintf(stderr, \"no imap host specified\\n\");\n+\t\t\tfprintf(stderr, \"no IMAP host specified\\n\");\n \t\t\tret = 1;\n \t\t\tgoto out;\n \t\t}\n@@ -1824,7 +1824,7 @@ int cmd_main(int argc, const char **argv)\n \t}\n \n \tif (!server.folder) {\n-\t\tfprintf(stderr, \"no imap store specified\\n\");\n+\t\tfprintf(stderr, \"no IMAP store specified\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n@@ -1844,7 +1844,7 @@ int cmd_main(int argc, const char **argv)\n \n \ttotal = count_messages(&all_msgs);\n \tif (!total) {\n-\t\tfprintf(stderr, \"no messages to send\\n\");\n+\t\tfprintf(stderr, \"no messages found to send\\n\");\n \t\tret = 1;\n \t\tgoto out;\n \t}\n-- \n2.49.0.824.gcc76007b2f\n\n"},{"id":"520480","messageId":"PN3PR01MB9597001636F70B33B52650FDB87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","threadId":"63502","inReplyTo":"F0A06034-99B8-4BD1-9CDE-515A3EA430DA@gmail.com","subject":"Re: [PATCH v18 04/10] imap-send: add support for OAuth2.0 authentication","fromName":"Aditya Garg","fromEmail":"gargaditya08@live.com","sentAt":"2025-06-20T07:00:51Z","receivedAt":"2025-06-20T07:01:21Z","isPatch":true,"sender":{"key":"gargaditya08@live.com","avatar":"https://avatars.githubusercontent.com/u/85610623?v=4"},"body":" \n> This looks very fragile. It would be safer to use an strbuf or if there are no embedded nul bytes xstrfmt() and strlen(). This applies to the next patch as well and any others that are building strings with snprintf() or memcpy(). \n> \n\nv19 should have these fixed.\n"},{"id":"520500","messageId":"xmqqh60a4dk6.fsf@gitster.g","threadId":"63502","inReplyTo":"PN3PR01MB9597F9CAD0DA83152E651194B87CA@PN3PR01MB9597.INDPRD01.PROD.OUTLOOK.COM","subject":"Re: [PATCH v19 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-20T15:50:49Z","receivedAt":"2025-06-20T15:50:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Aditya Garg <gargaditya08@live.com> writes:\n\n> v19: - Use xstrfmt() for OAuth2 strings and strbuf for PLAIN.\n>\n> Aditya Garg (10):\n>   imap-send: fix bug causing cfg->folder being set to NULL\n>   imap-send: fix memory leak in case auth_cram_md5 fails\n>   imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>     without OpenSSL\n>   imap-send: add support for OAuth2.0 authentication\n>   imap-send: add PLAIN authentication method to OpenSSL\n>   imap-send: enable specifying the folder using the command line\n>   imap-send: add ability to list the available folders\n>   imap-send: display port alongwith host when git credential is invoked\n>   imap-send: display the destination mailbox when sending a message\n>   imap-send: fix minor mistakes in the logs\n>\n>  Documentation/config/imap.adoc   |  11 +-\n>  Documentation/git-imap-send.adoc |  68 +++++-\n>  imap-send.c                      | 405 ++++++++++++++++++++++++++-----\n>  3 files changed, 407 insertions(+), 77 deletions(-)\n\nLooking good.  Will replace.\n\nShould we declare victory and mark the topic for 'next' now?\n\nThanks.\n"},{"id":"520558","messageId":"c787a41c-97c6-437f-aae0-52132c79db7c@gmail.com","threadId":"63502","inReplyTo":"xmqqh60a4dk6.fsf@gitster.g","subject":"Re: [PATCH v19 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-06-23T09:09:25Z","receivedAt":"2025-06-23T09:09:29Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 20/06/2025 16:50, Junio C Hamano wrote:\n> Aditya Garg <gargaditya08@live.com> writes:\n> \n>> v19: - Use xstrfmt() for OAuth2 strings and strbuf for PLAIN.\n>>\n>> Aditya Garg (10):\n>>    imap-send: fix bug causing cfg->folder being set to NULL\n>>    imap-send: fix memory leak in case auth_cram_md5 fails\n>>    imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>>      without OpenSSL\n>>    imap-send: add support for OAuth2.0 authentication\n>>    imap-send: add PLAIN authentication method to OpenSSL\n>>    imap-send: enable specifying the folder using the command line\n>>    imap-send: add ability to list the available folders\n>>    imap-send: display port alongwith host when git credential is invoked\n>>    imap-send: display the destination mailbox when sending a message\n>>    imap-send: fix minor mistakes in the logs\n>>\n>>   Documentation/config/imap.adoc   |  11 +-\n>>   Documentation/git-imap-send.adoc |  68 +++++-\n>>   imap-send.c                      | 405 ++++++++++++++++++++++++++-----\n>>   3 files changed, 407 insertions(+), 77 deletions(-)\n> \n> Looking good.  Will replace.\n> \n> Should we declare victory and mark the topic for 'next' now?\n\nI think so, the range diff looks good. I've not reviewed each patch but \nI just had a quick scan of\n\n     git diff origin/master origin/seen imap-send.c\n\nand it looked reasonable.\n\nBest Wishes\n\nPhillip\n"},{"id":"520574","messageId":"xmqqy0ti5sq2.fsf@gitster.g","threadId":"63502","inReplyTo":"c787a41c-97c6-437f-aae0-52132c79db7c@gmail.com","subject":"Re: [PATCH v19 00/10] imap-send: make it usable again and add OAuth2.0 support","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-23T16:27:01Z","receivedAt":"2025-06-23T16:27:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 20/06/2025 16:50, Junio C Hamano wrote:\n>> Aditya Garg <gargaditya08@live.com> writes:\n>> \n>>> v19: - Use xstrfmt() for OAuth2 strings and strbuf for PLAIN.\n>>>\n>>> Aditya Garg (10):\n>>>    imap-send: fix bug causing cfg->folder being set to NULL\n>>>    imap-send: fix memory leak in case auth_cram_md5 fails\n>>>    imap-send: gracefully fail if CRAM-MD5 authentication is requested\n>>>      without OpenSSL\n>>>    imap-send: add support for OAuth2.0 authentication\n>>>    imap-send: add PLAIN authentication method to OpenSSL\n>>>    imap-send: enable specifying the folder using the command line\n>>>    imap-send: add ability to list the available folders\n>>>    imap-send: display port alongwith host when git credential is invoked\n>>>    imap-send: display the destination mailbox when sending a message\n>>>    imap-send: fix minor mistakes in the logs\n>>>\n>>>   Documentation/config/imap.adoc   |  11 +-\n>>>   Documentation/git-imap-send.adoc |  68 +++++-\n>>>   imap-send.c                      | 405 ++++++++++++++++++++++++++-----\n>>>   3 files changed, 407 insertions(+), 77 deletions(-)\n>> Looking good.  Will replace.\n>> Should we declare victory and mark the topic for 'next' now?\n>\n> I think so, the range diff looks good. I've not reviewed each patch\n> but I just had a quick scan of\n>\n>     git diff origin/master origin/seen imap-send.c\n>\n> and it looked reasonable.\n>\n> Best Wishes\n>\n> Phillip\n\nThanks.\n\n"}]}