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
This commit is contained in:
Junio C Hamano
2025-06-20 08:29:34 -07:00
3 changed files with 408 additions and 78 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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 *cram(const char *challenge_64 UNUSED,
const char *user UNUSED,
const char *pass UNUSED)
static char *oauthbearer_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 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;
}
#endif
static char *xoauth2_base64(const char *user, const char *access_token)
{
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;
}
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;
}