[PATCH v16 00/10] imap-send: make it usable again and add OAuth2.0 support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.

Lastly, 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.

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
v9:  - Encourage users to use OAuth2.0 for Gmail (similar change done for send-email docs).
v10: - Fix comment styles
     - Fix failing tests
v11: - Use lower case letters for the first word of a sendtence in an error message
       and avoid using full stops at the end of a sentence.
v12: - Gracefully exit PLAIN, CRAM-MD5, OAUTHBEARER and XOAUTH2 authentication methods
       if OpenSSL support is not compiled in, but is requested by the user.
     - Use backticks for string literals.
     - Wrap documentation text to 75 columns.
     - End the last member of enum CAPABILITY with a trailing comma.
v13: - Fix logic error which was using || instead of && when checking if
       the authentication method is neither XOAUTH2 nor OAUTHBEARER.
v14: - Specify why we are not using CURLOPT_PASSWORD for OAuth2.0
       methods using a comment.
     - Add a function try_auth_method() to reduce code duplication
       when trying to authenticate using a specific method.
v15: - Simply rearrange the patches to make the cram md5 patches come
       before adding OAuth2.0 and PLAIN authentication methods. No 
       change has been done to the code itself.
v16: - Rearrage some more patches so that the two new features, i.e.,
       --folder and --list come just after the new authentication
       methods. Then the two patches with minor improvements of displaying
       the destination mailbox and displaying port alongwith host have
       been added. The patch fixing other minor mistakes in the logs has
       been moved to the end. Just like v15, no change has been done
       to the code itself.

Aditya Garg (10):
  imap-send: fix bug causing cfg->folder being set to NULL
  imap-send: fix memory leak in case auth_cram_md5 fails
  imap-send: gracefully fail if CRAM-MD5 authentication is requested
    without OpenSSL
  imap-send: add support for OAuth2.0 authentication
  imap-send: add PLAIN authentication method to OpenSSL
  imap-send: enable specifying the folder using the command line
  imap-send: add ability to list the available folders
  imap-send: display port alongwith host when git credential is invoked
  imap-send: display the destination mailbox when sending a message
  imap-send: fix minor mistakes in the logs

 Documentation/config/imap.adoc   |  11 +-
 Documentation/git-imap-send.adoc |  68 ++++-
 imap-send.c                      | 412 ++++++++++++++++++++++++++-----
 3 files changed, 414 insertions(+), 77 deletions(-)

