[PATCH v18 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]

 



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.
v17: - Rebase on top of master where 30325e2 was causing a conflict.
       (Sorry for the bad range diff, but I think its easy to understand)
v18: - Avoid initialising variables with 0 or NULL. Let them remain
       uninitialised
     - Add a white at it note to the commit message of the patch that
       adds support to specify the folder.
     - Add another minor fix to the log that displays the unknown auth
       mechanism used. It was displaying the host rather than the mechanism.
     - Remove unecessary and pessimistic lines from the patch that enabled
       showing the host alongwith the port.

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 v17:
 -:  ---------- >  1:  4accbe6ecf imap-send: fix bug causing cfg->folder being set to NULL
 -:  ---------- >  2:  1cfd66ccea imap-send: fix memory leak in case auth_cram_md5 fails
 -:  ---------- >  3:  12ff5135be imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL
 1:  0c6283407c !  4:  43b18dbfb0 imap-send: enable specifying the folder using the command line
    @@ Metadata
     Author: Aditya Garg <gargaditya08@xxxxxxxx>
     
      ## Commit message ##
    -    imap-send: enable specifying the folder using the command line
    +    imap-send: add support for OAuth2.0 authentication
     
    -    Some users may very often want to imap-send messages to a folder
    -    other than the default set in the config. Add a command line
    -    argument for the same.
    +    OAuth2.0 is a new way of authentication supported by various email providers
    +    these days. OAUTHBEARER and XOAUTH2 are the two most common mechanisms used
    +    for OAuth2.0. OAUTHBEARER is described in RFC5801[1] and RFC7628[2], whereas
    +    XOAUTH2 is Google's proprietary mechanism (See [3]).
    +
    +    [1]: https://datatracker.ietf.org/doc/html/rfc5801
    +    [2]: https://datatracker.ietf.org/doc/html/rfc7628
    +    [3]: https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
     
         Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx>
     
      ## Documentation/config/imap.adoc ##
    -@@
    - imap.folder::
    - 	The folder to drop the mails into, which is typically the Drafts
    --	folder. For example: "INBOX.Drafts", "INBOX/Drafts" or
    --	"[Gmail]/Drafts". Required.
    -+	folder. For example: `INBOX.Drafts`, `INBOX/Drafts` or
    -+	`[Gmail]/Drafts`. The IMAP folder to interact with MUST be specified;
    -+	the value of this configuration variable is used as the fallback
    -+	default value when the `--folder` option is not given.
    - 
    - imap.tunnel::
    - 	Command used to set up a tunnel to the IMAP server through which
    +@@ Documentation/config/imap.adoc: imap.authMethod::
    + 	Specify the authentication method for authenticating with the IMAP server.
    + 	If Git was built with the NO_CURL option, or if your curl version is older
    + 	than 7.34.0, or if you're running git-imap-send with the `--no-curl`
    +-	option, the only supported method is 'CRAM-MD5'. If this is not set
    +-	then 'git imap-send' uses the basic IMAP plaintext LOGIN command.
    ++	option, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and
    ++	`XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP
    ++	plaintext `LOGIN` command.
     
      ## Documentation/git-imap-send.adoc ##
    -@@ Documentation/git-imap-send.adoc: git-imap-send - Send a collection of patches from stdin to an IMAP folder
    - SYNOPSIS
    - --------
    - [verse]
    --'git imap-send' [-v] [-q] [--[no-]curl]
    -+'git imap-send' [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>]
    - 
    - 
    - DESCRIPTION
    - -----------
    --This command uploads a mailbox generated with 'git format-patch'
    -+This command uploads a mailbox generated with `git format-patch`
    - into an IMAP drafts folder.  This allows patches to be sent as
    - other email is when using mail clients that cannot read mailbox
    - files directly. The command also works with any general mailbox
    --in which emails have the fields "From", "Date", and "Subject" in
    -+in which emails have the fields `From`, `Date`, and `Subject` in
    - that order.
    - 
    - Typical usage is something like:
    - 
    --git format-patch --signoff --stdout --attach origin | git imap-send
    -+------
    -+$ git format-patch --signoff --stdout --attach origin | git imap-send
    -+------
    - 
    - 
    - OPTIONS
    -@@ Documentation/git-imap-send.adoc: OPTIONS
    - --quiet::
    - 	Be quiet.
    - 
    -+-f <folder>::
    -+--folder=<folder>::
    -+	Specify the folder in which the emails have to saved.
    -+	For example: `--folder=[Gmail]/Drafts` or `-f INBOX/Drafts`.
    -+
    - --curl::
    - 	Use libcurl to communicate with the IMAP server, unless tunneling
    - 	into it.  Ignored if Git was built without the USE_CURL_FOR_IMAP_SEND
    -
    - ## imap-send.c ##
    -@@
    +@@ 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.
      
    - static int verbosity;
    - static int use_curl = USE_CURL_DEFAULT;
    -+static char *opt_folder = NULL;
    ++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:
      
    --static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <mbox>", NULL };
    -+static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>", NULL };
    +   $ 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).
      
    - static struct option imap_send_options[] = {
    - 	OPT__VERBOSITY(&verbosity),
    - 	OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
    -+	OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"),
    - 	OPT_END()
    ++In case you are using OAuth2.0 authentication, it is easier to use credential
    ++helpers to generate tokens. Credential helpers suggested in
    ++linkgit:git-send-email[1] can be used for `git imap-send` as well.
    ++
    + CAUTION
    + -------
    + It is still your responsibility to make sure that the email message
    +
    + ## imap-send.c ##
    +@@ imap-send.c: enum CAPABILITY {
    + 	LITERALPLUS,
    + 	NAMESPACE,
    + 	STARTTLS,
    +-	AUTH_CRAM_MD5
    ++	AUTH_CRAM_MD5,
    ++	AUTH_OAUTHBEARER,
    ++	AUTH_XOAUTH2,
    + };
    + 
    + static const char *cap_list[] = {
    +@@ imap-send.c: static const char *cap_list[] = {
    + 	"NAMESPACE",
    + 	"STARTTLS",
    + 	"AUTH=CRAM-MD5",
    ++	"AUTH=OAUTHBEARER",
    ++	"AUTH=XOAUTH2",
      };
      
    -@@ imap-send.c: int cmd_main(int argc, const char **argv)
    + #define RESP_OK    0
    +@@ imap-send.c: static char *cram(const char *challenge_64, const char *user, const char *pass)
    + 	return (char *)response_64;
    + }
      
    - 	argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
    ++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;
    + }
      
    -+	if (opt_folder) {
    -+		free(server.folder);
    -+		server.folder = xstrdup(opt_folder);
    ++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");
     +	}
     +
    - 	if (argc)
    - 		usage_with_options(imap_send_usage, imap_send_options);
    ++	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, (long)srvc->port);
    + 
    + 	if (srvc->auth_method) {
    +-		struct strbuf auth = STRBUF_INIT;
    +-		strbuf_addstr(&auth, "AUTH=");
    +-		strbuf_addstr(&auth, srvc->auth_method);
    +-		curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
    +-		strbuf_release(&auth);
    ++		if (!strcmp(srvc->auth_method, "XOAUTH2") ||
    ++		    !strcmp(srvc->auth_method, "OAUTHBEARER")) {
    ++
    ++			/*
    ++			 * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
    ++			 * upon debugging, it has been found that it is capable of detecting
    ++			 * the best option out of OAUTHBEARER and XOAUTH2.
    ++			 */
    ++			curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);
    ++		} else {
    ++			struct strbuf auth = STRBUF_INIT;
    ++			strbuf_addstr(&auth, "AUTH=");
    ++			strbuf_addstr(&auth, srvc->auth_method);
    ++			curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
    ++			strbuf_release(&auth);
    ++		}
    + 	}
      
    + 	if (!srvc->use_ssl)
 -:  ---------- >  5:  1ebf9f935f imap-send: add PLAIN authentication method to OpenSSL
 -:  ---------- >  6:  ce2cfa34cf imap-send: enable specifying the folder using the command line
 2:  f59cb1dca1 !  7:  5c36e68493 imap-send: add ability to list the available folders
    @@ imap-send.c
      #endif
      
      static int verbosity;
    -+static int list_folders = 0;
    ++static int list_folders;
      static int use_curl = USE_CURL_DEFAULT;
    - static char *opt_folder = NULL;
    + static char *opt_folder;
      
     -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[] = {
 3:  1247afbe78 !  8:  cc4f88791f imap-send: display port alongwith host when git credential is invoked
    @@ Commit message
         Also, this behaviour will also mimic git send-email, which displays
         the port along with the host name when requesting for a password.
     
    -    FWIW, if no port is specified by the user, the default port, 993 for
    -    IMAPS and 143 for IMAP is used by the code. So, the case of no port
    -    defined for the helper is not possible, and therefore is not added.
    -
         Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx>
     
      ## imap-send.c ##
 4:  c30ecbf508 =  9:  82432c7b21 imap-send: display the destination mailbox when sending a message
 5:  eaff4db692 ! 10:  d780afc026 imap-send: fix minor mistakes in the logs
    @@ Commit message
     
         Some minor mistakes have been found in the logs. Most of them include
         error messages starting with a capital letter, and ending with a period.
    -    Also, abbreviations like "IMAP" and "OK" should be in uppercase. Fix them.
    +    Abbreviations like "IMAP" and "OK" should also be in uppercase. Another
    +    mistake was that the error message showing unknown authentication
    +    mechanism used was displaying the host rather than the mechanism in the
    +    logs. Fix them.
     
         Signed-off-by: Aditya Garg <gargaditya08@xxxxxxxx>
     
    @@ imap-send.c: static struct imap_store *imap_open_store(struct imap_server_conf *
      					goto bail;
      			} else {
     -				fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
    -+				fprintf(stderr, "unknown authentication method:%s\n", srvc->host);
    ++				fprintf(stderr, "unknown authentication mechanism: %s\n", srvc->auth_method);
      				goto bail;
      			}
      		} else {
-- 
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