From: Bernhard Reiter <ockham@raz.or.at>
To: git@vger.kernel.org
Subject: [PATCH/RFC] git-imap-send: use libcurl for implementation
Date: Tue, 12 Aug 2014 23:50:54 +0200 [thread overview]
Message-ID: <53EA8C3E.1080500@raz.or.at> (raw)
[-- Attachment #1: Type: text/plain, Size: 1162 bytes --]
Use libcurl's high-level API functions to implement git-imap-send
instead of the previous low-level OpenSSL-based functions.
Signed-off-by: Bernhard Reiter <ockham@raz.or.at>
---
Since version 7.30.0, libcurl's API has been able to communicate with
IMAP servers. Using those high-level functions instead of the current
ones reduces imap-send.c by some 1200 lines of code.
As I don't have access to that many IMAP servers, I haven't been able to
test a variety of parameter combinations. I did test both secure and
insecure (imaps:// and imap://) connections -- this e-mail was generated
that way -- but could e.g. neither test the authMethod nor the tunnel
parameter.
As git-imap-send is one of the two instances OpenSSL is currently used
by git -- the other one being SHA1 -- it might be worthwhile considering
dropping it altogether as there's already a SHA1 library built into git
available as an alternative.
Kind regards
Bernhard
PS: Please CC!
INSTALL | 14 +-
Makefile | 4 +-
git.spec.in | 5 +-
imap-send.c | 1288
+++++------------------------------------------------------
4 files changed, 111 insertions(+), 1200 deletions(-)
[-- Attachment #2: 0001-git-imap-send-use-libcurl-for-implementation.patch --]
[-- Type: text/x-patch, Size: 36413 bytes --]
diff --git a/INSTALL b/INSTALL
index 6ec7a24..2cd3a42 100644
--- a/INSTALL
+++ b/INSTALL
@@ -108,18 +108,16 @@ Issues of note:
so you might need to install additional packages other than Perl
itself, e.g. Time::HiRes.
- - "openssl" library is used by git-imap-send to use IMAP over SSL.
- If you don't need it, use NO_OPENSSL.
-
- By default, git uses OpenSSL for SHA1 but it will use its own
+ - By default, git uses OpenSSL for SHA1 but it will use its own
library (inspired by Mozilla's) with either NO_OPENSSL or
BLK_SHA1. Also included is a version optimized for PowerPC
(PPC_SHA1).
- - "libcurl" library is used by git-http-fetch and git-fetch. You
- might also want the "curl" executable for debugging purposes.
- If you do not use http:// or https:// repositories, you do not
- have to have them (use NO_CURL).
+ - "libcurl" library is used by git-http-fetch, git-fetch, and
+ git-impap-send. You might also want the "curl" executable for
+ debugging purposes. If you do not use http:// or https://
+ repositories, and do not want to put patches into an IMAP
+ mailbox, you do not have to have them (use NO_CURL).
- "expat" library; git-http-push uses it for remote lock
management over DAV. Similar to "curl" above, this is optional
diff --git a/Makefile b/Makefile
index 2320de5..7805603 100644
--- a/Makefile
+++ b/Makefile
@@ -2067,9 +2067,9 @@ endif
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-git-imap-send$X: imap-send.o GIT-LDFLAGS $(GITLIBS)
+git-imap-send$X: imap-send.o http.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
- $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
+ $(LIBS) $(CURL_LIBCURL)
git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
diff --git a/git.spec.in b/git.spec.in
index d61d537..7d9230f 100644
--- a/git.spec.in
+++ b/git.spec.in
@@ -8,7 +8,7 @@ License: GPL
Group: Development/Tools
URL: http://kernel.org/pub/software/scm/git/
Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
-BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
+BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel >= 7.30.0, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
Requires: perl-Git = %{version}-%{release}
@@ -214,6 +214,9 @@ rm -rf $RPM_BUILD_ROOT
# No files for you!
%changelog
+* Mon Aug 11 2014 Bernhard Reiter <ockham@raz.or.at>
+- Require version >= 7.30.0 of curl-devel for IMAP functions.
+
* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com>
- Add gitweb manpages to 'gitweb' subpackage
diff --git a/imap-send.c b/imap-send.c
index 524fbab..0c4583f 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -22,47 +22,13 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include "cache.h"
-#include "credential.h"
+#include "http.h"
#include "exec_cmd.h"
#include "run-command.h"
-#ifdef NO_OPENSSL
-typedef void *SSL;
-#endif
-static const char imap_send_usage[] = "git imap-send < <mbox>";
-
-#undef DRV_OK
-#define DRV_OK 0
-#define DRV_MSG_BAD -1
-#define DRV_BOX_BAD -2
-#define DRV_STORE_BAD -3
-
-static int Verbose, Quiet;
-
-__attribute__((format (printf, 1, 2)))
-static void imap_info(const char *, ...);
-__attribute__((format (printf, 1, 2)))
-static void imap_warn(const char *, ...);
-
-static char *next_arg(char **);
-
-__attribute__((format (printf, 3, 4)))
-static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
+#include <curl/curl.h>
-static int nfvasprintf(char **strp, const char *fmt, va_list ap)
-{
- int len;
- char tmp[8192];
-
- len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
- if (len < 0)
- die("Fatal: Out of memory");
- if (len >= sizeof(tmp))
- die("imap command overflow!");
- *strp = xmemdupz(tmp, len);
- return len;
-}
+static const char imap_send_usage[] = "git imap-send < <mbox>";
struct imap_server_conf {
char *name;
@@ -90,1144 +56,6 @@ static struct imap_server_conf server = {
NULL, /* auth_method */
};
-struct imap_socket {
- int fd[2];
- SSL *ssl;
-};
-
-struct imap_buffer {
- struct imap_socket sock;
- int bytes;
- int offset;
- char buf[1024];
-};
-
-struct imap_cmd;
-
-struct imap {
- int uidnext; /* from SELECT responses */
- unsigned caps, rcaps; /* CAPABILITY results */
- /* command queue */
- int nexttag, num_in_progress, literal_pending;
- struct imap_cmd *in_progress, **in_progress_append;
- struct imap_buffer buf; /* this is BIG, so put it last */
-};
-
-struct imap_store {
- /* currently open mailbox */
- const char *name; /* foreign! maybe preset? */
- int uidvalidity;
- struct imap *imap;
- const char *prefix;
-};
-
-struct imap_cmd_cb {
- int (*cont)(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt);
- void (*done)(struct imap_store *ctx, struct imap_cmd *cmd, int response);
- void *ctx;
- char *data;
- int dlen;
- int uid;
- unsigned create:1, trycreate:1;
-};
-
-struct imap_cmd {
- struct imap_cmd *next;
- struct imap_cmd_cb cb;
- char *cmd;
- int tag;
-};
-
-#define CAP(cap) (imap->caps & (1 << (cap)))
-
-enum CAPABILITY {
- NOLOGIN = 0,
- UIDPLUS,
- LITERALPLUS,
- NAMESPACE,
- STARTTLS,
- AUTH_CRAM_MD5
-};
-
-static const char *cap_list[] = {
- "LOGINDISABLED",
- "UIDPLUS",
- "LITERAL+",
- "NAMESPACE",
- "STARTTLS",
- "AUTH=CRAM-MD5",
-};
-
-#define RESP_OK 0
-#define RESP_NO 1
-#define RESP_BAD 2
-
-static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
-
-
-#ifndef NO_OPENSSL
-static void ssl_socket_perror(const char *func)
-{
- fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), NULL));
-}
-#endif
-
-static void socket_perror(const char *func, struct imap_socket *sock, int ret)
-{
-#ifndef NO_OPENSSL
- if (sock->ssl) {
- int sslerr = SSL_get_error(sock->ssl, ret);
- switch (sslerr) {
- case SSL_ERROR_NONE:
- break;
- case SSL_ERROR_SYSCALL:
- perror("SSL_connect");
- break;
- default:
- ssl_socket_perror("SSL_connect");
- break;
- }
- } else
-#endif
- {
- if (ret < 0)
- perror(func);
- else
- fprintf(stderr, "%s: unexpected EOF\n", func);
- }
-}
-
-#ifdef NO_OPENSSL
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
-{
- fprintf(stderr, "SSL requested but SSL support not compiled in\n");
- return -1;
-}
-
-#else
-
-static int host_matches(const char *host, const char *pattern)
-{
- if (pattern[0] == '*' && pattern[1] == '.') {
- pattern += 2;
- if (!(host = strchr(host, '.')))
- return 0;
- host++;
- }
-
- return *host && *pattern && !strcasecmp(host, pattern);
-}
-
-static int verify_hostname(X509 *cert, const char *hostname)
-{
- int len;
- X509_NAME *subj;
- char cname[1000];
- int i, found;
- STACK_OF(GENERAL_NAME) *subj_alt_names;
-
- /* try the DNS subjectAltNames */
- found = 0;
- if ((subj_alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) {
- int num_subj_alt_names = sk_GENERAL_NAME_num(subj_alt_names);
- for (i = 0; !found && i < num_subj_alt_names; i++) {
- GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i);
- if (subj_alt_name->type == GEN_DNS &&
- strlen((const char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length &&
- host_matches(hostname, (const char *)(subj_alt_name->d.ia5->data)))
- found = 1;
- }
- sk_GENERAL_NAME_pop_free(subj_alt_names, GENERAL_NAME_free);
- }
- if (found)
- return 0;
-
- /* try the common name */
- if (!(subj = X509_get_subject_name(cert)))
- 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");
- if (strlen(cname) == (size_t)len && host_matches(hostname, cname))
- return 0;
- return error("certificate owner '%s' does not match hostname '%s'",
- cname, hostname);
-}
-
-static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
-{
-#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
- const SSL_METHOD *meth;
-#else
- SSL_METHOD *meth;
-#endif
- SSL_CTX *ctx;
- int ret;
- X509 *cert;
-
- SSL_library_init();
- SSL_load_error_strings();
-
- if (use_tls_only)
- meth = TLSv1_method();
- else
- meth = SSLv23_method();
-
- if (!meth) {
- ssl_socket_perror("SSLv23_method");
- return -1;
- }
-
- ctx = SSL_CTX_new(meth);
-
- if (verify)
- SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
-
- if (!SSL_CTX_set_default_verify_paths(ctx)) {
- ssl_socket_perror("SSL_CTX_set_default_verify_paths");
- return -1;
- }
- sock->ssl = SSL_new(ctx);
- if (!sock->ssl) {
- ssl_socket_perror("SSL_new");
- return -1;
- }
- if (!SSL_set_rfd(sock->ssl, sock->fd[0])) {
- ssl_socket_perror("SSL_set_rfd");
- return -1;
- }
- if (!SSL_set_wfd(sock->ssl, sock->fd[1])) {
- ssl_socket_perror("SSL_set_wfd");
- return -1;
- }
-
-#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
- /*
- * SNI (RFC4366)
- * OpenSSL does not document this function, but the implementation
- * returns 1 on success, 0 on failure after calling SSLerr().
- */
- ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
- if (ret != 1)
- warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
-#endif
-
- ret = SSL_connect(sock->ssl);
- if (ret <= 0) {
- socket_perror("SSL_connect", sock, ret);
- return -1;
- }
-
- if (verify) {
- /* make sure the hostname matches that of the certificate */
- cert = SSL_get_peer_certificate(sock->ssl);
- if (!cert)
- return error("unable to get peer certificate.");
- if (verify_hostname(cert, server.host) < 0)
- return -1;
- }
-
- return 0;
-}
-#endif
-
-static int socket_read(struct imap_socket *sock, char *buf, int len)
-{
- ssize_t n;
-#ifndef NO_OPENSSL
- if (sock->ssl)
- n = SSL_read(sock->ssl, buf, len);
- else
-#endif
- n = xread(sock->fd[0], buf, len);
- if (n <= 0) {
- socket_perror("read", sock, n);
- close(sock->fd[0]);
- close(sock->fd[1]);
- sock->fd[0] = sock->fd[1] = -1;
- }
- return n;
-}
-
-static int socket_write(struct imap_socket *sock, const char *buf, int len)
-{
- int n;
-#ifndef NO_OPENSSL
- if (sock->ssl)
- n = SSL_write(sock->ssl, buf, len);
- else
-#endif
- n = write_in_full(sock->fd[1], buf, len);
- if (n != len) {
- socket_perror("write", sock, n);
- close(sock->fd[0]);
- close(sock->fd[1]);
- sock->fd[0] = sock->fd[1] = -1;
- }
- return n;
-}
-
-static void socket_shutdown(struct imap_socket *sock)
-{
-#ifndef NO_OPENSSL
- if (sock->ssl) {
- SSL_shutdown(sock->ssl);
- SSL_free(sock->ssl);
- }
-#endif
- close(sock->fd[0]);
- close(sock->fd[1]);
-}
-
-/* simple line buffering */
-static int buffer_gets(struct imap_buffer *b, char **s)
-{
- int n;
- int start = b->offset;
-
- *s = b->buf + start;
-
- for (;;) {
- /* make sure we have enough data to read the \r\n sequence */
- if (b->offset + 1 >= b->bytes) {
- if (start) {
- /* shift down used bytes */
- *s = b->buf;
-
- assert(start <= b->bytes);
- n = b->bytes - start;
-
- if (n)
- memmove(b->buf, b->buf + start, n);
- b->offset -= start;
- b->bytes = n;
- start = 0;
- }
-
- n = socket_read(&b->sock, b->buf + b->bytes,
- sizeof(b->buf) - b->bytes);
-
- if (n <= 0)
- return -1;
-
- b->bytes += n;
- }
-
- if (b->buf[b->offset] == '\r') {
- assert(b->offset + 1 < b->bytes);
- if (b->buf[b->offset + 1] == '\n') {
- b->buf[b->offset] = 0; /* terminate the string */
- b->offset += 2; /* next line */
- if (Verbose)
- puts(*s);
- return 0;
- }
- }
-
- b->offset++;
- }
- /* not reached */
-}
-
-static void imap_info(const char *msg, ...)
-{
- va_list va;
-
- if (!Quiet) {
- va_start(va, msg);
- vprintf(msg, va);
- va_end(va);
- fflush(stdout);
- }
-}
-
-static void imap_warn(const char *msg, ...)
-{
- va_list va;
-
- if (Quiet < 2) {
- va_start(va, msg);
- vfprintf(stderr, msg, va);
- va_end(va);
- }
-}
-
-static char *next_arg(char **s)
-{
- char *ret;
-
- if (!s || !*s)
- return NULL;
- while (isspace((unsigned char) **s))
- (*s)++;
- if (!**s) {
- *s = NULL;
- return NULL;
- }
- if (**s == '"') {
- ++*s;
- ret = *s;
- *s = strchr(*s, '"');
- } else {
- ret = *s;
- while (**s && !isspace((unsigned char) **s))
- (*s)++;
- }
- if (*s) {
- if (**s)
- *(*s)++ = 0;
- if (!**s)
- *s = NULL;
- }
- return ret;
-}
-
-static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
-{
- int ret;
- va_list va;
-
- va_start(va, fmt);
- if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
- die("Fatal: buffer too small. Please report a bug.");
- va_end(va);
- return ret;
-}
-
-static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
- struct imap_cmd_cb *cb,
- const char *fmt, va_list ap)
-{
- struct imap *imap = ctx->imap;
- struct imap_cmd *cmd;
- int n, bufl;
- char buf[1024];
-
- cmd = xmalloc(sizeof(struct imap_cmd));
- nfvasprintf(&cmd->cmd, fmt, ap);
- cmd->tag = ++imap->nexttag;
-
- if (cb)
- cmd->cb = *cb;
- else
- memset(&cmd->cb, 0, sizeof(cmd->cb));
-
- while (imap->literal_pending)
- get_cmd_result(ctx, NULL);
-
- if (!cmd->cb.data)
- bufl = nfsnprintf(buf, sizeof(buf), "%d %s\r\n", cmd->tag, cmd->cmd);
- else
- bufl = nfsnprintf(buf, sizeof(buf), "%d %s{%d%s}\r\n",
- cmd->tag, cmd->cmd, cmd->cb.dlen,
- CAP(LITERALPLUS) ? "+" : "");
-
- if (Verbose) {
- if (imap->num_in_progress)
- printf("(%d in progress) ", imap->num_in_progress);
- if (memcmp(cmd->cmd, "LOGIN", 5))
- printf(">>> %s", buf);
- else
- printf(">>> %d LOGIN <user> <pass>\n", cmd->tag);
- }
- if (socket_write(&imap->buf.sock, buf, bufl) != bufl) {
- free(cmd->cmd);
- free(cmd);
- if (cb)
- free(cb->data);
- return NULL;
- }
- if (cmd->cb.data) {
- if (CAP(LITERALPLUS)) {
- n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen);
- free(cmd->cb.data);
- if (n != cmd->cb.dlen ||
- socket_write(&imap->buf.sock, "\r\n", 2) != 2) {
- free(cmd->cmd);
- free(cmd);
- return NULL;
- }
- cmd->cb.data = NULL;
- } else
- imap->literal_pending = 1;
- } else if (cmd->cb.cont)
- imap->literal_pending = 1;
- cmd->next = NULL;
- *imap->in_progress_append = cmd;
- imap->in_progress_append = &cmd->next;
- imap->num_in_progress++;
- return cmd;
-}
-
-__attribute__((format (printf, 3, 4)))
-static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
- struct imap_cmd_cb *cb,
- const char *fmt, ...)
-{
- struct imap_cmd *ret;
- va_list ap;
-
- va_start(ap, fmt);
- ret = v_issue_imap_cmd(ctx, cb, fmt, ap);
- va_end(ap);
- return ret;
-}
-
-__attribute__((format (printf, 3, 4)))
-static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
- const char *fmt, ...)
-{
- va_list ap;
- struct imap_cmd *cmdp;
-
- va_start(ap, fmt);
- cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
- va_end(ap);
- if (!cmdp)
- return RESP_BAD;
-
- return get_cmd_result(ctx, cmdp);
-}
-
-__attribute__((format (printf, 3, 4)))
-static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
- const char *fmt, ...)
-{
- va_list ap;
- struct imap_cmd *cmdp;
-
- va_start(ap, fmt);
- cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
- va_end(ap);
- if (!cmdp)
- return DRV_STORE_BAD;
-
- switch (get_cmd_result(ctx, cmdp)) {
- case RESP_BAD: return DRV_STORE_BAD;
- case RESP_NO: return DRV_MSG_BAD;
- default: return DRV_OK;
- }
-}
-
-static int skip_imap_list_l(char **sp, int level)
-{
- char *s = *sp;
-
- for (;;) {
- while (isspace((unsigned char)*s))
- s++;
- if (level && *s == ')') {
- s++;
- break;
- }
- if (*s == '(') {
- /* sublist */
- s++;
- if (skip_imap_list_l(&s, level + 1))
- goto bail;
- } else if (*s == '"') {
- /* quoted string */
- s++;
- for (; *s != '"'; s++)
- if (!*s)
- goto bail;
- s++;
- } else {
- /* atom */
- for (; *s && !isspace((unsigned char)*s); s++)
- if (level && *s == ')')
- break;
- }
-
- if (!level)
- break;
- if (!*s)
- goto bail;
- }
- *sp = s;
- return 0;
-
-bail:
- return -1;
-}
-
-static void skip_list(char **sp)
-{
- skip_imap_list_l(sp, 0);
-}
-
-static void parse_capability(struct imap *imap, char *cmd)
-{
- char *arg;
- unsigned i;
-
- imap->caps = 0x80000000;
- while ((arg = next_arg(&cmd)))
- for (i = 0; i < ARRAY_SIZE(cap_list); i++)
- if (!strcmp(cap_list[i], arg))
- imap->caps |= 1 << i;
- imap->rcaps = imap->caps;
-}
-
-static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
- char *s)
-{
- struct imap *imap = ctx->imap;
- char *arg, *p;
-
- if (*s != '[')
- return RESP_OK; /* no response code */
- s++;
- if (!(p = strchr(s, ']'))) {
- fprintf(stderr, "IMAP error: malformed response code\n");
- return RESP_BAD;
- }
- *p++ = 0;
- arg = next_arg(&s);
- if (!strcmp("UIDVALIDITY", arg)) {
- if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
- fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
- return RESP_BAD;
- }
- } else if (!strcmp("UIDNEXT", arg)) {
- if (!(arg = next_arg(&s)) || !(imap->uidnext = atoi(arg))) {
- fprintf(stderr, "IMAP error: malformed NEXTUID status\n");
- return RESP_BAD;
- }
- } else if (!strcmp("CAPABILITY", arg)) {
- parse_capability(imap, s);
- } else if (!strcmp("ALERT", arg)) {
- /* RFC2060 says that these messages MUST be displayed
- * to the user
- */
- for (; isspace((unsigned char)*p); p++);
- fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
- } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
- if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg)) ||
- !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
- fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
- return RESP_BAD;
- }
- }
- return RESP_OK;
-}
-
-static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
-{
- struct imap *imap = ctx->imap;
- struct imap_cmd *cmdp, **pcmdp, *ncmdp;
- char *cmd, *arg, *arg1, *p;
- int n, resp, resp2, tag;
-
- for (;;) {
- if (buffer_gets(&imap->buf, &cmd))
- return RESP_BAD;
-
- arg = next_arg(&cmd);
- if (*arg == '*') {
- arg = next_arg(&cmd);
- if (!arg) {
- fprintf(stderr, "IMAP error: unable to parse untagged response\n");
- return RESP_BAD;
- }
-
- if (!strcmp("NAMESPACE", arg)) {
- /* rfc2342 NAMESPACE response. */
- skip_list(&cmd); /* Personal mailboxes */
- skip_list(&cmd); /* Others' mailboxes */
- skip_list(&cmd); /* Shared mailboxes */
- } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
- !strcmp("NO", arg) || !strcmp("BYE", arg)) {
- if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
- return resp;
- } else if (!strcmp("CAPABILITY", arg)) {
- parse_capability(imap, cmd);
- } else if ((arg1 = next_arg(&cmd))) {
- ; /*
- * Unhandled response-data with at least two words.
- * Ignore it.
- *
- * NEEDSWORK: Previously this case handled '<num> EXISTS'
- * and '<num> RECENT' but as a probably-unintended side
- * effect it ignores other unrecognized two-word
- * responses. imap-send doesn't ever try to read
- * messages or mailboxes these days, so consider
- * eliminating this case.
- */
- } else {
- fprintf(stderr, "IMAP error: unable to parse untagged response\n");
- return RESP_BAD;
- }
- } else if (!imap->in_progress) {
- fprintf(stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "");
- return RESP_BAD;
- } else if (*arg == '+') {
- /* This can happen only with the last command underway, as
- it enforces a round-trip. */
- cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
- offsetof(struct imap_cmd, next));
- if (cmdp->cb.data) {
- n = socket_write(&imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen);
- free(cmdp->cb.data);
- cmdp->cb.data = NULL;
- if (n != (int)cmdp->cb.dlen)
- return RESP_BAD;
- } else if (cmdp->cb.cont) {
- if (cmdp->cb.cont(ctx, cmdp, cmd))
- return RESP_BAD;
- } else {
- fprintf(stderr, "IMAP error: unexpected command continuation request\n");
- return RESP_BAD;
- }
- if (socket_write(&imap->buf.sock, "\r\n", 2) != 2)
- return RESP_BAD;
- if (!cmdp->cb.cont)
- imap->literal_pending = 0;
- if (!tcmd)
- return DRV_OK;
- } else {
- tag = atoi(arg);
- for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
- if (cmdp->tag == tag)
- goto gottag;
- fprintf(stderr, "IMAP error: unexpected tag %s\n", arg);
- return RESP_BAD;
- gottag:
- if (!(*pcmdp = cmdp->next))
- imap->in_progress_append = pcmdp;
- imap->num_in_progress--;
- if (cmdp->cb.cont || cmdp->cb.data)
- imap->literal_pending = 0;
- arg = next_arg(&cmd);
- if (!strcmp("OK", arg))
- resp = DRV_OK;
- else {
- if (!strcmp("NO", arg)) {
- if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp(cmd, "[TRYCREATE]", 11))) { /* SELECT, APPEND or UID COPY */
- p = strchr(cmdp->cmd, '"');
- if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", (int)(strchr(p + 1, '"') - p + 1), p)) {
- resp = RESP_BAD;
- goto normal;
- }
- /* not waiting here violates the spec, but a server that does not
- grok this nonetheless violates it too. */
- cmdp->cb.create = 0;
- if (!(ncmdp = issue_imap_cmd(ctx, &cmdp->cb, "%s", cmdp->cmd))) {
- resp = RESP_BAD;
- goto normal;
- }
- free(cmdp->cmd);
- free(cmdp);
- if (!tcmd)
- return 0; /* ignored */
- if (cmdp == tcmd)
- tcmd = ncmdp;
- continue;
- }
- resp = RESP_NO;
- } else /*if (!strcmp("BAD", arg))*/
- resp = RESP_BAD;
- fprintf(stderr, "IMAP command '%s' returned response (%s) - %s\n",
- memcmp(cmdp->cmd, "LOGIN", 5) ?
- cmdp->cmd : "LOGIN <user> <pass>",
- arg, cmd ? cmd : "");
- }
- if ((resp2 = parse_response_code(ctx, &cmdp->cb, cmd)) > resp)
- resp = resp2;
- normal:
- if (cmdp->cb.done)
- cmdp->cb.done(ctx, cmdp, resp);
- free(cmdp->cb.data);
- free(cmdp->cmd);
- free(cmdp);
- if (!tcmd || tcmd == cmdp)
- return resp;
- }
- }
- /* not reached */
-}
-
-static void imap_close_server(struct imap_store *ictx)
-{
- struct imap *imap = ictx->imap;
-
- if (imap->buf.sock.fd[0] != -1) {
- imap_exec(ictx, NULL, "LOGOUT");
- socket_shutdown(&imap->buf.sock);
- }
- free(imap);
-}
-
-static void imap_close_store(struct imap_store *ctx)
-{
- imap_close_server(ctx);
- free(ctx);
-}
-
-#ifndef NO_OPENSSL
-
-/*
- * hexchar() and cram() functions are based on the code from the isync
- * project (http://isync.sf.net/).
- */
-static char hexchar(unsigned int b)
-{
- return b < 10 ? '0' + b : 'a' + (b - 10);
-}
-
-#define ENCODED_SIZE(n) (4*((n+2)/3))
-static char *cram(const char *challenge_64, const char *user, const char *pass)
-{
- int i, resp_len, encoded_len, decoded_len;
- HMAC_CTX hmac;
- unsigned char hash[16];
- char hex[33];
- char *response, *response_64, *challenge;
-
- /*
- * length of challenge_64 (i.e. base-64 encoded string) is a good
- * enough upper bound for challenge (decoded result).
- */
- encoded_len = strlen(challenge_64);
- challenge = xmalloc(encoded_len);
- decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
- (unsigned char *)challenge_64, encoded_len);
- if (decoded_len < 0)
- die("invalid challenge %s", challenge_64);
- HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
- HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
- HMAC_Final(&hmac, hash, NULL);
- HMAC_CTX_cleanup(&hmac);
-
- hex[32] = 0;
- for (i = 0; i < 16; i++) {
- hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
- hex[2 * i + 1] = hexchar(hash[i] & 0xf);
- }
-
- /* response: "<user> <digest in hex>" */
- resp_len = strlen(user) + 1 + strlen(hex) + 1;
- response = xmalloc(resp_len);
- sprintf(response, "%s %s", user, hex);
-
- response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
- encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
- (unsigned char *)response, resp_len);
- if (encoded_len < 0)
- die("EVP_EncodeBlock error");
- response_64[encoded_len] = '\0';
- return (char *)response_64;
-}
-
-#else
-
-static char *cram(const char *challenge_64, const char *user, const char *pass)
-{
- die("If you want to use CRAM-MD5 authenticate method, "
- "you have to build git-imap-send with OpenSSL library.");
-}
-
-#endif
-
-static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt)
-{
- int ret;
- char *response;
-
- response = cram(prompt, server.user, server.pass);
-
- ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
- if (ret != strlen(response))
- return error("IMAP error: sending response failed");
-
- free(response);
-
- return 0;
-}
-
-static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
-{
- struct credential cred = CREDENTIAL_INIT;
- struct imap_store *ctx;
- struct imap *imap;
- char *arg, *rsp;
- int s = -1, preauth;
-
- ctx = xcalloc(1, sizeof(*ctx));
-
- ctx->imap = imap = xcalloc(sizeof(*imap), 1);
- imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
- imap->in_progress_append = &imap->in_progress;
-
- /* open connection to IMAP server */
-
- if (srvc->tunnel) {
- const char *argv[] = { srvc->tunnel, NULL };
- struct child_process tunnel = {NULL};
-
- imap_info("Starting tunnel '%s'... ", srvc->tunnel);
-
- tunnel.argv = argv;
- tunnel.use_shell = 1;
- tunnel.in = -1;
- tunnel.out = -1;
- if (start_command(&tunnel))
- die("cannot start proxy %s", argv[0]);
-
- imap->buf.sock.fd[0] = tunnel.out;
- imap->buf.sock.fd[1] = tunnel.in;
-
- imap_info("ok\n");
- } else {
-#ifndef NO_IPV6
- struct addrinfo hints, *ai0, *ai;
- int gai;
- char portstr[6];
-
- snprintf(portstr, sizeof(portstr), "%d", srvc->port);
-
- memset(&hints, 0, sizeof(hints));
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
-
- imap_info("Resolving %s... ", srvc->host);
- gai = getaddrinfo(srvc->host, portstr, &hints, &ai);
- if (gai) {
- fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai));
- goto bail;
- }
- imap_info("ok\n");
-
- for (ai0 = ai; ai; ai = ai->ai_next) {
- char addr[NI_MAXHOST];
-
- s = socket(ai->ai_family, ai->ai_socktype,
- ai->ai_protocol);
- if (s < 0)
- continue;
-
- getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
- sizeof(addr), NULL, 0, NI_NUMERICHOST);
- imap_info("Connecting to [%s]:%s... ", addr, portstr);
-
- if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
- close(s);
- s = -1;
- perror("connect");
- continue;
- }
-
- break;
- }
- freeaddrinfo(ai0);
-#else /* NO_IPV6 */
- struct hostent *he;
- struct sockaddr_in addr;
-
- memset(&addr, 0, sizeof(addr));
- addr.sin_port = htons(srvc->port);
- addr.sin_family = AF_INET;
-
- imap_info("Resolving %s... ", srvc->host);
- he = gethostbyname(srvc->host);
- if (!he) {
- perror("gethostbyname");
- goto bail;
- }
- imap_info("ok\n");
-
- addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
-
- s = socket(PF_INET, SOCK_STREAM, 0);
-
- imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
- if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
- close(s);
- s = -1;
- perror("connect");
- }
-#endif
- if (s < 0) {
- fputs("Error: unable to connect to server.\n", stderr);
- goto bail;
- }
-
- imap->buf.sock.fd[0] = s;
- imap->buf.sock.fd[1] = dup(s);
-
- if (srvc->use_ssl &&
- ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
- close(s);
- goto bail;
- }
- imap_info("ok\n");
- }
-
- /* read the greeting string */
- if (buffer_gets(&imap->buf, &rsp)) {
- fprintf(stderr, "IMAP error: no greeting response\n");
- goto bail;
- }
- arg = next_arg(&rsp);
- if (!arg || *arg != '*' || (arg = next_arg(&rsp)) == NULL) {
- fprintf(stderr, "IMAP error: invalid greeting response\n");
- goto bail;
- }
- preauth = 0;
- if (!strcmp("PREAUTH", arg))
- preauth = 1;
- else if (strcmp("OK", arg) != 0) {
- fprintf(stderr, "IMAP error: unknown greeting response\n");
- goto bail;
- }
- parse_response_code(ctx, NULL, rsp);
- if (!imap->caps && imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
- goto bail;
-
- if (!preauth) {
-#ifndef NO_OPENSSL
- if (!srvc->use_ssl && CAP(STARTTLS)) {
- if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
- goto bail;
- if (ssl_socket_connect(&imap->buf.sock, 1,
- srvc->ssl_verify))
- goto bail;
- /* capabilities may have changed, so get the new capabilities */
- if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
- goto bail;
- }
-#endif
- imap_info("Logging in...\n");
- if (!srvc->user || !srvc->pass) {
- cred.protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap");
- cred.host = xstrdup(srvc->host);
-
- if (srvc->user)
- cred.username = xstrdup(srvc->user);
- if (srvc->pass)
- cred.password = xstrdup(srvc->pass);
-
- credential_fill(&cred);
-
- if (!srvc->user)
- srvc->user = xstrdup(cred.username);
- if (!srvc->pass)
- srvc->pass = xstrdup(cred.password);
- }
-
- if (CAP(NOLOGIN)) {
- fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
- goto bail;
- }
-
- 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);
- 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");
- goto bail;
- }
- } else {
- fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
- goto bail;
- }
- } else {
- if (!imap->buf.sock.ssl)
- imap_warn("*** IMAP Warning *** Password is being "
- "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;
- }
- }
- } /* !preauth */
-
- if (cred.username)
- credential_approve(&cred);
- credential_clear(&cred);
-
- ctx->prefix = "";
- return ctx;
-
-bail:
- if (cred.username)
- credential_reject(&cred);
- credential_clear(&cred);
-
- imap_close_store(ctx);
- return NULL;
-}
-
-/*
- * Insert CR characters as necessary in *msg to ensure that every LF
- * character in *msg is preceded by a CR.
- */
-static void lf_to_crlf(struct strbuf *msg)
-{
- char *new;
- size_t i, j;
- char lastc;
-
- /* First pass: tally, in j, the size of the new string: */
- for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
- if (msg->buf[i] == '\n' && lastc != '\r')
- j++; /* a CR will need to be added here */
- lastc = msg->buf[i];
- j++;
- }
-
- new = xmalloc(j + 1);
-
- /*
- * Second pass: write the new string. Note that this loop is
- * otherwise identical to the first pass.
- */
- for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
- if (msg->buf[i] == '\n' && lastc != '\r')
- new[j++] = '\r';
- lastc = new[j++] = msg->buf[i];
- }
- strbuf_attach(msg, new, j, j + 1);
-}
-
-/*
- * Store msg to IMAP. Also detach and free the data from msg->data,
- * leaving msg->data empty.
- */
-static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
-{
- struct imap *imap = ctx->imap;
- struct imap_cmd_cb cb;
- const char *prefix, *box;
- int ret;
-
- lf_to_crlf(msg);
- memset(&cb, 0, sizeof(cb));
-
- cb.dlen = msg->len;
- cb.data = strbuf_detach(msg, NULL);
-
- box = ctx->name;
- prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
- cb.create = 0;
- ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
- imap->caps = imap->rcaps;
- if (ret != DRV_OK)
- return ret;
-
- return DRV_OK;
-}
-
static void wrap_in_html(struct strbuf *msg)
{
struct strbuf buf = STRBUF_INIT;
@@ -1324,6 +152,28 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
return 1;
}
+static curl_socket_t opensocket(void *clientp, curlsocktype purpose,
+ struct curl_sockaddr *address)
+{
+ curl_socket_t sockfd;
+ (void)purpose;
+ (void)address;
+ sockfd = *(curl_socket_t *)clientp;
+ /* the actual externally set socket is passed in via the OPENSOCKETDATA
+ option */
+ return sockfd;
+}
+
+static int sockopt_callback(void *clientp, curl_socket_t curlfd,
+ curlsocktype purpose)
+{
+ (void)clientp;
+ (void)curlfd;
+ (void)purpose;
+ /* This return code was added in libcurl 7.21.5 */
+ return CURL_SOCKOPT_ALREADY_CONNECTED;
+}
+
static char *imap_folder;
static int git_imap_config(const char *key, const char *val, void *cb)
@@ -1368,12 +218,14 @@ static int git_imap_config(const char *key, const char *val, void *cb)
int main(int argc, char **argv)
{
struct strbuf all_msgs = STRBUF_INIT;
- struct strbuf msg = STRBUF_INIT;
- struct imap_store *ctx = NULL;
+ struct buffer msg = { STRBUF_INIT, 0 };
int ofs = 0;
- int r;
int total, n = 0;
int nongit_ok;
+ char path[8192];
+ int pathlen;
+ CURL *curl;
+ CURLcode r = CURLE_OK;
git_extract_argv0_path(argv[0]);
@@ -1417,31 +269,89 @@ int main(int argc, char **argv)
return 1;
}
- /* write it to the imap server */
- ctx = imap_open_store(&server);
- if (!ctx) {
- fprintf(stderr, "failed to open store\n");
+ curl_global_init(CURL_GLOBAL_ALL);
+ curl = curl_easy_init();
+
+ if (!curl) {
+ fprintf(stderr, "failed to init curl\n");
return 1;
}
+ curl_easy_setopt(curl, CURLOPT_USERNAME, server.user);
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, server.pass);
+
+ if (ends_with(server.host, "/"))
+ pathlen = snprintf (path, sizeof(path), "%s%s", server.host, imap_folder);
+ else
+ pathlen = snprintf (path, sizeof(path), "%s/%s", server.host, imap_folder);
+
+ if (pathlen < 0)
+ die("Fatal: Out of memory");
+ if (pathlen >= sizeof(path))
+ die("imap URL overflow!");
+
+ curl_easy_setopt(curl, CURLOPT_URL, path);
+ curl_easy_setopt(curl, CURLOPT_PORT, server.port);
+ curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, server.auth_method);
+
+ if (server.tunnel) {
+ const char *argv[] = { server.tunnel, NULL };
+ struct child_process tunnel = {NULL};
+
+ fprintf(stderr, "Starting tunnel '%s'... ", server.tunnel);
+
+ tunnel.argv = argv;
+ tunnel.use_shell = 1;
+ tunnel.in = -1;
+ tunnel.out = -1;
+ if (start_command(&tunnel))
+ die("cannot start proxy %s", argv[0]);
+
+ curl_socket_t sockfd = tunnel.out; // what about tunnel.in ?
+
+ /* call this function to get a socket */
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket);
+ curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &sockfd);
+
+ /* call this function to set options for the socket */
+ curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
+ }
+
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, server.ssl_verify);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, server.ssl_verify);
+
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &msg);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
+
+ /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */
+
fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
- ctx->name = imap_folder;
+
while (1) {
unsigned percent = n * 100 / total;
fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
- if (!split_msg(&all_msgs, &msg, &ofs))
+ int prev_len = msg.buf.len;
+ if (!split_msg(&all_msgs, &msg.buf, &ofs))
break;
if (server.use_html)
- wrap_in_html(&msg);
- r = imap_store_msg(ctx, &msg);
- if (r != DRV_OK)
+ wrap_in_html(&msg.buf);
+
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)(msg.buf.len-prev_len));
+
+ r = curl_easy_perform(curl);
+
+ if(r != CURLE_OK) {
+ fprintf(stderr, "curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(r));
break;
+ }
n++;
}
fprintf(stderr, "\n");
- imap_close_store(ctx);
-
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
return 0;
}
next reply other threads:[~2014-08-12 21:58 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-08-12 21:50 Bernhard Reiter [this message]
2014-08-13 1:59 ` [PATCH/RFC] git-imap-send: use libcurl for implementation Jonathan Nieder
2014-08-17 8:30 ` Jeff King
2014-08-17 12:56 ` Bernhard Reiter
2014-08-17 18:42 ` Jeff King
2014-08-19 11:14 ` Bernhard Reiter
2014-08-19 17:13 ` Junio C Hamano
-- strict thread matches above, loose matches on Subject: below --
2014-08-14 21:46 Bernhard Reiter
2014-08-19 17:51 ` Junio C Hamano
2014-08-25 20:11 ` Bernhard Reiter
2014-08-25 21:08 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=53EA8C3E.1080500@raz.or.at \
--to=ockham@raz.or.at \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.