summaryrefslogtreecommitdiff
path: root/imap-send.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap-send.c')
-rw-r--r--imap-send.c1273
1 files changed, 644 insertions, 629 deletions
diff --git a/imap-send.c b/imap-send.c
index 3847fd151d..37ac4aa86a 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -23,90 +23,50 @@
*/
#include "cache.h"
+#include "credential.h"
#include "exec_cmd.h"
+#include "run-command.h"
+#include "parse-options.h"
#ifdef NO_OPENSSL
typedef void *SSL;
#endif
+#ifdef USE_CURL_FOR_IMAP_SEND
+#include "http.h"
+#endif
-struct store_conf {
- char *name;
- const char *path; /* should this be here? its interpretation is driver-specific */
- char *map_inbox;
- char *trash;
- unsigned max_size; /* off_t is overkill */
- unsigned trash_remote_new:1, trash_only_new:1;
-};
-
-struct string_list {
- struct string_list *next;
- char string[1];
-};
-
-struct channel_conf {
- struct channel_conf *next;
- char *name;
- struct store_conf *master, *slave;
- char *master_name, *slave_name;
- char *sync_state;
- struct string_list *patterns;
- int mops, sops;
- unsigned max_messages; /* for slave only */
-};
-
-struct group_conf {
- struct group_conf *next;
- char *name;
- struct string_list *channels;
-};
-
-/* For message->status */
-#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
-#define M_DEAD (1<<1) /* expunged */
-#define M_FLAGS (1<<2) /* flags fetched */
+#if defined(USE_CURL_FOR_IMAP_SEND) && defined(NO_OPENSSL)
+/* only available option */
+#define USE_CURL_DEFAULT 1
+#else
+/* strictly opt in */
+#define USE_CURL_DEFAULT 0
+#endif
-struct message {
- struct message *next;
- /* struct string_list *keywords; */
- size_t size; /* zero implies "not fetched" */
- int uid;
- unsigned char flags, status;
-};
+static int verbosity;
+static int use_curl = USE_CURL_DEFAULT;
-struct store {
- struct store_conf *conf; /* foreign */
+static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <mbox>", NULL };
- /* currently open mailbox */
- const char *name; /* foreign! maybe preset? */
- char *path; /* own */
- struct message *msgs; /* own */
- int uidvalidity;
- unsigned char opts; /* maybe preset? */
- /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
- int count; /* # of messages */
- int recent; /* # of recent messages - don't trust this beyond the initial read */
-};
-
-struct msg_data {
- char *data;
- int len;
- unsigned char flags;
- unsigned int crlf:1;
+static struct option imap_send_options[] = {
+ OPT__VERBOSITY(&verbosity),
+ OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
+ OPT_END()
};
+#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 **);
-static void free_generic_messages(struct message *);
-
+__attribute__((format (printf, 3, 4)))
static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
static int nfvasprintf(char **strp, const char *fmt, va_list ap)
@@ -123,38 +83,36 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
return len;
}
-static void arc4_init(void);
-static unsigned char arc4_getbyte(void);
-
struct imap_server_conf {
char *name;
char *tunnel;
char *host;
int port;
+ char *folder;
char *user;
char *pass;
int use_ssl;
int ssl_verify;
int use_html;
+ char *auth_method;
};
-struct imap_store_conf {
- struct store_conf gen;
- struct imap_server_conf *server;
- unsigned use_namespace:1;
-};
-
-#define NIL (void *)0x1
-#define LIST (void *)0x2
-
-struct imap_list {
- struct imap_list *next, *child;
- char *val;
- int len;
+static struct imap_server_conf server = {
+ NULL, /* name */
+ NULL, /* tunnel */
+ NULL, /* host */
+ 0, /* port */
+ NULL, /* folder */
+ NULL, /* user */
+ NULL, /* pass */
+ 0, /* use_ssl */
+ 1, /* ssl_verify */
+ 0, /* use_html */
+ NULL, /* auth_method */
};
struct imap_socket {
- int fd;
+ int fd[2];
SSL *ssl;
};
@@ -169,7 +127,6 @@ struct imap_cmd;
struct imap {
int uidnext; /* from SELECT responses */
- struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
unsigned caps, rcaps; /* CAPABILITY results */
/* command queue */
int nexttag, num_in_progress, literal_pending;
@@ -178,11 +135,11 @@ struct imap {
};
struct imap_store {
- struct store gen;
+ /* currently open mailbox */
+ const char *name; /* foreign! maybe preset? */
int uidvalidity;
struct imap *imap;
const char *prefix;
- unsigned /*currentnc:1,*/ trashnc:1;
};
struct imap_cmd_cb {
@@ -192,7 +149,6 @@ struct imap_cmd_cb {
char *data;
int dlen;
int uid;
- unsigned create:1, trycreate:1;
};
struct imap_cmd {
@@ -210,6 +166,7 @@ enum CAPABILITY {
LITERALPLUS,
NAMESPACE,
STARTTLS,
+ AUTH_CRAM_MD5
};
static const char *cap_list[] = {
@@ -218,6 +175,7 @@ static const char *cap_list[] = {
"LITERAL+",
"NAMESPACE",
"STARTTLS",
+ "AUTH=CRAM-MD5",
};
#define RESP_OK 0
@@ -227,14 +185,6 @@ static const char *cap_list[] = {
static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
-static const char *Flags[] = {
- "Draft",
- "Flagged",
- "Answered",
- "Seen",
- "Deleted",
-};
-
#ifndef NO_OPENSSL
static void ssl_socket_perror(const char *func)
{
@@ -267,15 +217,72 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
}
}
+#ifdef NO_OPENSSL
static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
{
-#ifdef NO_OPENSSL
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();
@@ -304,20 +311,44 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
ssl_socket_perror("SSL_new");
return -1;
}
- if (!SSL_set_fd(sock->ssl, sock->fd)) {
- ssl_socket_perror("SSL_set_fd");
+ 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
}
+#endif
static int socket_read(struct imap_socket *sock, char *buf, int len)
{
@@ -327,11 +358,12 @@ static int socket_read(struct imap_socket *sock, char *buf, int len)
n = SSL_read(sock->ssl, buf, len);
else
#endif
- n = xread(sock->fd, buf, len);
+ n = xread(sock->fd[0], buf, len);
if (n <= 0) {
socket_perror("read", sock, n);
- close(sock->fd);
- sock->fd = -1;
+ close(sock->fd[0]);
+ close(sock->fd[1]);
+ sock->fd[0] = sock->fd[1] = -1;
}
return n;
}
@@ -344,11 +376,12 @@ static int socket_write(struct imap_socket *sock, const char *buf, int len)
n = SSL_write(sock->ssl, buf, len);
else
#endif
- n = write_in_full(sock->fd, buf, len);
+ n = write_in_full(sock->fd[1], buf, len);
if (n != len) {
socket_perror("write", sock, n);
- close(sock->fd);
- sock->fd = -1;
+ close(sock->fd[0]);
+ close(sock->fd[1]);
+ sock->fd[0] = sock->fd[1] = -1;
}
return n;
}
@@ -361,7 +394,8 @@ static void socket_shutdown(struct imap_socket *sock)
SSL_free(sock->ssl);
}
#endif
- close(sock->fd);
+ close(sock->fd[0]);
+ close(sock->fd[1]);
}
/* simple line buffering */
@@ -403,7 +437,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 (Verbose)
+ if (0 < verbosity)
puts(*s);
return 0;
}
@@ -418,7 +452,7 @@ static void imap_info(const char *msg, ...)
{
va_list va;
- if (!Quiet) {
+ if (0 <= verbosity) {
va_start(va, msg);
vprintf(msg, va);
va_end(va);
@@ -430,7 +464,7 @@ static void imap_warn(const char *msg, ...)
{
va_list va;
- if (Quiet < 2) {
+ if (-2 < verbosity) {
va_start(va, msg);
vfprintf(stderr, msg, va);
va_end(va);
@@ -467,16 +501,6 @@ static char *next_arg(char **s)
return ret;
}
-static void free_generic_messages(struct message *msgs)
-{
- struct message *tmsg;
-
- for (; msgs; msgs = tmsg) {
- tmsg = msgs->next;
- free(msgs);
- }
-}
-
static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
{
int ret;
@@ -489,55 +513,9 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
return ret;
}
-static struct {
- unsigned char i, j, s[256];
-} rs;
-
-static void arc4_init(void)
-{
- int i, fd;
- unsigned char j, si, dat[128];
-
- if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) {
- fprintf(stderr, "Fatal: no random number source available.\n");
- exit(3);
- }
- if (read_in_full(fd, dat, 128) != 128) {
- fprintf(stderr, "Fatal: cannot read random number source.\n");
- exit(3);
- }
- close(fd);
-
- for (i = 0; i < 256; i++)
- rs.s[i] = i;
- for (i = j = 0; i < 256; i++) {
- si = rs.s[i];
- j += si + dat[i & 127];
- rs.s[i] = rs.s[j];
- rs.s[j] = si;
- }
- rs.i = rs.j = 0;
-
- for (i = 0; i < 256; i++)
- arc4_getbyte();
-}
-
-static unsigned char arc4_getbyte(void)
-{
- unsigned char si, sj;
-
- rs.i++;
- si = rs.s[rs.i];
- rs.j += si;
- sj = rs.s[rs.j];
- rs.s[rs.i] = sj;
- rs.s[rs.j] = si;
- return rs.s[(si + sj) & 0xff];
-}
-
-static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
- struct imap_cmd_cb *cb,
- const char *fmt, va_list ap)
+static struct imap_cmd *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;
@@ -556,13 +534,17 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
while (imap->literal_pending)
get_cmd_result(ctx, NULL);
- bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ?
- "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n",
- cmd->tag, cmd->cmd, cmd->cb.dlen);
- if (Verbose) {
+ 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 (0 < verbosity) {
if (imap->num_in_progress)
printf("(%d in progress) ", imap->num_in_progress);
- if (memcmp(cmd->cmd, "LOGIN", 5))
+ if (!starts_with(cmd->cmd, "LOGIN"))
printf(">>> %s", buf);
else
printf(">>> %d LOGIN <user> <pass>\n", cmd->tag);
@@ -596,19 +578,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
return cmd;
}
-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, ...)
{
@@ -616,7 +586,7 @@ static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
struct imap_cmd *cmdp;
va_start(ap, fmt);
- cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
+ cmdp = issue_imap_cmd(ctx, cb, fmt, ap);
va_end(ap);
if (!cmdp)
return RESP_BAD;
@@ -624,6 +594,7 @@ static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
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, ...)
{
@@ -631,7 +602,7 @@ static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
struct imap_cmd *cmdp;
va_start(ap, fmt);
- cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap);
+ cmdp = issue_imap_cmd(ctx, cb, fmt, ap);
va_end(ap);
if (!cmdp)
return DRV_STORE_BAD;
@@ -643,35 +614,9 @@ static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
}
}
-static int is_atom(struct imap_list *list)
-{
- return list && list->val && list->val != NIL && list->val != LIST;
-}
-
-static int is_list(struct imap_list *list)
-{
- return list && list->val == LIST;
-}
-
-static void free_list(struct imap_list *list)
-{
- struct imap_list *tmp;
-
- for (; list; list = tmp) {
- tmp = list->next;
- if (is_list(list))
- free_list(list->child);
- else if (is_atom(list))
- free(list->val);
- free(list);
- }
-}
-
-static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
+static int skip_imap_list_l(char **sp, int level)
{
- struct imap_list *cur;
- char *s = *sp, *p;
- int n, bytes;
+ char *s = *sp;
for (;;) {
while (isspace((unsigned char)*s))
@@ -680,68 +625,23 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
s++;
break;
}
- *curp = cur = xmalloc(sizeof(*cur));
- curp = &cur->next;
- cur->val = NULL; /* for clean bail */
if (*s == '(') {
/* sublist */
s++;
- cur->val = LIST;
- if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
- goto bail;
- } else if (imap && *s == '{') {
- /* literal */
- bytes = cur->len = strtol(s + 1, &s, 10);
- if (*s != '}')
- goto bail;
-
- s = cur->val = xmalloc(cur->len);
-
- /* dump whats left over in the input buffer */
- n = imap->buf.bytes - imap->buf.offset;
-
- if (n > bytes)
- /* the entire message fit in the buffer */
- n = bytes;
-
- memcpy(s, imap->buf.buf + imap->buf.offset, n);
- s += n;
- bytes -= n;
-
- /* mark that we used part of the buffer */
- imap->buf.offset += n;
-
- /* now read the rest of the message */
- while (bytes > 0) {
- if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
- goto bail;
- s += n;
- bytes -= n;
- }
-
- if (buffer_gets(&imap->buf, &s))
+ if (skip_imap_list_l(&s, level + 1))
goto bail;
} else if (*s == '"') {
/* quoted string */
s++;
- p = s;
for (; *s != '"'; s++)
if (!*s)
goto bail;
- cur->len = s - p;
s++;
- cur->val = xmemdupz(p, cur->len);
} else {
/* atom */
- p = s;
for (; *s && !isspace((unsigned char)*s); s++)
if (level && *s == ')')
break;
- cur->len = s - p;
- if (cur->len == 3 && !memcmp("NIL", p, 3))
- cur->val = NIL;
- else
- cur->val = xmemdupz(p, cur->len);
}
if (!level)
@@ -750,27 +650,15 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
goto bail;
}
*sp = s;
- *curp = NULL;
return 0;
bail:
- *curp = NULL;
return -1;
}
-static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
+static void skip_list(char **sp)
{
- struct imap_list *head;
-
- if (!parse_imap_list_l(imap, sp, &head, 0))
- return head;
- free_list(head);
- return NULL;
-}
-
-static struct imap_list *parse_list(char **sp)
-{
- return parse_imap_list(NULL, sp);
+ skip_imap_list_l(sp, 0);
}
static void parse_capability(struct imap *imap, char *cmd)
@@ -802,7 +690,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
*p++ = 0;
arg = next_arg(&s);
if (!strcmp("UIDVALIDITY", arg)) {
- if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
+ if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
return RESP_BAD;
}
@@ -820,7 +708,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
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->gen.uidvalidity = atoi(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;
@@ -832,8 +720,8 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
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;
+ struct imap_cmd *cmdp, **pcmdp;
+ char *cmd, *arg, *arg1;
int n, resp, resp2, tag;
for (;;) {
@@ -849,20 +737,28 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
}
if (!strcmp("NAMESPACE", arg)) {
- imap->ns_personal = parse_list(&cmd);
- imap->ns_other = parse_list(&cmd);
- imap->ns_shared = parse_list(&cmd);
+ /* 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))
+ } else if (!strcmp("CAPABILITY", arg)) {
parse_capability(imap, cmd);
- else if ((arg1 = next_arg(&cmd))) {
- if (!strcmp("EXISTS", arg1))
- ctx->gen.count = atoi(arg);
- else if (!strcmp("RECENT", arg1))
- ctx->gen.recent = atoi(arg);
+ } 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;
@@ -911,39 +807,17 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
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\"", 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;
- }
+ if (!strcmp("NO", arg))
resp = RESP_NO;
- } else /*if (!strcmp("BAD", arg))*/
+ else /*if (!strcmp("BAD", arg))*/
resp = RESP_BAD;
fprintf(stderr, "IMAP command '%s' returned response (%s) - %s\n",
- memcmp(cmdp->cmd, "LOGIN", 5) ?
+ !starts_with(cmdp->cmd, "LOGIN") ?
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);
@@ -960,62 +834,130 @@ static void imap_close_server(struct imap_store *ictx)
{
struct imap *imap = ictx->imap;
- if (imap->buf.sock.fd != -1) {
+ if (imap->buf.sock.fd[0] != -1) {
imap_exec(ictx, NULL, "LOGOUT");
socket_shutdown(&imap->buf.sock);
}
- free_list(imap->ns_personal);
- free_list(imap->ns_other);
- free_list(imap->ns_shared);
free(imap);
}
-static void imap_close_store(struct store *ctx)
+static void imap_close_store(struct imap_store *ctx)
{
- imap_close_server((struct imap_store *)ctx);
- free_generic_messages(ctx->msgs);
+ imap_close_server(ctx);
free(ctx);
}
-static struct store *imap_open_store(struct imap_server_conf *srvc)
+#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, char *folder)
+{
+ struct credential cred = CREDENTIAL_INIT;
struct imap_store *ctx;
struct imap *imap;
char *arg, *rsp;
- int s = -1, a[2], preauth;
- pid_t pid;
+ int s = -1, preauth;
- ctx = xcalloc(sizeof(*ctx), 1);
+ ctx = xcalloc(1, sizeof(*ctx));
- ctx->imap = imap = xcalloc(sizeof(*imap), 1);
- imap->buf.sock.fd = -1;
+ ctx->imap = imap = xcalloc(1, sizeof(*imap));
+ 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) {
- imap_info("Starting tunnel '%s'... ", srvc->tunnel);
+ struct child_process tunnel = CHILD_PROCESS_INIT;
- if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) {
- perror("socketpair");
- exit(1);
- }
-
- pid = fork();
- if (pid < 0)
- _exit(127);
- if (!pid) {
- if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1)
- _exit(127);
- close(a[0]);
- close(a[1]);
- execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL);
- _exit(127);
- }
+ imap_info("Starting tunnel '%s'... ", srvc->tunnel);
- close(a[0]);
+ argv_array_push(&tunnel.args, srvc->tunnel);
+ tunnel.use_shell = 1;
+ tunnel.in = -1;
+ tunnel.out = -1;
+ if (start_command(&tunnel))
+ die("cannot start proxy %s", srvc->tunnel);
- imap->buf.sock.fd = a[1];
+ imap->buf.sock.fd[0] = tunnel.out;
+ imap->buf.sock.fd[1] = tunnel.in;
imap_info("ok\n");
} else {
@@ -1024,7 +966,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
int gai;
char portstr[6];
- snprintf(portstr, sizeof(portstr), "%hu", srvc->port);
+ snprintf(portstr, sizeof(portstr), "%d", srvc->port);
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
@@ -1092,7 +1034,8 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
goto bail;
}
- imap->buf.sock.fd = s;
+ 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)) {
@@ -1126,266 +1069,218 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
if (!preauth) {
#ifndef NO_OPENSSL
if (!srvc->use_ssl && CAP(STARTTLS)) {
- if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK)
+ 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, 0, "CAPABILITY") != RESP_OK)
+ if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
goto bail;
}
#endif
imap_info("Logging in...\n");
- if (!srvc->user) {
- fprintf(stderr, "Skipping server %s, no user\n", srvc->host);
- goto bail;
- }
- if (!srvc->pass) {
- char prompt[80];
- sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
- arg = getpass(prompt);
- if (!arg) {
- perror("getpass");
- exit(1);
- }
- if (!*arg) {
- fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
- goto bail;
- }
- /*
- * getpass() returns a pointer to a static buffer. make a copy
- * for long term storage.
- */
- srvc->pass = xstrdup(arg);
+ 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 (!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;
+
+ 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);
+
+ /* check the target mailbox exists */
+ ctx->name = folder;
+ switch (imap_exec(ctx, NULL, "EXAMINE \"%s\"", ctx->name)) {
+ case RESP_OK:
+ /* ok */
+ break;
+ case RESP_BAD:
+ fprintf(stderr, "IMAP error: could not check mailbox\n");
+ goto out;
+ case RESP_NO:
+ if (imap_exec(ctx, NULL, "CREATE \"%s\"", ctx->name) == RESP_OK) {
+ imap_info("Created missing mailbox\n");
+ } else {
+ fprintf(stderr, "IMAP error: could not create missing mailbox\n");
+ goto out;
+ }
+ break;
+ }
+
ctx->prefix = "";
- ctx->trashnc = 1;
- return (struct store *)ctx;
+ return ctx;
bail:
- imap_close_store(&ctx->gen);
+ if (cred.username)
+ credential_reject(&cred);
+ credential_clear(&cred);
+
+ out:
+ imap_close_store(ctx);
return NULL;
}
-static int imap_make_flags(int flags, char *buf)
+/*
+ * 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)
{
- const char *s;
- unsigned i, d;
-
- for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
- if (flags & (1 << i)) {
- buf[d++] = ' ';
- buf[d++] = '\\';
- for (s = Flags[i]; *s; s++)
- buf[d++] = *s;
- }
- buf[0] = '(';
- buf[d++] = ')';
- return d;
-}
+ 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++;
+ }
-#define TUIDL 8
+ new = xmalloc(j + 1);
-static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
+ /*
+ * 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_store *ctx = (struct imap_store *)gctx;
struct imap *imap = ctx->imap;
struct imap_cmd_cb cb;
- char *fmap, *buf;
const char *prefix, *box;
- int ret, i, j, d, len, extra, nocr;
- int start, sbreak = 0, ebreak = 0;
- char flagstr[128], tuid[TUIDL * 2 + 1];
+ int ret;
+ lf_to_crlf(msg);
memset(&cb, 0, sizeof(cb));
- fmap = data->data;
- len = data->len;
- nocr = !data->crlf;
- extra = 0, i = 0;
- if (!CAP(UIDPLUS) && uid) {
- nloop:
- start = i;
- while (i < len)
- if (fmap[i++] == '\n') {
- extra += nocr;
- if (i - 2 + nocr == start) {
- sbreak = ebreak = i - 2 + nocr;
- goto mktid;
- }
- if (!memcmp(fmap + start, "X-TUID: ", 8)) {
- extra -= (ebreak = i) - (sbreak = start) + nocr;
- goto mktid;
- }
- goto nloop;
- }
- /* invalid message */
- free(fmap);
- return DRV_MSG_BAD;
- mktid:
- for (j = 0; j < TUIDL; j++)
- sprintf(tuid + j * 2, "%02x", arc4_getbyte());
- extra += 8 + TUIDL * 2 + 2;
- }
- if (nocr)
- for (; i < len; i++)
- if (fmap[i] == '\n')
- extra++;
-
- cb.dlen = len + extra;
- buf = cb.data = xmalloc(cb.dlen);
- i = 0;
- if (!CAP(UIDPLUS) && uid) {
- if (nocr) {
- for (; i < sbreak; i++)
- if (fmap[i] == '\n') {
- *buf++ = '\r';
- *buf++ = '\n';
- } else
- *buf++ = fmap[i];
- } else {
- memcpy(buf, fmap, sbreak);
- buf += sbreak;
- }
- memcpy(buf, "X-TUID: ", 8);
- buf += 8;
- memcpy(buf, tuid, TUIDL * 2);
- buf += TUIDL * 2;
- *buf++ = '\r';
- *buf++ = '\n';
- i = ebreak;
- }
- if (nocr) {
- for (; i < len; i++)
- if (fmap[i] == '\n') {
- *buf++ = '\r';
- *buf++ = '\n';
- } else
- *buf++ = fmap[i];
- } else
- memcpy(buf, fmap + i, len - i);
-
- free(fmap);
+ cb.dlen = msg->len;
+ cb.data = strbuf_detach(msg, NULL);
- d = 0;
- if (data->flags) {
- d = imap_make_flags(data->flags, flagstr);
- flagstr[d++] = ' ';
- }
- flagstr[d] = 0;
-
- if (!uid) {
- box = gctx->conf->trash;
- prefix = ctx->prefix;
- cb.create = 1;
- if (ctx->trashnc)
- imap->caps = imap->rcaps & ~(1 << LITERALPLUS);
- } else {
- box = gctx->name;
- prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
- cb.create = 0;
- }
- cb.ctx = uid;
- ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
+ box = ctx->name;
+ prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
+ ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
imap->caps = imap->rcaps;
if (ret != DRV_OK)
return ret;
- if (!uid)
- ctx->trashnc = 0;
- else
- gctx->count++;
return DRV_OK;
}
-static void encode_html_chars(struct strbuf *p)
-{
- int i;
- for (i = 0; i < p->len; i++) {
- if (p->buf[i] == '&')
- strbuf_splice(p, i, 1, "&amp;", 5);
- if (p->buf[i] == '<')
- strbuf_splice(p, i, 1, "&lt;", 4);
- if (p->buf[i] == '>')
- strbuf_splice(p, i, 1, "&gt;", 4);
- if (p->buf[i] == '"')
- strbuf_splice(p, i, 1, "&quot;", 6);
- }
-}
-static void wrap_in_html(struct msg_data *msg)
+static void wrap_in_html(struct strbuf *msg)
{
struct strbuf buf = STRBUF_INIT;
- struct strbuf **lines;
- struct strbuf **p;
static char *content_type = "Content-Type: text/html;\n";
static char *pre_open = "<pre>\n";
static char *pre_close = "</pre>\n";
- int added_header = 0;
-
- strbuf_attach(&buf, msg->data, msg->len, msg->len);
- lines = strbuf_split(&buf, '\n');
- strbuf_release(&buf);
- for (p = lines; *p; p++) {
- if (! added_header) {
- if ((*p)->len == 1 && *((*p)->buf) == '\n') {
- strbuf_addstr(&buf, content_type);
- strbuf_addbuf(&buf, *p);
- strbuf_addstr(&buf, pre_open);
- added_header = 1;
- continue;
- }
- }
- else
- encode_html_chars(*p);
- strbuf_addbuf(&buf, *p);
- }
+ const char *body = strstr(msg->buf, "\n\n");
+
+ if (!body)
+ return; /* Headers but no body; no wrapping needed */
+
+ body += 2;
+
+ strbuf_add(&buf, msg->buf, body - msg->buf - 1);
+ strbuf_addstr(&buf, content_type);
+ strbuf_addch(&buf, '\n');
+ strbuf_addstr(&buf, pre_open);
+ strbuf_addstr_xml_quoted(&buf, body);
strbuf_addstr(&buf, pre_close);
- strbuf_list_free(lines);
- msg->len = buf.len;
- msg->data = strbuf_detach(&buf, NULL);
+
+ strbuf_release(msg);
+ *msg = buf;
}
#define CHUNKSIZE 0x1000
-static int read_message(FILE *f, struct msg_data *msg)
+static int read_message(FILE *f, struct strbuf *all_msgs)
{
- struct strbuf buf = STRBUF_INIT;
-
- memset(msg, 0, sizeof(*msg));
-
do {
- if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0)
+ if (strbuf_fread(all_msgs, CHUNKSIZE, f) <= 0)
break;
} while (!feof(f));
- msg->len = buf.len;
- msg->data = strbuf_detach(&buf, NULL);
- return msg->len;
+ return ferror(f) ? -1 : 0;
}
-static int count_messages(struct msg_data *msg)
+static int count_messages(struct strbuf *all_msgs)
{
int count = 0;
- char *p = msg->data;
+ char *p = all_msgs->buf;
while (1) {
- if (!prefixcmp(p, "From ")) {
+ if (starts_with(p, "From ")) {
+ p = strstr(p+5, "\nFrom: ");
+ if (!p) break;
+ p = strstr(p+7, "\nDate: ");
+ if (!p) break;
+ p = strstr(p+7, "\nSubject: ");
+ if (!p) break;
+ p += 10;
count++;
- p += 5;
}
p = strstr(p+5, "\nFrom ");
if (!p)
@@ -1395,112 +1290,242 @@ static int count_messages(struct msg_data *msg)
return count;
}
-static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
+/*
+ * Copy the next message from all_msgs, starting at offset *ofs, to
+ * msg. Update *ofs to the start of the following message. Return
+ * true iff a message was successfully copied.
+ */
+static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
{
char *p, *data;
+ size_t len;
- memset(msg, 0, sizeof *msg);
if (*ofs >= all_msgs->len)
return 0;
- data = &all_msgs->data[*ofs];
- msg->len = all_msgs->len - *ofs;
+ data = &all_msgs->buf[*ofs];
+ len = all_msgs->len - *ofs;
- if (msg->len < 5 || prefixcmp(data, "From "))
+ if (len < 5 || !starts_with(data, "From "))
return 0;
p = strchr(data, '\n');
if (p) {
- p = &p[1];
- msg->len -= p-data;
- *ofs += p-data;
+ p++;
+ len -= p - data;
+ *ofs += p - data;
data = p;
}
p = strstr(data, "\nFrom ");
if (p)
- msg->len = &p[1] - data;
+ len = &p[1] - data;
- msg->data = xmemdupz(data, msg->len);
- *ofs += msg->len;
+ strbuf_add(msg, data, len);
+ *ofs += len;
return 1;
}
-static struct imap_server_conf server = {
- NULL, /* name */
- NULL, /* tunnel */
- NULL, /* host */
- 0, /* port */
- NULL, /* user */
- NULL, /* pass */
- 0, /* use_ssl */
- 1, /* ssl_verify */
- 0, /* use_html */
-};
+static void git_imap_config(void)
+{
+ const char *val = NULL;
-static char *imap_folder;
+ git_config_get_bool("imap.sslverify", &server.ssl_verify);
+ git_config_get_bool("imap.preformattedhtml", &server.use_html);
+ git_config_get_string("imap.folder", &server.folder);
-static int git_imap_config(const char *key, const char *val, void *cb)
+ if (!git_config_get_value("imap.host", &val)) {
+ if (!val) {
+ git_die_config("imap.host", "Missing value for 'imap.host'");
+ } else {
+ if (starts_with(val, "imap:"))
+ val += 5;
+ else if (starts_with(val, "imaps:")) {
+ val += 6;
+ server.use_ssl = 1;
+ }
+ if (starts_with(val, "//"))
+ val += 2;
+ server.host = xstrdup(val);
+ }
+ }
+
+ git_config_get_string("imap.user", &server.user);
+ git_config_get_string("imap.pass", &server.pass);
+ git_config_get_int("imap.port", &server.port);
+ git_config_get_string("imap.tunnel", &server.tunnel);
+ git_config_get_string("imap.authmethod", &server.auth_method);
+}
+
+static int append_msgs_to_imap(struct imap_server_conf *server,
+ struct strbuf* all_msgs, int total)
{
- char imap_key[] = "imap.";
+ struct strbuf msg = STRBUF_INIT;
+ struct imap_store *ctx = NULL;
+ int ofs = 0;
+ int r;
+ int n = 0;
- if (strncmp(key, imap_key, sizeof imap_key - 1))
- return 0;
+ ctx = imap_open_store(server, server->folder);
+ if (!ctx) {
+ fprintf(stderr, "failed to open store\n");
+ return 1;
+ }
+ ctx->name = server->folder;
+
+ fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
+ while (1) {
+ unsigned percent = n * 100 / total;
+
+ fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
+
+ if (!split_msg(all_msgs, &msg, &ofs))
+ break;
+ if (server->use_html)
+ wrap_in_html(&msg);
+ r = imap_store_msg(ctx, &msg);
+ if (r != DRV_OK)
+ break;
+ n++;
+ }
+ fprintf(stderr, "\n");
+
+ imap_close_store(ctx);
+
+ return 0;
+}
+
+#ifdef USE_CURL_FOR_IMAP_SEND
+static CURL *setup_curl(struct imap_server_conf *srvc)
+{
+ CURL *curl;
+ struct strbuf path = STRBUF_INIT;
+
+ if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
+ die("curl_global_init failed");
+
+ curl = curl_easy_init();
+
+ if (!curl)
+ die("curl_easy_init failed");
+
+ curl_easy_setopt(curl, CURLOPT_USERNAME, server.user);
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, server.pass);
+
+ strbuf_addstr(&path, server.host);
+ if (!path.len || path.buf[path.len - 1] != '/')
+ strbuf_addch(&path, '/');
+ strbuf_addstr(&path, server.folder);
+
+ curl_easy_setopt(curl, CURLOPT_URL, path.buf);
+ strbuf_release(&path);
+ curl_easy_setopt(curl, CURLOPT_PORT, server.port);
+
+ if (server.auth_method) {
+ struct strbuf auth = STRBUF_INIT;
+ strbuf_addstr(&auth, "AUTH=");
+ strbuf_addstr(&auth, server.auth_method);
+ curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
+ strbuf_release(&auth);
+ }
+
+ if (!server.use_ssl)
+ curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY);
+
+ 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_UPLOAD, 1L);
+
+ if (0 < verbosity || getenv("GIT_CURL_VERBOSE"))
+ curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+
+ return curl;
+}
+
+static int curl_append_msgs_to_imap(struct imap_server_conf *server,
+ struct strbuf* all_msgs, int total) {
+ int ofs = 0;
+ int n = 0;
+ struct buffer msgbuf = { STRBUF_INIT, 0 };
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+
+ curl = setup_curl(server);
+ curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
+
+ fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : "");
+ while (1) {
+ unsigned percent = n * 100 / total;
+ int prev_len;
+
+ fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
+
+ prev_len = msgbuf.buf.len;
+ if (!split_msg(all_msgs, &msgbuf.buf, &ofs))
+ break;
+ if (server->use_html)
+ wrap_in_html(&msgbuf.buf);
+ lf_to_crlf(&msgbuf.buf);
- if (!val)
- return config_error_nonbool(key);
+ curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
+ (curl_off_t)(msgbuf.buf.len-prev_len));
- key += sizeof imap_key - 1;
+ res = curl_easy_perform(curl);
- if (!strcmp("folder", key)) {
- imap_folder = xstrdup(val);
- } else if (!strcmp("host", key)) {
- if (!prefixcmp(val, "imap:"))
- val += 5;
- else if (!prefixcmp(val, "imaps:")) {
- val += 6;
- server.use_ssl = 1;
+ if(res != CURLE_OK) {
+ fprintf(stderr, "curl_easy_perform() failed: %s\n",
+ curl_easy_strerror(res));
+ break;
}
- if (!prefixcmp(val, "//"))
- val += 2;
- server.host = xstrdup(val);
- } else if (!strcmp("user", key))
- server.user = xstrdup(val);
- else if (!strcmp("pass", key))
- server.pass = xstrdup(val);
- else if (!strcmp("port", key))
- server.port = git_config_int(key, val);
- else if (!strcmp("tunnel", key))
- server.tunnel = xstrdup(val);
- else if (!strcmp("sslverify", key))
- server.ssl_verify = git_config_bool(key, val);
- else if (!strcmp("preformattedHTML", key))
- server.use_html = git_config_bool(key, val);
+
+ n++;
+ }
+ fprintf(stderr, "\n");
+
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
+
return 0;
}
+#endif
int main(int argc, char **argv)
{
- struct msg_data all_msgs, msg;
- struct store *ctx = NULL;
- int uid = 0;
- int ofs = 0;
- int r;
- int total, n = 0;
+ struct strbuf all_msgs = STRBUF_INIT;
+ int total;
int nongit_ok;
git_extract_argv0_path(argv[0]);
- /* init the random number generator */
- arc4_init();
+ git_setup_gettext();
setup_git_directory_gently(&nongit_ok);
- git_config(git_imap_config, NULL);
+ git_imap_config();
+
+ argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
+
+ if (argc)
+ usage_with_options(imap_send_usage, imap_send_options);
+
+#ifndef USE_CURL_FOR_IMAP_SEND
+ if (use_curl) {
+ warning("--curl not supported in this build");
+ use_curl = 0;
+ }
+#elif defined(NO_OPENSSL)
+ if (!use_curl) {
+ warning("--no-curl not supported in this build");
+ use_curl = 1;
+ }
+#endif
if (!server.port)
server.port = server.use_ssl ? 993 : 143;
- if (!imap_folder) {
+ if (!server.folder) {
fprintf(stderr, "no imap store specified\n");
return 1;
}
@@ -1513,7 +1538,12 @@ int main(int argc, char **argv)
}
/* read the messages */
- if (!read_message(stdin, &all_msgs)) {
+ if (read_message(stdin, &all_msgs)) {
+ fprintf(stderr, "error reading input\n");
+ return 1;
+ }
+
+ if (all_msgs.len == 0) {
fprintf(stderr, "nothing to send\n");
return 1;
}
@@ -1525,29 +1555,14 @@ int main(int argc, char **argv)
}
/* write it to the imap server */
- ctx = imap_open_store(&server);
- if (!ctx) {
- fprintf(stderr, "failed to open store\n");
- return 1;
- }
- 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))
- break;
- if (server.use_html)
- wrap_in_html(&msg);
- r = imap_store_msg(ctx, &msg, &uid);
- if (r != DRV_OK)
- break;
- n++;
- }
- fprintf(stderr, "\n");
+ if (server.tunnel)
+ return append_msgs_to_imap(&server, &all_msgs, total);
- imap_close_store(ctx);
+#ifdef USE_CURL_FOR_IMAP_SEND
+ if (use_curl)
+ return curl_append_msgs_to_imap(&server, &all_msgs, total);
+#endif
- return 0;
+ return append_msgs_to_imap(&server, &all_msgs, total);
}