This patch series does the following things: Firstly it basically makes the imap-send command usable again since it was broken because of not being able to correctly parse the config file. Further it adds support for OAuth2.0 and PLAIN authentication to git imap-send. Last, it does some minor improvements including adding the ability to specify the folder using the command line and ability to list the available folders by adding a `--list` option. P.S.: I am surprised this thing even exists xD. v2: - Added support for OAuth2.0 with curl. - Fixed the memory leak in case auth_cram_md5 fails. v3: - Improve wording in first patch - Change misleading message if OAuth2.0 is used without OpenSSL v4: - Add PLAIN authentication mechanism for OpenSSL - Improved wording in the first patch a bit more v5: - Add ability to specify destination folder using the command line - Add ability to set a default between curl and openssl using the config v6: - Fix minor mistakes in --folder documentation v7: - Fix spelling and grammar mistakes in logs shown to the user when running imap-send - Display port alongwith host when git credential is invoked and asks for a password - Display the destination mailbox when sending a message v8: - Drop the patch that enabled user to choose between libcurl and openssl using the config - Add ability to list the available folders by adding a `--list` option Aditya Garg (9): imap-send: fix bug causing cfg->folder being set to NULL imap-send: add support for OAuth2.0 authentication imap-send: add PLAIN authentication method to OpenSSL imap-send: fix memory leak in case auth_cram_md5 fails imap-send: enable specifying the folder using the command line imap-send: fix numerous spelling and grammar mistakes in logs imap-send: display port alongwith host when git credential is invoked imap-send: display the destination mailbox when sending a message imap-send: add ability to list the available folders Documentation/config/imap.adoc | 10 +- Documentation/git-imap-send.adoc | 67 ++++- imap-send.c | 417 +++++++++++++++++++++++++++---- 3 files changed, 431 insertions(+), 63 deletions(-) Range-diff against v7: 1: 4757d0305d = 1: 3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL 2: c4e2a5659b = 2: f0743d46e1 imap-send: add support for OAuth2.0 authentication 3: af9aa85cab = 3: b1602644b7 imap-send: add PLAIN authentication method to OpenSSL 4: 2ca10774db = 4: 49790e60cc imap-send: fix memory leak in case auth_cram_md5 fails 5: 190bed0bff = 5: 2efe897379 imap-send: enable specifying the folder using the command line 7: 6a839e5f4d = 6: 8f6676a046 imap-send: fix numerous spelling and grammar mistakes in logs 8: a60d8f458f = 7: 69fdae55cd imap-send: display port alongwith host when git credential is invoked 9: 5db5b64a3b = 8: 187dbccd03 imap-send: display the destination mailbox when sending a message 6: 469c05321b ! 9: 03d7d6a772 imap-send: enable user to choose between libcurl and openssl using the config @@ Metadata Author: Aditya Garg <gargaditya08@xxxxxxxx> ## Commit message ## - imap-send: enable user to choose between libcurl and openssl using the config + imap-send: add ability to list the available folders - Currently, imap-send allows the user to choose between libcurl and - openssl in case Git is compiled with both libraries only using the - command line, and no option to set a default using the config is - available. Add support for the same. + Various IMAP servers have different ways to name common folders. + For example, the folder where all deleted messages are stored is often + named "[Gmail]/Trash" on Gmail servers, and "Deleted" on Outlook. + Similarly, the Drafts folder is simply named "Drafts" on Outlook, but + on Gmail it is named "[Gmail]/Drafts". - Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx> + This commit adds a `--list` command to the `imap-send` tool that lists + the available folders on the IMAP server, allowing users to see + which folders are available and how they are named. A sample output + looks like this when run against a Gmail server: - ## Documentation/config/imap.adoc ## -@@ Documentation/config/imap.adoc: imap.port:: - Defaults to 143 for imap:// hosts and 993 for imaps:// hosts. - Ignored when imap.tunnel is set. - -+imap.usecurl:: -+ A boolean to choose whether to use libcurl or not to communicate -+ with the IMAP server. -+ Ignored if Git was built without `USE_CURL_FOR_IMAP_SEND` option -+ or with `NO_OPENSSL` option set. -+ `--[no]-curl` argument will override this option. -+ - imap.sslverify:: - A boolean to enable/disable verification of the server certificate - used by the SSL/TLS connection. Default is `true`. Ignored when + Fetching the list of available folders... + * LIST (\HasNoChildren) "/" "INBOX" + * LIST (\HasChildren \Noselect) "/" "[Gmail]" + * LIST (\All \HasNoChildren) "/" "[Gmail]/All Mail" + * LIST (\Drafts \HasNoChildren) "/" "[Gmail]/Drafts" + * LIST (\HasNoChildren \Important) "/" "[Gmail]/Important" + * LIST (\HasNoChildren \Sent) "/" "[Gmail]/Sent Mail" + * LIST (\HasNoChildren \Junk) "/" "[Gmail]/Spam" + * LIST (\Flagged \HasNoChildren) "/" "[Gmail]/Starred" + * LIST (\HasNoChildren \Trash) "/" "[Gmail]/Trash" + + For OpenSSL, this is achived by running the 'IMAP LIST' command and + parsing the response. This command is specified in RFC6154: + https://datatracker.ietf.org/doc/html/rfc6154#section-5.1 + + For libcurl, the example code published in the libcurl documentation + is used to implement this functionality: + https://curl.se/libcurl/c/imap-list.html + + Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx> ## Documentation/git-imap-send.adoc ## -@@ Documentation/git-imap-send.adoc: OPTIONS +@@ Documentation/git-imap-send.adoc: SYNOPSIS + -------- + [verse] + 'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] ++'git imap-send' --list - --curl:: - Use libcurl to communicate with the IMAP server, unless tunneling -- into it. Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND -+ into it. Ignored if Git was built without the `USE_CURL_FOR_IMAP_SEND` - option set. - --no-curl:: - Talk to the IMAP server using git's own IMAP routines instead of -- using libcurl. Ignored if Git was built with the NO_OPENSSL option -+ using libcurl. Ignored if Git was built with the `NO_OPENSSL` option + DESCRIPTION +@@ Documentation/git-imap-send.adoc: OPTIONS + using libcurl. Ignored if Git was built with the NO_OPENSSL option set. ++--list:: ++ Run the IMAP LIST command to output a list of all the folders present. + + CONFIGURATION + ------------- +@@ Documentation/git-imap-send.adoc: authentication as described below. + [NOTE] + You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error +-that the "Folder doesn't exist". ++that the "Folder doesn't exist". You can also run `git imap-send --list` to get a ++list of available folders. + + [NOTE] + If your Gmail account is set to another language than English, the name of the "Drafts" ## imap-send.c ## -@@ imap-send.c: static int git_imap_config(const char *var, const char *val, - return git_config_string(&cfg->auth_method, var, val); - } else if (!strcmp("imap.port", var)) { - cfg->port = git_config_int(var, val, ctx->kvi); -+ } else if (!strcmp("imap.usecurl", var)) { -+ use_curl = git_config_bool(var, val); - } else if (!strcmp("imap.host", var)) { - if (!val) { - return config_error_nonbool(var); +@@ + #endif + + static int verbosity; ++static int list_folders = 0; + static int use_curl = USE_CURL_DEFAULT; + static char *opt_folder = NULL; + +-static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>", NULL }; ++static char const * const imap_send_usage[] = { ++ N_("git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>"), ++ "git imap-send --list", ++ NULL ++}; + + static struct option imap_send_options[] = { + OPT__VERBOSITY(&verbosity), + OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"), + OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"), ++ OPT_BOOL(0, "list", &list_folders, "list all folders on the IMAP server"), + OPT_END() + }; + +@@ imap-send.c: static int buffer_gets(struct imap_buffer *b, char **s) + if (b->buf[b->offset + 1] == '\n') { + b->buf[b->offset] = 0; /* terminate the string */ + b->offset += 2; /* next line */ +- if (0 < verbosity) ++ if ((0 < verbosity) || (list_folders && strstr(*s, "* LIST"))) + puts(*s); + return 0; + } +@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server, + return 0; + } + ++static int list_imap_folders(struct imap_server_conf *server) ++{ ++ struct imap_store *ctx = imap_open_store(server, "INBOX"); ++ if (!ctx) { ++ fprintf(stderr, "Failed to connect to IMAP server.\n"); ++ return 1; ++ } ++ ++ fprintf(stderr, "Fetching the list of available folders...\n"); ++ /* Issue the LIST command and print the results */ ++ if (imap_exec(ctx, NULL, "LIST \"\" \"*\"") != RESP_OK) { ++ fprintf(stderr, "Failed to list folders.\n"); ++ imap_close_store(ctx); ++ return 1; ++ } ++ ++ imap_close_store(ctx); ++ return 0; ++} ++ + #ifdef USE_CURL_FOR_IMAP_SEND + static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) + { +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) + if (!path.len || path.buf[path.len - 1] != '/') + strbuf_addch(&path, '/'); + +- uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); +- if (!uri_encoded_folder) +- die("Failed to encode server folder."); +- strbuf_addstr(&path, uri_encoded_folder); +- curl_free(uri_encoded_folder); ++ if (!list_folders) { ++ uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); ++ if (!uri_encoded_folder) ++ die("Failed to encode server folder."); ++ strbuf_addstr(&path, uri_encoded_folder); ++ curl_free(uri_encoded_folder); ++ } + + curl_easy_setopt(curl, CURLOPT_URL, path.buf); + strbuf_release(&path); +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, srvc->ssl_verify); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, srvc->ssl_verify); + +- curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer); +- +- curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); +- + if (0 < verbosity || getenv("GIT_CURL_VERBOSE")) + http_trace_curl_no_data(); + setup_curl_trace(curl); +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server, + struct credential cred = CREDENTIAL_INIT; + + curl = setup_curl(server, &cred); ++ ++ curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer); ++ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); ++ + curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf); + + fprintf(stderr, "Sending %d message%s to %s folder...\n", +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server, + + return res != CURLE_OK; + } ++ ++static int curl_list_imap_folders(struct imap_server_conf *server) ++{ ++ CURL *curl; ++ CURLcode res = CURLE_OK; ++ struct credential cred = CREDENTIAL_INIT; ++ ++ fprintf(stderr, "Fetching the list of available folders...\n"); ++ curl = setup_curl(server, &cred); ++ res = curl_easy_perform(curl); ++ ++ curl_easy_cleanup(curl); ++ curl_global_cleanup(); ++ ++ if (cred.username) { ++ if (res == CURLE_OK) ++ credential_approve(the_repository, &cred); ++ else if (res == CURLE_LOGIN_DENIED) ++ credential_reject(the_repository, &cred); ++ } ++ ++ credential_clear(&cred); ++ ++ return res != CURLE_OK; ++} + #endif + + int cmd_main(int argc, const char **argv) +@@ imap-send.c: int cmd_main(int argc, const char **argv) + if (!server.port) + server.port = server.use_ssl ? 993 : 143; + +- if (!server.folder) { +- fprintf(stderr, "No IMAP store specified.\n"); +- ret = 1; +- goto out; +- } + if (!server.host) { + if (!server.tunnel) { + fprintf(stderr, "No IMAP host specified.\n"); +@@ imap-send.c: int cmd_main(int argc, const char **argv) + server.host = xstrdup("tunnel"); + } + ++ if (list_folders) { ++ if (server.tunnel) ++ ret = list_imap_folders(&server); ++#ifdef USE_CURL_FOR_IMAP_SEND ++ else if (use_curl) ++ ret = curl_list_imap_folders(&server); ++#endif ++ else ++ ret = list_imap_folders(&server); ++ goto out; ++ } ++ ++ if (!server.folder) { ++ fprintf(stderr, "No IMAP store specified.\n"); ++ ret = 1; ++ goto out; ++ } ++ + /* read the messages */ + if (strbuf_read(&all_msgs, 0, 0) < 0) { + error_errno(_("Could not read from stdin.")); -- 2.49.0.638.g602e07a80b.dirty