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 set a default between curl and openssl using the config. 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 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: enable user to choose between libcurl and openssl using the config 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 Documentation/config/imap.adoc | 17 +- Documentation/git-imap-send.adoc | 65 +++++- imap-send.c | 327 +++++++++++++++++++++++++++---- 3 files changed, 358 insertions(+), 51 deletions(-) Range-diff: -: ---------- > 1: 4757d0305d imap-send: fix bug causing cfg->folder being set to NULL 1: f5ad01abc5 ! 2: c4e2a5659b imap-send: add support for OAuth2.0 authentication @@ imap-send.c: static char *cram(const char *challenge_64 UNUSED, + const char *access_token UNUSED) +{ + die("You are trying to use OAUTHBEARER authenticate method " -+ "with OpenSSL library, but it's support has not been compiled in."); ++ "with OpenSSL library, but its support has not been compiled in."); +} + +static char *xoauth2_base64(const char *user UNUSED, + const char *access_token UNUSED) +{ + die("You are trying to use XOAUTH2 authenticate method " -+ "with OpenSSL library, but it's support has not been compiled in."); ++ "with OpenSSL library, but its support has not been compiled in."); +} + #endif -: ---------- > 3: af9aa85cab imap-send: add PLAIN authentication method to OpenSSL 3: 11f7ac1325 = 4: 2ca10774db imap-send: fix memory leak in case auth_cram_md5 fails 4: f6e7a5498e = 5: 190bed0bff imap-send: enable specifying the folder using the command line 5: 4769924781 = 6: 469c05321b imap-send: enable user to choose between libcurl and openssl using the config 2: e3dc19dc49 ! 7: 6a839e5f4d imap-send: add PLAIN authentication method to OpenSSL @@ Metadata Author: Aditya Garg <gargaditya08@xxxxxxxx> ## Commit message ## - imap-send: add PLAIN authentication method to OpenSSL + imap-send: fix numerous spelling and grammar mistakes in logs - The current implementation for PLAIN in imap-send works just fine - if using curl, but if attempted to use for OpenSSL, it is treated - as an invalid mechanism. The default implementation for OpenSSL is - IMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is - still used today by many email providers in form of app passwords, - lets add an implementation that can use AUTH PLAIN if specified. + A lot of spelling and grammar mistakes were found in the logs shown to + the user while using imap-send. Most of them are lack of a full stop at + the end of a sentence and first word of a sentence not being capitalized. Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx> - ## Documentation/config/imap.adoc ## -@@ Documentation/config/imap.adoc: imap.authMethod:: - Specify the authentication method for authenticating with the IMAP server. - If Git was built with the NO_CURL option, or if your curl version is older - than 7.34.0, or if you're running git-imap-send with the `--no-curl` -- option, the only supported methods are 'CRAM-MD5', 'OAUTHBEARER' and -- 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP -+ option, the only supported methods are 'PLAIN', 'CRAM-MD5', 'OAUTHBEARER' -+ and 'XOAUTH2'. If this is not set then `git imap-send` uses the basic IMAP - plaintext LOGIN command. - ## imap-send.c ## -@@ imap-send.c: enum CAPABILITY { - LITERALPLUS, - NAMESPACE, - STARTTLS, -+ AUTH_PLAIN, - AUTH_CRAM_MD5, - AUTH_OAUTHBEARER, - AUTH_XOAUTH2 -@@ imap-send.c: static const char *cap_list[] = { - "LITERAL+", - "NAMESPACE", - "STARTTLS", -+ "AUTH=PLAIN", - "AUTH=CRAM-MD5", - "AUTH=OAUTHBEARER", - "AUTH=XOAUTH2", -@@ imap-send.c: static char hexchar(unsigned int b) +@@ imap-send.c: static int ssl_socket_connect(struct imap_socket *sock UNUSED, + const struct imap_server_conf *cfg UNUSED, + int use_tls_only UNUSED) + { +- fprintf(stderr, "SSL requested but SSL support not compiled in\n"); ++ fprintf(stderr, "SSL requested, but SSL support is not compiled in.\n"); + return -1; } - #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3)) -+static char *plain_base64(const char *user, const char *pass) -+{ -+ int user_len = strlen(user); -+ int pass_len = strlen(pass); -+ int raw_len = 1 + user_len + 1 + pass_len; -+ int b64_len; -+ char *raw, *b64; -+ -+ /* Compose the PLAIN string -+ * -+ * The username and password are combined to one string and base64 encoded. -+ * "\0user\0pass" -+ * -+ * The method has been described in RFC4616. -+ * -+ * https://datatracker.ietf.org/doc/html/rfc4616 -+ */ -+ raw = xmallocz(raw_len); -+ raw[0] = '\0'; -+ memcpy(raw + 1, user, user_len); -+ raw[1 + user_len] = '\0'; -+ memcpy(raw + 2 + user_len, pass, pass_len); -+ -+ b64 = xmallocz(ENCODED_SIZE(raw_len)); -+ b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, raw_len); -+ free(raw); -+ -+ if (b64_len < 0) { -+ free(b64); -+ return NULL; -+ } -+ return b64; -+} -+ - static char *cram(const char *challenge_64, const char *user, const char *pass) - { - int i, resp_len, encoded_len, decoded_len; -@@ imap-send.c: static char *xoauth2_base64(const char *user, const char *access_token) - - #else - -+static char *plain_base64(const char *user UNUSED, -+ const char *access_token UNUSED) -+{ -+ die("You are trying to use PLAIN authenticate method " -+ "with OpenSSL library, but it's support has not been compiled in."); -+} -+ - static char *cram(const char *challenge_64 UNUSED, - const char *user UNUSED, - const char *pass UNUSED) -@@ imap-send.c: static char *xoauth2_base64(const char *user UNUSED, +@@ imap-send.c: static int verify_hostname(X509 *cert, const char *hostname) + + /* try the common name */ + if (!(subj = X509_get_subject_name(cert))) +- return error("cannot get certificate subject"); ++ return error("Cannot get certificate subject"); + if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0) +- return error("cannot get certificate common name"); ++ return error("Cannot get certificate common name"); + if (strlen(cname) == (size_t)len && host_matches(hostname, cname)) + return 0; + return error("certificate owner '%s' does not match hostname '%s'", +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass) + decoded_len = EVP_DecodeBlock((unsigned char *)challenge, + (unsigned char *)challenge_64, encoded_len); + if (decoded_len < 0) +- die("invalid challenge %s", challenge_64); ++ die("Invalid challenge %s", challenge_64); + if (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL)) + die("HMAC error"); + +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt) + ret = socket_write(&ctx->imap->buf.sock, response, strlen(response)); + if (ret != strlen(response)) { + free(response); +- return error("IMAP error: sending response failed"); ++ return error("IMAP error: sending CRAM-MD5 response failed"); + } + + free(response); +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c + tunnel.in = -1; + tunnel.out = -1; + if (start_command(&tunnel)) +- die("cannot start proxy %s", srvc->tunnel); ++ die("Cannot start proxy %s", srvc->tunnel); + imap->buf.sock.fd[0] = tunnel.out; + imap->buf.sock.fd[1] = tunnel.in; + +- imap_info("ok\n"); ++ imap_info("OK\n"); + } else { + #ifndef NO_IPV6 + struct addrinfo hints, *ai0, *ai; +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai)); + goto bail; + } +- imap_info("ok\n"); ++ imap_info("OK\n"); + + for (ai0 = ai; ai; ai = ai->ai_next) { + char addr[NI_MAXHOST]; +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c + perror("gethostbyname"); + goto bail; + } +- imap_info("ok\n"); ++ imap_info("OK\n"); + + addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); + +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c + } #endif + if (s < 0) { +- fputs("Error: unable to connect to server.\n", stderr); ++ fputs("Error: unable to connect to server\n", stderr); + goto bail; + } -+static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED) -+{ -+ int ret; -+ char *b64; -+ -+ b64 = plain_base64(ctx->cfg->user, ctx->cfg->pass); -+ if (!b64) -+ return error("PLAIN: base64 encoding failed"); -+ -+ /* Send the base64-encoded response */ -+ ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64)); -+ if (ret != (int)strlen(b64)) { -+ free(b64); -+ return error("IMAP error: sending PLAIN response failed"); -+ } -+ -+ free(b64); -+ return 0; -+} -+ - static int auth_cram_md5(struct imap_store *ctx, const char *prompt) - { - int ret; @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c - if (srvc->auth_method) { - struct imap_cmd_cb cb; - -- if (!strcmp(srvc->auth_method, "CRAM-MD5")) { -+ if (!strcmp(srvc->auth_method, "PLAIN")) { -+ if (!CAP(AUTH_PLAIN)) { -+ fprintf(stderr, "You specified " -+ "PLAIN as authentication method, " -+ "but %s doesn't support it.\n", srvc->host); -+ goto bail; -+ } -+ /* PLAIN */ -+ -+ memset(&cb, 0, sizeof(cb)); -+ cb.cont = auth_plain; -+ if (imap_exec(ctx, &cb, "AUTHENTICATE PLAIN") != RESP_OK) { -+ fprintf(stderr, "IMAP error: AUTHENTICATE PLAIN failed\n"); -+ goto bail; -+ } -+ } else if (!strcmp(srvc->auth_method, "CRAM-MD5")) { - if (!CAP(AUTH_CRAM_MD5)) { - fprintf(stderr, "You specified " - "CRAM-MD5 as authentication method, " + close(s); + goto bail; + } +- imap_info("ok\n"); ++ imap_info("OK\n"); + } + + /* read the greeting string */ +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c + } + } else { + if (CAP(NOLOGIN)) { +- fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", ++ fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN.\n", + srvc->user, srvc->host); + goto bail; + } + if (!imap->buf.sock.ssl) + imap_warn("*** IMAP Warning *** Password is being " +- "sent in the clear\n"); ++ "sent in the clear.\n"); + if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { + fprintf(stderr, "IMAP error: LOGIN failed\n"); + goto bail; +@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server, + + ctx = imap_open_store(server, server->folder); + if (!ctx) { +- fprintf(stderr, "failed to open store\n"); ++ fprintf(stderr, "Failed to open store.\n"); + return 1; + } + ctx->name = server->folder; + +- fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : ""); ++ fprintf(stderr, "Sending %d message%s\n", total, (total != 1) ? "s" : ""); + while (1) { + unsigned percent = n * 100 / total; + +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) + + uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0); + if (!uri_encoded_folder) +- die("failed to encode server folder"); ++ die("Failed to encode server folder."); + strbuf_addstr(&path, uri_encoded_folder); + curl_free(uri_encoded_folder); + +@@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server, + curl = setup_curl(server, &cred); + curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf); + +- fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : ""); ++ fprintf(stderr, "Sending %d message%s\n", total, (total != 1) ? "s" : ""); + while (1) { + unsigned percent = n * 100 / total; + int prev_len; +@@ imap-send.c: int cmd_main(int argc, const char **argv) + server.port = server.use_ssl ? 993 : 143; + + if (!server.folder) { +- fprintf(stderr, "no imap store specified\n"); ++ fprintf(stderr, "No IMAP store specified.\n"); + ret = 1; + goto out; + } + if (!server.host) { + if (!server.tunnel) { +- fprintf(stderr, "no imap host specified\n"); ++ fprintf(stderr, "No IMAP host specified.\n"); + ret = 1; + goto out; + } +@@ imap-send.c: int cmd_main(int argc, const char **argv) + + /* read the messages */ + if (strbuf_read(&all_msgs, 0, 0) < 0) { +- error_errno(_("could not read from stdin")); ++ error_errno(_("Could not read from stdin.")); + ret = 1; + goto out; + } + + if (all_msgs.len == 0) { +- fprintf(stderr, "nothing to send\n"); ++ fprintf(stderr, "Nothing to send.\n"); + ret = 1; + goto out; + } + + total = count_messages(&all_msgs); + if (!total) { +- fprintf(stderr, "no messages to send\n"); ++ fprintf(stderr, "No messages found to send.\n"); + ret = 1; + goto out; + } -: ---------- > 8: a60d8f458f imap-send: display port alongwith host when git credential is invoked -: ---------- > 9: 5db5b64a3b imap-send: display the destination mailbox when sending a message -- 2.49.0.638.g5db5b64a3b.dirty