diff options
| author | Junio C Hamano <gitster@pobox.com> | 2025-06-20 08:29:34 -0700 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2025-06-20 08:29:34 -0700 |
| commit | 0ed16dc3c4f60ac79f9f51b4b991bf36d7ab73ea (patch) | |
| tree | 767b04736bd8d37c7173298083b4c3dbd2ed2bc5 | |
| parent | Git 2.50 (diff) | |
| parent | imap-send: fix minor mistakes in the logs (diff) | |
| download | git-0ed16dc3c4f60ac79f9f51b4b991bf36d7ab73ea.tar.gz git-0ed16dc3c4f60ac79f9f51b4b991bf36d7ab73ea.zip | |
Merge branch 'ag/imap-send-resurrection' into jt/imap-send-message-fix
* ag/imap-send-resurrection:
imap-send: fix minor mistakes in the logs
imap-send: display the destination mailbox when sending a message
imap-send: display port alongwith host when git credential is invoked
imap-send: add ability to list the available folders
imap-send: enable specifying the folder using the command line
imap-send: add PLAIN authentication method to OpenSSL
imap-send: add support for OAuth2.0 authentication
imap-send: gracefully fail if CRAM-MD5 authentication is requested without OpenSSL
imap-send: fix memory leak in case auth_cram_md5 fails
imap-send: fix bug causing cfg->folder being set to NULL
| -rw-r--r-- | Documentation/config/imap.adoc | 11 | ||||
| -rw-r--r-- | Documentation/git-imap-send.adoc | 68 | ||||
| -rw-r--r-- | imap-send.c | 405 |
3 files changed, 407 insertions, 77 deletions
diff --git a/Documentation/config/imap.adoc b/Documentation/config/imap.adoc index 3d28f72643..4682a6bd03 100644 --- a/Documentation/config/imap.adoc +++ b/Documentation/config/imap.adoc @@ -1,7 +1,9 @@ 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 @@ -40,5 +42,6 @@ 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 `PLAIN`, `CRAM-MD5`, `OAUTHBEARER` + and `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP + plaintext `LOGIN` command. diff --git a/Documentation/git-imap-send.adoc b/Documentation/git-imap-send.adoc index 26ccf4e433..17147f93c3 100644 --- a/Documentation/git-imap-send.adoc +++ b/Documentation/git-imap-send.adoc @@ -9,21 +9,24 @@ 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>] +'git imap-send' --list 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 @@ -37,6 +40,11 @@ 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 @@ -47,6 +55,8 @@ 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 ------------- @@ -102,20 +112,56 @@ Using Gmail's IMAP interface: --------- [imap] - folder = "[Gmail]/Drafts" - host = imaps://imap.gmail.com - user = user@gmail.com - port = 993 + folder = "[Gmail]/Drafts" + host = imaps://imap.gmail.com + user = user@gmail.com + 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". +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" 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@gmail.com + 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@outlook.com + 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 @@ -124,6 +170,10 @@ 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 diff --git a/imap-send.c b/imap-send.c index 2e812f5a6e..603e3d6fbc 100644 --- a/imap-send.c +++ b/imap-send.c @@ -45,13 +45,21 @@ #endif static int verbosity; +static int list_folders; static int use_curl = USE_CURL_DEFAULT; +static char *opt_folder; -static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <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() }; @@ -139,7 +147,10 @@ enum CAPABILITY { LITERALPLUS, NAMESPACE, STARTTLS, - AUTH_CRAM_MD5 + AUTH_PLAIN, + AUTH_CRAM_MD5, + AUTH_OAUTHBEARER, + AUTH_XOAUTH2, }; static const char *cap_list[] = { @@ -148,7 +159,10 @@ static const char *cap_list[] = { "LITERAL+", "NAMESPACE", "STARTTLS", + "AUTH=PLAIN", "AUTH=CRAM-MD5", + "AUTH=OAUTHBEARER", + "AUTH=XOAUTH2", }; #define RESP_OK 0 @@ -197,7 +211,7 @@ 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; } @@ -421,7 +435,7 @@ 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; } @@ -847,6 +861,38 @@ static char hexchar(unsigned int b) } #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3)) +static char *plain_base64(const char *user, const char *pass) +{ + struct strbuf raw = STRBUF_INIT; + int b64_len; + char *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 + */ + strbuf_addch(&raw, '\0'); + strbuf_addstr(&raw, user); + strbuf_addch(&raw, '\0'); + strbuf_addstr(&raw, pass); + + b64 = xmallocz(ENCODED_SIZE(raw.len)); + b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw.buf, raw.len); + strbuf_release(&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; @@ -885,17 +931,83 @@ static char *cram(const char *challenge_64, const char *user, const char *pass) return (char *)response_64; } -#else +static char *oauthbearer_base64(const char *user, const char *access_token) +{ + int b64_len; + char *raw, *b64; -static char *cram(const char *challenge_64 UNUSED, - const char *user UNUSED, - const char *pass UNUSED) + /* + * 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 = xstrfmt("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) { - die("If you want to use CRAM-MD5 authenticate method, " - "you have to build git-imap-send with OpenSSL library."); + int 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 = xstrfmt("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; } -#endif +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) { @@ -905,21 +1017,72 @@ static int auth_cram_md5(struct imap_store *ctx, const char *prompt) response = cram(prompt, ctx->cfg->user, ctx->cfg->pass); ret = socket_write(&ctx->imap->buf.sock, response, strlen(response)); - if (ret != strlen(response)) - return error("IMAP error: sending response failed"); + if (ret != strlen(response)) { + free(response); + return error("IMAP error: sending CRAM-MD5 response failed"); + } free(response); 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_plain NULL +#define auth_cram_md5 NULL +#define auth_oauthbearer NULL +#define auth_xoauth2 NULL + +#endif + static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred) { if (srvc->user && srvc->pass) return; cred->protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap"); - cred->host = xstrdup(srvc->host); + cred->host = xstrfmt("%s:%d", srvc->host, srvc->port); cred->username = xstrdup_or_null(srvc->user); cred->password = xstrdup_or_null(srvc->pass); @@ -932,6 +1095,38 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent srvc->pass = xstrdup(cred->password); } +static int try_auth_method(struct imap_server_conf *srvc, + struct imap_store *ctx, + struct imap *imap, + const char *auth_method, + enum CAPABILITY cap, + int (*fn)(struct imap_store *, const char *)) +{ + struct imap_cmd_cb cb = {0}; + + if (!CAP(cap)) { + fprintf(stderr, "You specified " + "%s as authentication method, " + "but %s doesn't support it.\n", + auth_method, srvc->host); + return -1; + } + cb.cont = fn; + + if (NOT_CONSTANT(!cb.cont)) { + fprintf(stderr, "If you want to use %s authentication mechanism, " + "you have to build git-imap-send with OpenSSL library.", + auth_method); + return -1; + } + if (imap_exec(ctx, &cb, "AUTHENTICATE %s", auth_method) != RESP_OK) { + fprintf(stderr, "IMAP error: AUTHENTICATE %s failed\n", + auth_method); + return -1; + } + return 0; +} + static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder) { struct credential cred = CREDENTIAL_INIT; @@ -964,7 +1159,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c 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; @@ -983,7 +1178,7 @@ 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]; @@ -1021,7 +1216,7 @@ 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]); @@ -1035,7 +1230,7 @@ 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; } @@ -1047,7 +1242,7 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c close(s); goto bail; } - imap_info("ok\n"); + imap_info("OK\n"); } /* read the greeting string */ @@ -1087,30 +1282,25 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c server_fill_credential(srvc, &cred); if (srvc->auth_method) { - struct imap_cmd_cb cb; - - if (!strcmp(srvc->auth_method, "CRAM-MD5")) { - if (!CAP(AUTH_CRAM_MD5)) { - fprintf(stderr, "You specified " - "CRAM-MD5 as authentication method, " - "but %s doesn't support it.\n", srvc->host); + if (!strcmp(srvc->auth_method, "PLAIN")) { + if (try_auth_method(srvc, ctx, imap, "PLAIN", AUTH_PLAIN, auth_plain)) goto bail; - } - /* CRAM-MD5 */ - - memset(&cb, 0, sizeof(cb)); - cb.cont = auth_cram_md5; - if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) { - fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n"); + } else 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); + fprintf(stderr, "unknown authentication mechanism: %s\n", srvc->auth_method); goto bail; } } 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; } @@ -1316,16 +1506,16 @@ static int git_imap_config(const char *var, const char *val, FREE_AND_NULL(cfg->folder); return git_config_string(&cfg->folder, var, val); } else if (!strcmp("imap.user", var)) { - FREE_AND_NULL(cfg->folder); + FREE_AND_NULL(cfg->user); return git_config_string(&cfg->user, var, val); } else if (!strcmp("imap.pass", var)) { - FREE_AND_NULL(cfg->folder); + FREE_AND_NULL(cfg->pass); return git_config_string(&cfg->pass, var, val); } else if (!strcmp("imap.tunnel", var)) { - FREE_AND_NULL(cfg->folder); + FREE_AND_NULL(cfg->tunnel); return git_config_string(&cfg->tunnel, var, val); } else if (!strcmp("imap.authmethod", var)) { - FREE_AND_NULL(cfg->folder); + FREE_AND_NULL(cfg->auth_method); return git_config_string(&cfg->auth_method, var, val); } else if (!strcmp("imap.port", var)) { cfg->port = git_config_int(var, val, ctx->kvi); @@ -1366,7 +1556,8 @@ 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 to %s folder...\n", + total, (total != 1) ? "s" : "", server->folder); while (1) { unsigned percent = n * 100 / total; @@ -1388,6 +1579,26 @@ 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) { @@ -1405,29 +1616,51 @@ 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); 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); 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) @@ -1436,10 +1669,6 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)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); @@ -1458,9 +1687,14 @@ 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\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; @@ -1503,6 +1737,31 @@ 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) @@ -1520,6 +1779,11 @@ int cmd_main(int argc, const char **argv) argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0); + if (opt_folder) { + free(server.folder); + server.folder = xstrdup(opt_folder); + } + if (argc) usage_with_options(imap_send_usage, imap_send_options); @@ -1538,20 +1802,33 @@ 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"); + fprintf(stderr, "no IMAP host specified\n"); ret = 1; goto out; } 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")); @@ -1567,7 +1844,7 @@ int cmd_main(int argc, const char **argv) 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; } |