Range-diff against v15:
 -:  ---------- >  1:  3e3ddf7077 imap-send: fix bug causing cfg->folder being set to NULL
 -:  ---------- >  2:  417b3b8e38 imap-send: fix memory leak in case auth_cram_md5 fails
 -:  ---------- >  3:  c4216528e7 imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL
 3:  668e62c0e0 !  4:  b38fca0e6a imap-send: display the destination mailbox when sending a message
    @@ Metadata
     Author: Aditya Garg <gargaditya08@xxxxxxxx>
     
      ## Commit message ##
    -    imap-send: display the destination mailbox when sending a message
    +    imap-send: add support for OAuth2.0 authentication
     
    -    Whenever we sent a message using the `imap-send` command, it would
    -    display a log showing the number of messages which are to be sent.
    -    For example:
    +    OAuth2.0 is a new way of authentication supported by various email providers
    +    these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used
    +    for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas
    +    XOAUTH2 is Google's proprietary mechanism (See [3]).
     
    -        Sending 1 message
    -         100% (1/1) done
    +    [1]: https://datatracker.ietf.org/doc/html/rfc5801
    +    [2]: https://datatracker.ietf.org/doc/html/rfc7628
    +    [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
     
    -    This had been made more informative by adding the name of the destination
    -    folder as well:
    +    Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx>
     
    -        Sending 1 message to Drafts folder...
    -         100% (1/1) done
    + ## 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 method is 'CRAM-MD5'. If this is not set
    +-	then 'git imap-send' uses the basic IMAP plaintext LOGIN command.
    ++	option, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and
    ++	`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP
    ++	plaintext `LOGIN` command.
     
    -    Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx>
    + ## Documentation/git-imap-send.adoc ##
    +@@ Documentation/git-imap-send.adoc: Using Gmail's IMAP interface:
    + 
    + ---------
    + [imap]
    +-	folder = "[Gmail]/Drafts"
    +-	host = imaps://imap.gmail.com
    +-	user = user@xxxxxxxxx
    +-	port = 993
    ++    folder = "[Gmail]/Drafts"
    ++    host = imaps://imap.gmail.com
    ++    user = user@xxxxxxxxx
    ++    port = 993
    + ---------
    + 
    ++Gmail does not allow using your regular password for `git imap-send`.
    ++If you have multi-factor authentication set up on your Gmail account, you
    ++can generate an app-specific password for use with `git imap-send`.
    ++Visit https://security.google.com/settings/security/apppasswords to create
    ++it. Alternatively, use OAuth2.0 authentication as described below.
    ++
    + [NOTE]
    + You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
    + that the "Folder doesn't exist".
    +@@ Documentation/git-imap-send.adoc: that the "Folder doesn't exist".
    + If your Gmail account is set to another language than English, the name of the "Drafts"
    + folder will be localized.
    + 
    ++If you want to use OAuth2.0 based authentication, you can specify
    ++`OAUTHBEARER` or `XOAUTH2` mechanism in your config. It is more secure
    ++than using app-specific passwords, and also does not enforce the need of
    ++having multi-factor authentication. You will have to use an OAuth2.0
    ++access token in place of your password when using this authentication.
    ++
    ++---------
    ++[imap]
    ++    folder = "[Gmail]/Drafts"
    ++    host = imaps://imap.gmail.com
    ++    user = user@xxxxxxxxx
    ++    port = 993
    ++    authmethod = OAUTHBEARER
    ++---------
    ++
    ++Using Outlook's IMAP interface:
    ++
    ++Unlike Gmail, Outlook only supports OAuth2.0 based authentication. Also, it
    ++supports only `XOAUTH2` as the mechanism.
    ++
    ++---------
    ++[imap]
    ++    folder = "Drafts"
    ++    host = imaps://outlook.office365.com
    ++    user = user@xxxxxxxxxxx
    ++    port = 993
    ++    authmethod = XOAUTH2
    ++---------
    ++
    + Once the commits are ready to be sent, run the following command:
    + 
    +   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
    +@@ Documentation/git-imap-send.adoc: Just make sure to disable line wrapping in the email client (Gmail's web
    + interface will wrap lines no matter what, so you need to use a real
    + IMAP client).
    + 
    ++In case you are using OAuth2.0 authentication, it is easier to use credential
    ++helpers to generate tokens. Credential helpers suggested in
    ++linkgit:git-send-email[1] can be used for `git imap-send` as well.
    ++
    + CAUTION
    + -------
    + It is still your responsibility to make sure that the email message
     
      ## imap-send.c ##
    -@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,
    +@@ imap-send.c: enum CAPABILITY {
    + 	LITERALPLUS,
    + 	NAMESPACE,
    + 	STARTTLS,
    +-	AUTH_CRAM_MD5
    ++	AUTH_CRAM_MD5,
    ++	AUTH_OAUTHBEARER,
    ++	AUTH_XOAUTH2,
    + };
    + 
    + static const char *cap_list[] = {
    +@@ imap-send.c: static const char *cap_list[] = {
    + 	"NAMESPACE",
    + 	"STARTTLS",
    + 	"AUTH=CRAM-MD5",
    ++	"AUTH=OAUTHBEARER",
    ++	"AUTH=XOAUTH2",
    + };
    + 
    + #define RESP_OK    0
    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)
    + 	return (char *)response_64;
    + }
    + 
    ++static char *oauthbearer_base64(const char *user, const char *access_token)
    ++{
    ++	int raw_len, b64_len;
    ++	char *raw, *b64;
    ++
    ++	/*
    ++	 * Compose the OAUTHBEARER string
    ++	 *
    ++	 * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
    ++	 *
    ++	 * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
    ++	 * * gs2-cb-flag `n` -> client does not support CB
    ++	 * * gs2-authzid `a=" {User} "`
    ++	 *
    ++	 * The second part are key value pairs containing host, port and auth as
    ++	 * described in RFC7628.
    ++	 *
    ++	 * https://datatracker.ietf.org/doc/html/rfc5801
    ++	 * https://datatracker.ietf.org/doc/html/rfc7628
    ++	 */
    ++	raw_len = strlen(user) + strlen(access_token) + 20;
    ++	raw = xmallocz(raw_len + 1);
    ++	snprintf(raw, raw_len + 1, "n,a=%s,\001auth=Bearer %s\001\001", user, access_token);
    ++
    ++	/* Base64 encode */
    ++	b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
    ++	b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
    ++	free(raw);
    ++
    ++	if (b64_len < 0) {
    ++		free(b64);
    ++		return NULL;
    ++	}
    ++	return b64;
    ++}
    ++
    ++static char *xoauth2_base64(const char *user, const char *access_token)
    ++{
    ++	int raw_len, b64_len;
    ++	char *raw, *b64;
    ++
    ++	/*
    ++	 * Compose the XOAUTH2 string
    ++	 * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
    ++	 * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
    ++	 */
    ++	raw_len = strlen(user) + strlen(access_token) + 20;
    ++	raw = xmallocz(raw_len + 1);
    ++	snprintf(raw, raw_len + 1, "user=%s\001auth=Bearer %s\001\001", user, access_token);
    ++
    ++	/* Base64 encode */
    ++	b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
    ++	b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
    ++	free(raw);
    ++
    ++	if (b64_len < 0) {
    ++		free(b64);
    ++		return NULL;
    ++	}
    ++	return b64;
    ++}
    ++
    + static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
    + {
    + 	int ret;
    +@@ imap-send.c: static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
    + 	return 0;
    + }
    + 
    ++static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)
    ++{
    ++	int ret;
    ++	char *b64;
    ++
    ++	b64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);
    ++	if (!b64)
    ++		return error("OAUTHBEARER: base64 encoding failed");
    ++
    ++	/* Send the base64-encoded response */
    ++	ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
    ++	if (ret != (int)strlen(b64)) {
    ++		free(b64);
    ++		return error("IMAP error: sending OAUTHBEARER response failed");
    ++	}
    ++
    ++	free(b64);
    ++	return 0;
    ++}
    ++
    ++static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)
    ++{
    ++	int ret;
    ++	char *b64;
    ++
    ++	b64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);
    ++	if (!b64)
    ++		return error("XOAUTH2: base64 encoding failed");
    ++
    ++	/* Send the base64-encoded response */
    ++	ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
    ++	if (ret != (int)strlen(b64)) {
    ++		free(b64);
    ++		return error("IMAP error: sending XOAUTH2 response failed");
    ++	}
    ++
    ++	free(b64);
    ++	return 0;
    ++}
    ++
    + #else
    + 
    + #define auth_cram_md5 NULL
    ++#define auth_oauthbearer NULL
    ++#define auth_xoauth2 NULL
    + 
    + #endif
    + 
    +@@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
    + 			if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
    + 				if (try_auth_method(srvc, ctx, imap, "CRAM-MD5", AUTH_CRAM_MD5, auth_cram_md5))
    + 					goto bail;
    ++			} else if (!strcmp(srvc->auth_method, "OAUTHBEARER")) {
    ++				if (try_auth_method(srvc, ctx, imap, "OAUTHBEARER", AUTH_OAUTHBEARER, auth_oauthbearer))
    ++					goto bail;
    ++			} else if (!strcmp(srvc->auth_method, "XOAUTH2")) {
    ++				if (try_auth_method(srvc, ctx, imap, "XOAUTH2", AUTH_XOAUTH2, auth_xoauth2))
    ++					goto bail;
    + 			} else {
    + 				fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
    + 				goto bail;
    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
    + 
    + 	server_fill_credential(srvc, cred);
    + 	curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);
    +-	curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
    ++
    ++	/*
    ++	 * Use CURLOPT_PASSWORD irrespective of whether there is
    ++	 * an auth method specified or not, unless it's OAuth2.0,
    ++	 * where we use CURLOPT_XOAUTH2_BEARER.
    ++	 */
    ++	if (!srvc->auth_method ||
    ++	    (strcmp(srvc->auth_method, "XOAUTH2") &&
    ++	    strcmp(srvc->auth_method, "OAUTHBEARER")))
    ++		curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
    + 
    + 	strbuf_addstr(&path, srvc->use_ssl ? "imaps://" : "imap://");
    + 	strbuf_addstr(&path, srvc->host);
    +@@ imap-send.c: static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
    + 	curl_easy_setopt(curl, CURLOPT_PORT, srvc->port);
    + 
    + 	if (srvc->auth_method) {
    +-		struct strbuf auth = STRBUF_INIT;
    +-		strbuf_addstr(&auth, "AUTH=");
    +-		strbuf_addstr(&auth, srvc->auth_method);
    +-		curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
    +-		strbuf_release(&auth);
    ++		if (!strcmp(srvc->auth_method, "XOAUTH2") ||
    ++		    !strcmp(srvc->auth_method, "OAUTHBEARER")) {
    ++
    ++			/*
    ++			 * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
    ++			 * upon debugging, it has been found that it is capable of detecting
    ++			 * the best option out of OAUTHBEARER and XOAUTH2.
    ++			 */
    ++			curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);
    ++		} else {
    ++			struct strbuf auth = STRBUF_INIT;
    ++			strbuf_addstr(&auth, "AUTH=");
    ++			strbuf_addstr(&auth, srvc->auth_method);
    ++			curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
    ++			strbuf_release(&auth);
    ++		}
      	}
    - 	ctx->name = server->folder;
    - 
    --	fprintf(stderr, "Sending %d message%s\n", total, (total != 1) ? "s" : "");
    -+	fprintf(stderr, "Sending %d message%s to %s folder...\n",
    -+		total, (total != 1) ? "s" : "", server->folder);
    - 	while (1) {
    - 		unsigned percent = n * 100 / total;
    - 
    -@@ 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 to %s folder...\n",
    -+		total, (total != 1) ? "s" : "", server->folder);
    - 	while (1) {
    - 		unsigned percent = n * 100 / total;
    - 		int prev_len;
    + 
    + 	if (!srvc->use_ssl)
 -:  ---------- >  5:  86d3d2c54d imap-send: add PLAIN authentication method to OpenSSL
 -:  ---------- >  6:  7674e749c8 imap-send: enable specifying the folder using the command line
 4:  4d9a3b5661 !  7:  90ce3a63f3 imap-send: add ability to list the available folders
    @@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server
     +
      	curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
      
    - 	fprintf(stderr, "Sending %d message%s to %s folder...\n",
    + 	fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
     @@ imap-send.c: static int curl_append_msgs_to_imap(struct imap_server_conf *server,
      
      	return res != CURLE_OK;
    @@ 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");
     @@ imap-send.c: int cmd_main(int argc, const char **argv)
      		server.host = xstrdup("tunnel");
      	}
    @@ imap-send.c: int cmd_main(int argc, const char **argv)
     +	}
     +
     +	if (!server.folder) {
    -+		fprintf(stderr, "no IMAP store specified\n");
    ++		fprintf(stderr, "no imap store specified\n");
     +		ret = 1;
     +		goto out;
     +	}
 2:  b2e7ef35ed =  8:  1bdd054908 imap-send: display port alongwith host when git credential is invoked
 -:  ---------- >  9:  e381120ab5 imap-send: display the destination mailbox when sending a message
 1:  a67322ce06 ! 10:  6561d45bee imap-send: fix minor mistakes in the logs
    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *
      					srvc->user, srvc->host);
      				goto bail;
      			}
    -@@ imap-send.c: static int append_msgs_to_imap(struct imap_server_conf *server,
    - 	}
    - 	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 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");
    @@ imap-send.c: int cmd_main(int argc, const char **argv)
      			goto out;
      		}
     @@ imap-send.c: int cmd_main(int argc, const char **argv)
    + 	}
    + 
    + 	if (!server.folder) {
    +-		fprintf(stderr, "no imap store specified\n");
    ++		fprintf(stderr, "no IMAP store specified\n");
    + 		ret = 1;
    + 		goto out;
    + 	}
    +@@ imap-send.c: int cmd_main(int argc, const char **argv)
      
      	total = count_messages(&all_msgs);
      	if (!total) {
-- 
2.49.0





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux