* [PATCH] Add git-imap-send.
@ 2006-03-06 13:09 Mike McCormack
2006-03-09 10:35 ` Junio C Hamano
0 siblings, 1 reply; 13+ messages in thread
From: Mike McCormack @ 2006-03-06 13:09 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 848 bytes --]
This probably needs a bit more work, but I'll solicit comments and
flames anyway...
git-imap-send drops a patch series generated by git-format-patch into an
IMAP folder. This allows patch submitters to send patches through their
own mail program.
git-imap-send uses the following values from the GIT repository
configuration:
The target IMAP folder:
[imap]
Folder = "INBOX.Drafts"
A command to open an ssh tunnel to the imap mail server.
[imap]
Tunnel = "ssh -q user@imap.server.com /usr/bin/imapd ./Maildir
2> /dev/null"
OR
[imap]
Host = imap.server.com
User = bob
Password = pwd
Port = 143
---
.gitignore | 1
Makefile | 4
imap-send.c | 1935
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1939 insertions(+), 1 deletions(-)
create mode 100644 imap-send.c
[-- Attachment #2: b8fbeb40b92b9b6048c9f7ad66a10d8222c58b76.diff --]
[-- Type: text/x-patch, Size: 46777 bytes --]
b8fbeb40b92b9b6048c9f7ad66a10d8222c58b76
diff --git a/.gitignore b/.gitignore
index abbc509..c567cc0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ git-grep
git-hash-object
git-http-fetch
git-http-push
+git-imap-send
git-index-pack
git-init-db
git-local-fetch
diff --git a/Makefile b/Makefile
index b6d8804..bc49f06 100644
--- a/Makefile
+++ b/Makefile
@@ -165,7 +165,7 @@ PROGRAMS = \
git-upload-pack$X git-verify-pack$X git-write-tree$X \
git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
- git-describe$X git-merge-tree$X git-blame$X
+ git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -522,6 +522,8 @@ git-ssh-upload$X: rsh.o
git-ssh-pull$X: rsh.o fetch.o
git-ssh-push$X: rsh.o
+git-imap-send$X: imap-send.o $(LIB_FILE)
+
git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE)
$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
$(LIBS) $(CURL_LIBCURL)
diff --git a/imap-send.c b/imap-send.c
new file mode 100644
index 0000000..b6e5151
--- /dev/null
+++ b/imap-send.c
@@ -0,0 +1,1935 @@
+/*
+ * git-imap-send - drops patches into an imap Drafts folder
+ *
+ * derived from isync/mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
+ * Copyright (C) 2006 Mike McCormack
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#if HAVE_LIBSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/hmac.h>
+#endif
+#include <pwd.h>
+
+#include "cache.h"
+
+#define as(ar) (sizeof(ar)/sizeof(ar[0]))
+
+typedef 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;
+} store_conf_t;
+
+typedef struct string_list {
+ struct string_list *next;
+ char string[1];
+} string_list_t;
+
+typedef struct channel_conf {
+ struct channel_conf *next;
+ char *name;
+ store_conf_t *master, *slave;
+ char *master_name, *slave_name;
+ char *sync_state;
+ string_list_t *patterns;
+ int mops, sops;
+ unsigned max_messages; /* for slave only */
+} channel_conf_t;
+
+typedef struct group_conf {
+ struct group_conf *next;
+ char *name;
+ string_list_t *channels;
+} group_conf_t;
+
+/* 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 */
+#define M_PROCESSED (1<<3) /* registered in pair */
+#define M_NOT_SYNCED (1<<4) /* not in remote mailbox, yet */
+#define M_EXPIRED (1<<5) /* kicked out by MaxMessages */
+
+typedef struct message {
+ struct message *next;
+ /* string_list_t *keywords; */
+ size_t size; /* zero implies "not fetched" */
+ int uid;
+ unsigned char flags, status;
+} message_t;
+
+/* For opts, both in store and driver_t->select() */
+#define OPEN_OLD (1<<0)
+#define OPEN_NEW (1<<1)
+#define OPEN_FLAGS (1<<2)
+#define OPEN_SIZE (1<<3)
+#define OPEN_CREATE (1<<4)
+#define OPEN_EXPUNGE (1<<5)
+#define OPEN_SETFLAGS (1<<6)
+#define OPEN_APPEND (1<<7)
+
+typedef struct store {
+ store_conf_t *conf; /* foreign */
+
+ /* currently open mailbox */
+ const char *name; /* foreign! maybe preset? */
+ char *path; /* own */
+ message_t *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 */
+} store_t;
+
+typedef struct {
+ char *data;
+ int len;
+ unsigned char flags;
+ unsigned char crlf:1;
+} msg_data_t;
+
+#define DRV_OK 0
+#define DRV_MSG_BAD -1
+#define DRV_BOX_BAD -2
+#define DRV_STORE_BAD -3
+
+static int Verbose, Quiet;
+
+static void info( const char *, ... );
+static void warn( const char *, ... );
+
+static char *next_arg( char ** );
+
+static void free_generic_messages( message_t * );
+
+static int nfvasprintf( char **str, const char *fmt, va_list va );
+static int nfsnprintf( char *buf, int blen, const char *fmt, ... );
+
+
+static void arc4_init( void );
+static unsigned char arc4_getbyte( void );
+
+typedef struct imap_server_conf {
+ char *name;
+ char *tunnel;
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+#if HAVE_LIBSSL
+ char *cert_file;
+ unsigned use_imaps:1;
+ unsigned require_ssl:1;
+ unsigned use_sslv2:1;
+ unsigned use_sslv3:1;
+ unsigned use_tlsv1:1;
+ unsigned require_cram:1;
+#endif
+} imap_server_conf_t;
+
+typedef struct imap_store_conf {
+ store_conf_t gen;
+ imap_server_conf_t *server;
+ unsigned use_namespace:1;
+} imap_store_conf_t;
+
+typedef struct imap_message {
+ message_t gen;
+/* int seq; will be needed when expunges are tracked */
+} imap_message_t;
+
+#define NIL (void*)0x1
+#define LIST (void*)0x2
+
+typedef struct _list {
+ struct _list *next, *child;
+ char *val;
+ int len;
+} list_t;
+
+typedef struct {
+ int fd;
+#if HAVE_LIBSSL
+ SSL *ssl;
+ unsigned int use_ssl:1;
+#endif
+} Socket_t;
+
+typedef struct {
+ Socket_t sock;
+ int bytes;
+ int offset;
+ char buf[1024];
+} buffer_t;
+
+struct imap_cmd;
+#define max_in_progress 50 /* make this configurable? */
+
+typedef struct imap {
+ int uidnext; /* from SELECT responses */
+ list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
+ string_list_t *boxes; /* LIST results */
+ message_t **msgapp; /* FETCH results */
+ unsigned caps, rcaps; /* CAPABILITY results */
+ /* command queue */
+ int nexttag, num_in_progress, literal_pending;
+ struct imap_cmd *in_progress, **in_progress_append;
+#if HAVE_LIBSSL
+ SSL_CTX *SSLContext;
+#endif
+ buffer_t buf; /* this is BIG, so put it last */
+} imap_t;
+
+typedef struct imap_store {
+ store_t gen;
+ int uidvalidity;
+ imap_t *imap;
+ const char *prefix;
+ unsigned /*currentnc:1,*/ trashnc:1;
+} imap_store_t;
+
+struct imap_cmd_cb {
+ int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt );
+ void (*done)( imap_store_t *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,
+#if HAVE_LIBSSL
+ CRAM,
+ STARTTLS,
+#endif
+};
+
+static const char *cap_list[] = {
+ "LOGINDISABLED",
+ "UIDPLUS",
+ "LITERAL+",
+ "NAMESPACE",
+#if HAVE_LIBSSL
+ "AUTH=CRAM-MD5",
+ "STARTTLS",
+#endif
+};
+
+#define RESP_OK 0
+#define RESP_NO 1
+#define RESP_BAD 2
+
+static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd );
+
+
+static const char *Flags[] = {
+ "Draft",
+ "Flagged",
+ "Answered",
+ "Seen",
+ "Deleted",
+};
+
+#if HAVE_LIBSSL
+
+/* this gets called when a certificate is to be verified */
+static int
+verify_cert( SSL *ssl )
+{
+ X509 *cert;
+ int err;
+ char buf[256];
+ int ret = -1;
+ BIO *bio;
+
+ cert = SSL_get_peer_certificate( ssl );
+ if (!cert) {
+ fprintf( stderr, "Error, no server certificate\n" );
+ return -1;
+ }
+
+ err = SSL_get_verify_result( ssl );
+ if (err == X509_V_OK)
+ return 0;
+
+ fprintf( stderr, "Error, can't verify certificate: %s (%d)\n",
+ X509_verify_cert_error_string(err), err );
+
+ X509_NAME_oneline( X509_get_subject_name( cert ), buf, sizeof(buf) );
+ info( "\nSubject: %s\n", buf );
+ X509_NAME_oneline( X509_get_issuer_name( cert ), buf, sizeof(buf) );
+ info( "Issuer: %s\n", buf );
+ bio = BIO_new( BIO_s_mem() );
+ ASN1_TIME_print( bio, X509_get_notBefore( cert ) );
+ memset( buf, 0, sizeof(buf) );
+ BIO_read( bio, buf, sizeof(buf) - 1 );
+ info( "Valid from: %s\n", buf );
+ ASN1_TIME_print( bio, X509_get_notAfter( cert ) );
+ memset( buf, 0, sizeof(buf) );
+ BIO_read( bio, buf, sizeof(buf) - 1 );
+ BIO_free( bio );
+ info( " to: %s\n", buf );
+
+ fputs( "\n*** WARNING *** There is no way to verify this certificate. It is\n"
+ " possible that a hostile attacker has replaced the\n"
+ " server certificate. Continue at your own risk!\n"
+ "\nAccept this certificate anyway? [no]: ", stderr );
+ if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y')) {
+ ret = 0;
+ fprintf( stderr, "\n*** Fine, but don't say I didn't warn you!\n\n" );
+ }
+ return ret;
+}
+
+static int
+init_ssl_ctx( imap_store_t *ctx )
+{
+ imap_t *imap = ctx->imap;
+ imap_store_conf_t *conf = (imap_store_conf_t *)ctx->gen.conf;
+ imap_server_conf_t *srvc = conf->server;
+ SSL_METHOD *method;
+ int options = 0;
+
+ if (srvc->use_tlsv1 && !srvc->use_sslv2 && !srvc->use_sslv3)
+ method = TLSv1_client_method();
+ else
+ method = SSLv23_client_method();
+ imap->SSLContext = SSL_CTX_new( method );
+
+ if (!srvc->cert_file) {
+ fprintf( stderr, "Error, CertificateFile not defined\n" );
+ return -1;
+ } else if (access( srvc->cert_file, R_OK ))
+ warn( "*** Warning: can't read CertificateFile, so can't verify server certificates\n" );
+ else if (!SSL_CTX_load_verify_locations( imap->SSLContext, srvc->cert_file, NULL )) {
+ fprintf( stderr, "Error, SSL_CTX_load_verify_locations: %s\n",
+ ERR_error_string( ERR_get_error(), 0 ) );
+ return -1;
+ }
+
+ if (!srvc->use_sslv2)
+ options |= SSL_OP_NO_SSLv2;
+ if (!srvc->use_sslv3)
+ options |= SSL_OP_NO_SSLv3;
+ if (!srvc->use_tlsv1)
+ options |= SSL_OP_NO_TLSv1;
+
+ SSL_CTX_set_options( imap->SSLContext, options );
+
+ /* we check the result of the verification after SSL_connect() */
+ SSL_CTX_set_verify( imap->SSLContext, SSL_VERIFY_NONE, 0 );
+ return 0;
+}
+#endif /* HAVE_LIBSSL */
+
+static void
+socket_perror( const char *func, Socket_t *sock, int ret )
+{
+#if HAVE_LIBSSL
+ int err;
+
+ if (sock->use_ssl) {
+ switch ((err = SSL_get_error( sock->ssl, ret ))) {
+ case SSL_ERROR_SYSCALL:
+ case SSL_ERROR_SSL:
+ if ((err = ERR_get_error()) == 0) {
+ if (ret == 0)
+ fprintf( stderr, "SSL_%s:got EOF\n", func );
+ else
+ fprintf( stderr, "SSL_%s:%d:%s\n", func, errno, strerror(errno) );
+ } else
+ fprintf( stderr, "SSL_%s:%d:%s\n", func, err, ERR_error_string( err, 0 ) );
+ return;
+ default:
+ fprintf( stderr, "SSL_%s:%d:unhandled SSL error\n", func, err );
+ break;
+ }
+ return;
+ }
+#else
+ (void)sock;
+#endif
+ if (ret < 0)
+ perror( func );
+ else
+ fprintf( stderr, "%s: unexpected EOF\n", func );
+}
+
+static int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+ int n =
+#if HAVE_LIBSSL
+ sock->use_ssl ? SSL_read( sock->ssl, buf, len ) :
+#endif
+ read( sock->fd, buf, len );
+ if (n <= 0) {
+ socket_perror( "read", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+static int
+socket_write( Socket_t *sock, char *buf, int len )
+{
+ int n =
+#if HAVE_LIBSSL
+ sock->use_ssl ? SSL_write( sock->ssl, buf, len ) :
+#endif
+ write( sock->fd, buf, len );
+ if (n != len) {
+ socket_perror( "write", sock, n );
+ close( sock->fd );
+ sock->fd = -1;
+ }
+ return n;
+}
+
+#if 0
+
+static int
+socket_pending( Socket_t *sock )
+{
+ int num = -1;
+
+ if (ioctl( sock->fd, FIONREAD, &num ) < 0)
+ return -1;
+ if (num > 0)
+ return num;
+#if HAVE_LIBSSL
+ if (sock->use_ssl)
+ return SSL_pending( sock->ssl );
+#endif
+ return 0;
+}
+
+#endif
+
+/* simple line buffering */
+static int
+buffer_gets( buffer_t * 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)
+ memcpy( 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
+info( const char *msg, ... )
+{
+ va_list va;
+
+ if (!Quiet) {
+ va_start( va, msg );
+ vprintf( msg, va );
+ va_end( va );
+ fflush( stdout );
+ }
+}
+
+static void
+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 0;
+ while (isspace( (unsigned char) **s ))
+ (*s)++;
+ if (!**s) {
+ *s = 0;
+ return 0;
+ }
+ 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 = 0;
+ }
+ return ret;
+}
+
+static void
+free_generic_messages( message_t *msgs )
+{
+ message_t *tmsg;
+
+ for (; msgs; msgs = tmsg) {
+ tmsg = msgs->next;
+ free( msgs );
+ }
+}
+
+static int
+vasprintf( char **strp, const char *fmt, va_list ap )
+{
+ int len;
+ char tmp[1024];
+
+ if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 )))
+ return -1;
+ if (len >= (int)sizeof(tmp))
+ vsprintf( *strp, fmt, ap );
+ else
+ memcpy( *strp, tmp, len + 1 );
+ return len;
+}
+
+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.\n");
+ va_end( va );
+ return ret;
+}
+
+static int
+nfvasprintf( char **str, const char *fmt, va_list va )
+{
+ int ret = vasprintf( str, fmt, va );
+ if (ret < 0)
+ die( "Fatal: Out of memory\n");
+ 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( 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( imap_store_t *ctx, struct imap_cmd_cb *cb,
+ const char *fmt, va_list ap )
+{
+ imap_t *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, 0 );
+
+ 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 (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 && cb->data)
+ 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 ||
+ (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2)
+ {
+ free( cmd->cmd );
+ free( cmd );
+ return NULL;
+ }
+ cmd->cb.data = 0;
+ } else
+ imap->literal_pending = 1;
+ } else if (cmd->cb.cont)
+ imap->literal_pending = 1;
+ cmd->next = 0;
+ *imap->in_progress_append = cmd;
+ imap->in_progress_append = &cmd->next;
+ imap->num_in_progress++;
+ return cmd;
+}
+
+static struct imap_cmd *
+issue_imap_cmd( imap_store_t *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;
+}
+
+#if 0
+
+static struct imap_cmd *
+issue_imap_cmd_w( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... )
+{
+ imap_t *imap = ctx->imap;
+ struct imap_cmd *ret;
+ va_list ap;
+
+ va_start( ap, fmt );
+ ret = v_issue_imap_cmd( ctx, cb, fmt, ap );
+ va_end( ap );
+ while (imap->num_in_progress > max_in_progress ||
+ socket_pending( &imap->buf.sock ))
+ get_cmd_result( ctx, 0 );
+ return ret;
+}
+
+#endif
+
+static int
+imap_exec( imap_store_t *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 );
+}
+
+#if 0
+
+static int
+imap_exec_b( imap_store_t *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_BOX_BAD;
+ default: return DRV_OK;
+ }
+}
+
+#endif
+
+static int
+imap_exec_m( imap_store_t *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 void
+drain_imap_replies( imap_t *imap )
+{
+ while (imap->num_in_progress)
+ get_cmd_result( imap, 0 );
+}
+*/
+
+static int
+is_atom( list_t *list )
+{
+ return list && list->val && list->val != NIL && list->val != LIST;
+}
+
+static int
+is_list( list_t *list )
+{
+ return list && list->val == LIST;
+}
+
+static void
+free_list( list_t *list )
+{
+ list_t *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( imap_t *imap, char **sp, list_t **curp, int level )
+{
+ list_t *cur;
+ char *s = *sp, *p;
+ int n, bytes;
+
+ for (;;) {
+ while (isspace( (unsigned char)*s ))
+ s++;
+ if (level && *s == ')') {
+ s++;
+ break;
+ }
+ *curp = cur = xmalloc( sizeof(*cur) );
+ curp = &cur->next;
+ cur->val = 0; /* 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 ))
+ goto bail;
+ } else if (*s == '"') {
+ /* quoted string */
+ s++;
+ p = s;
+ for (; *s != '"'; s++)
+ if (!*s)
+ goto bail;
+ cur->len = s - p;
+ s++;
+ cur->val = xmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ } 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 = xmalloc( cur->len + 1 );
+ memcpy( cur->val, p, cur->len );
+ cur->val[cur->len] = 0;
+ }
+ }
+
+ if (!level)
+ break;
+ if (!*s)
+ goto bail;
+ }
+ *sp = s;
+ *curp = 0;
+ return 0;
+
+ bail:
+ *curp = 0;
+ return -1;
+}
+
+static list_t *
+parse_imap_list( imap_t *imap, char **sp )
+{
+ list_t *head;
+
+ if (!parse_imap_list_l( imap, sp, &head, 0 ))
+ return head;
+ free_list( head );
+ return NULL;
+}
+
+static list_t *
+parse_list( char **sp )
+{
+ return parse_imap_list( 0, sp );
+}
+
+static int
+parse_fetch( imap_t *imap, char *cmd ) /* move this down */
+{
+ list_t *tmp, *list, *flags;
+ char *body = 0;
+ imap_message_t *cur;
+ msg_data_t *msgdata;
+ struct imap_cmd *cmdp;
+ int uid = 0, mask = 0, status = 0, size = 0;
+ unsigned i;
+
+ list = parse_imap_list( imap, &cmd );
+
+ if (!is_list( list )) {
+ fprintf( stderr, "IMAP error: bogus FETCH response\n" );
+ free_list( list );
+ return -1;
+ }
+
+ for (tmp = list->child; tmp; tmp = tmp->next) {
+ if (is_atom( tmp )) {
+ if (!strcmp( "UID", tmp->val )) {
+ tmp = tmp->next;
+ if (is_atom( tmp ))
+ uid = atoi( tmp->val );
+ else
+ fprintf( stderr, "IMAP error: unable to parse UID\n" );
+ } else if (!strcmp( "FLAGS", tmp->val )) {
+ tmp = tmp->next;
+ if (is_list( tmp )) {
+ for (flags = tmp->child; flags; flags = flags->next) {
+ if (is_atom( flags )) {
+ if (flags->val[0] == '\\') { /* ignore user-defined flags for now */
+ if (!strcmp( "Recent", flags->val + 1)) {
+ status |= M_RECENT;
+ goto flagok;
+ }
+ for (i = 0; i < as(Flags); i++)
+ if (!strcmp( Flags[i], flags->val + 1 )) {
+ mask |= 1 << i;
+ goto flagok;
+ }
+ fprintf( stderr, "IMAP warning: unknown system flag %s\n", flags->val );
+ }
+ flagok: ;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse FLAGS list\n" );
+ }
+ status |= M_FLAGS;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse FLAGS\n" );
+ } else if (!strcmp( "RFC822.SIZE", tmp->val )) {
+ tmp = tmp->next;
+ if (is_atom( tmp ))
+ size = atoi( tmp->val );
+ else
+ fprintf( stderr, "IMAP error: unable to parse SIZE\n" );
+ } else if (!strcmp( "BODY[]", tmp->val )) {
+ tmp = tmp->next;
+ if (is_atom( tmp )) {
+ body = tmp->val;
+ tmp->val = 0; /* don't free together with list */
+ size = tmp->len;
+ } else
+ fprintf( stderr, "IMAP error: unable to parse BODY[]\n" );
+ }
+ }
+ }
+
+ if (body) {
+ for (cmdp = imap->in_progress; cmdp; cmdp = cmdp->next)
+ if (cmdp->cb.uid == uid)
+ goto gotuid;
+ fprintf( stderr, "IMAP error: unexpected FETCH response (UID %d)\n", uid );
+ free_list( list );
+ return -1;
+ gotuid:
+ msgdata = (msg_data_t *)cmdp->cb.ctx;
+ msgdata->data = body;
+ msgdata->len = size;
+ msgdata->crlf = 1;
+ if (status & M_FLAGS)
+ msgdata->flags = mask;
+ } else if (uid) { /* ignore async flag updates for now */
+ /* XXX this will need sorting for out-of-order (multiple queries) */
+ cur = xcalloc( sizeof(*cur), 1 );
+ *imap->msgapp = &cur->gen;
+ imap->msgapp = &cur->gen.next;
+ cur->gen.next = 0;
+ cur->gen.uid = uid;
+ cur->gen.flags = mask;
+ cur->gen.status = status;
+ cur->gen.size = size;
+ }
+
+ free_list( list );
+ return 0;
+}
+
+static void
+parse_capability( imap_t *imap, char *cmd )
+{
+ char *arg;
+ unsigned i;
+
+ imap->caps = 0x80000000;
+ while ((arg = next_arg( &cmd )))
+ for (i = 0; i < as(cap_list); i++)
+ if (!strcmp( cap_list[i], arg ))
+ imap->caps |= 1 << i;
+ imap->rcaps = imap->caps;
+}
+
+static int
+parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s )
+{
+ imap_t *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->gen.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->gen.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( imap_store_t *ctx, struct imap_cmd *tcmd )
+{
+ imap_t *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 )) {
+ imap->ns_personal = parse_list( &cmd );
+ imap->ns_other = parse_list( &cmd );
+ imap->ns_shared = parse_list( &cmd );
+ } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) ||
+ !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) {
+ if ((resp = parse_response_code( ctx, 0, cmd )) != RESP_OK)
+ return resp;
+ } 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(!strcmp ( "FETCH", arg1 )) {
+ if (parse_fetch( imap, cmd ))
+ return RESP_BAD;
+ }
+ } 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 = 0;
+ 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, 0, "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;
+ }
+ 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 );
+ if (cmdp->cb.data)
+ free( cmdp->cb.data );
+ free( cmdp->cmd );
+ free( cmdp );
+ if (!tcmd || tcmd == cmdp)
+ return resp;
+ }
+ }
+ /* not reached */
+}
+
+static void
+imap_close_server( imap_store_t *ictx )
+{
+ imap_t *imap = ictx->imap;
+
+ if (imap->buf.sock.fd != -1) {
+ imap_exec( ictx, 0, "LOGOUT" );
+ close( imap->buf.sock.fd );
+ }
+#ifdef HAVE_LIBSSL
+ if (imap->SSLContext)
+ SSL_CTX_free( imap->SSLContext );
+#endif
+ free_list( imap->ns_personal );
+ free_list( imap->ns_other );
+ free_list( imap->ns_shared );
+ free( imap );
+}
+
+static void
+imap_close_store( store_t *ctx )
+{
+ imap_close_server( (imap_store_t *)ctx );
+ free_generic_messages( ctx->msgs );
+ free( ctx );
+}
+
+#ifdef HAVE_LIBSSL
+static int
+start_tls( imap_store_t *ctx )
+{
+ imap_t *imap = ctx->imap;
+ int ret;
+ static int ssl_inited;
+
+ if (!ssl_inited) {
+ SSL_library_init();
+ SSL_load_error_strings();
+ ssl_inited = 1;
+ }
+
+ if (init_ssl_ctx( ctx ))
+ return 1;
+
+ imap->buf.sock.ssl = SSL_new( imap->SSLContext );
+ SSL_set_fd( imap->buf.sock.ssl, imap->buf.sock.fd );
+ if ((ret = SSL_connect( imap->buf.sock.ssl )) <= 0) {
+ socket_perror( "connect", &imap->buf.sock, ret );
+ return 1;
+ }
+
+ /* verify the server certificate */
+ if (verify_cert( imap->buf.sock.ssl ))
+ return 1;
+
+ imap->buf.sock.use_ssl = 1;
+ info( "Connection is now encrypted\n" );
+ return 0;
+}
+
+#define ENCODED_SIZE(n) (4*((n+2)/3))
+
+static char
+hexchar( unsigned int b )
+{
+ if (b < 10)
+ return '0' + b;
+ return 'a' + (b - 10);
+}
+
+/* XXX merge into do_cram_auth? */
+static char *
+cram( const char *challenge, const char *user, const char *pass )
+{
+ HMAC_CTX hmac;
+ char hash[16];
+ char hex[33];
+ int i;
+ unsigned int hashlen = sizeof(hash);
+ char buf[256];
+ int len = strlen( challenge );
+ char *response = xcalloc( 1 + len, 1 );
+ char *final;
+
+ /* response will always be smaller than challenge because we are
+ * decoding.
+ */
+ len = EVP_DecodeBlock( (unsigned char *)response, (unsigned char *)challenge, strlen( challenge ) );
+
+ HMAC_Init( &hmac, (unsigned char *) pass, strlen( pass ), EVP_md5() );
+ HMAC_Update( &hmac, (unsigned char *)response, strlen( response ) );
+ HMAC_Final( &hmac, (unsigned char *)hash, &hashlen );
+
+ assert( hashlen == sizeof(hash) );
+
+ free( response );
+
+ 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 );
+ }
+
+ nfsnprintf( buf, sizeof(buf), "%s %s", user, hex );
+
+ len = strlen( buf );
+ len = ENCODED_SIZE( len ) + 1;
+ final = xmalloc( len );
+ final[len - 1] = 0;
+
+ assert( EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, strlen( buf ) ) == len - 1 );
+
+ return final;
+}
+
+static int
+do_cram_auth (imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt)
+{
+ imap_t *imap = ctx->imap;
+ imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
+ char *resp;
+ int n, l;
+
+ resp = cram( prompt, srvc->user, srvc->pass );
+
+ if (Verbose)
+ printf( ">+> %s\n", resp );
+ l = strlen( resp );
+ n = socket_write( &imap->buf.sock, resp, l );
+ free( resp );
+ if (n != l)
+ return -1;
+ cmdp->cb.cont = 0;
+ return 0;
+}
+#endif
+
+static store_t *
+imap_open_store( imap_server_conf_t *srvc )
+{
+ imap_store_t *ctx;
+ imap_t *imap;
+ char *arg, *rsp;
+ struct hostent *he;
+ struct sockaddr_in addr;
+ int s, a[2], preauth;
+#if HAVE_LIBSSL
+ int use_ssl;
+#endif
+
+ ctx = xcalloc( sizeof(*ctx), 1 );
+
+ ctx->imap = imap = xcalloc( sizeof(*imap), 1 );
+ imap->buf.sock.fd = -1;
+ imap->in_progress_append = &imap->in_progress;
+
+ /* open connection to IMAP server */
+#if HAVE_LIBSSL
+ use_ssl = 0;
+#endif
+
+ if (srvc->tunnel) {
+ info( "Starting tunnel '%s'... ", srvc->tunnel );
+
+ if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
+ perror( "socketpair" );
+ exit( 1 );
+ }
+
+ if (fork() == 0) {
+ 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, 0 );
+ _exit( 127 );
+ }
+
+ close (a[0]);
+
+ imap->buf.sock.fd = a[1];
+
+ info( "ok\n" );
+ } else {
+ memset( &addr, 0, sizeof(addr) );
+ addr.sin_port = htons( srvc->port );
+ addr.sin_family = AF_INET;
+
+ info( "Resolving %s... ", srvc->host );
+ he = gethostbyname( srvc->host );
+ if (!he) {
+ perror( "gethostbyname" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
+
+ s = socket( PF_INET, SOCK_STREAM, 0 );
+
+ info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
+ if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
+ close( s );
+ perror( "connect" );
+ goto bail;
+ }
+ info( "ok\n" );
+
+ imap->buf.sock.fd = s;
+
+#if HAVE_LIBSSL
+ if (srvc->use_imaps) {
+ if (start_tls( ctx ))
+ goto bail;
+ use_ssl = 1;
+ }
+#endif
+ }
+
+ /* 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, 0, rsp );
+ if (!imap->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+ goto bail;
+
+ if (!preauth) {
+#if HAVE_LIBSSL
+ if (!srvc->use_imaps && (srvc->use_sslv2 || srvc->use_sslv3 || srvc->use_tlsv1)) {
+ /* always try to select SSL support if available */
+ if (CAP(STARTTLS)) {
+ if (imap_exec( ctx, 0, "STARTTLS" ) != RESP_OK)
+ goto bail;
+ if (start_tls( ctx ))
+ goto bail;
+ use_ssl = 1;
+
+ if (imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK)
+ goto bail;
+ } else {
+ if (srvc->require_ssl) {
+ fprintf( stderr, "IMAP error: SSL support not available\n" );
+ goto bail;
+ } else
+ warn( "IMAP warning: SSL support not available\n" );
+ }
+ }
+#endif
+
+ 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 = strdup( arg );
+ }
+#if HAVE_LIBSSL
+ if (CAP(CRAM)) {
+ struct imap_cmd_cb cb;
+
+ info( "Authenticating with CRAM-MD5\n" );
+ memset( &cb, 0, sizeof(cb) );
+ cb.cont = do_cram_auth;
+ if (imap_exec( ctx, &cb, "AUTHENTICATE CRAM-MD5" ) != RESP_OK)
+ goto bail;
+ } else if (srvc->require_cram) {
+ fprintf( stderr, "IMAP error: CRAM-MD5 authentication is not supported by server\n" );
+ goto bail;
+ } else
+#endif
+ {
+ if (CAP(NOLOGIN)) {
+ fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
+ goto bail;
+ }
+#if HAVE_LIBSSL
+ if (!use_ssl)
+#endif
+ warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+ if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
+ fprintf( stderr, "IMAP error: LOGIN failed\n" );
+ goto bail;
+ }
+ }
+ } /* !preauth */
+
+ ctx->prefix = "";
+ ctx->trashnc = 1;
+ return (store_t *)ctx;
+
+ bail:
+ imap_close_store( &ctx->gen );
+ return 0;
+}
+
+static int
+imap_make_flags( int flags, char *buf )
+{
+ const char *s;
+ unsigned i, d;
+
+ for (i = d = 0; i < as(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;
+}
+
+#define TUIDL 8
+
+static int
+imap_store_msg( store_t *gctx, msg_data_t *data, int *uid )
+{
+ imap_store_t *ctx = (imap_store_t *)gctx;
+ imap_t *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];
+
+ 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 );
+
+ 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 = (gctx->opts & OPEN_CREATE) != 0;
+ /*if (ctx->currentnc)
+ imap->caps = imap->rcaps & ~(1 << LITERALPLUS);*/
+ }
+ cb.ctx = uid;
+ ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr );
+ imap->caps = imap->rcaps;
+ if (ret != DRV_OK)
+ return ret;
+ if (!uid)
+ ctx->trashnc = 0;
+ else {
+ /*ctx->currentnc = 0;*/
+ gctx->count++;
+ }
+
+ return DRV_OK;
+}
+
+
+/*
+ * mm-push-imap - push a patch into an imap folder
+ */
+
+#define CHUNKSIZE 0x1000
+
+static int
+read_message( FILE *f, msg_data_t *msg )
+{
+ int len, r;
+
+ memset( msg, 0, sizeof *msg );
+ len = CHUNKSIZE;
+ msg->data = xmalloc( len+1 );
+ msg->data[0] = 0;
+
+ while(!feof( f )) {
+ if (msg->len >= len) {
+ void *p;
+ len += CHUNKSIZE;
+ p = xrealloc(msg->data, len+1);
+ if (!p)
+ break;
+ }
+ r = fread( &msg->data[msg->len], 1, len - msg->len, f );
+ if (r <= 0)
+ break;
+ msg->len += r;
+ }
+ msg->data[msg->len] = 0;
+ return msg->len;
+}
+
+static int count_messages( msg_data_t *msg )
+{
+ int count = 0;
+ char *p = msg->data;
+
+ while (1) {
+ if (!strncmp( "From ", p, 5 )) {
+ count++;
+ p += 5;
+ }
+ p = strstr( p+5, "\nFrom ");
+ if (!p)
+ break;
+ p++;
+ }
+ return count;
+}
+
+static int
+split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
+{
+ char *p, *data;
+
+ memset( msg, 0, sizeof *msg );
+ if (*ofs >= all_msgs->len)
+ return 0;
+
+ data = &all_msgs->data[ *ofs ];
+ msg->len = all_msgs->len - *ofs;
+
+ if (msg->len < 5 || strncmp( data, "From ", 5 ))
+ return 0;
+
+ p = strstr( data, "\nFrom " );
+ if (p)
+ msg->len = &p[1] - data;
+
+ msg->data = xmalloc( msg->len + 1 );
+ if (!msg->data)
+ return 0;
+
+ memcpy( msg->data, data, msg->len );
+ msg->data[ msg->len ] = 0;
+
+ *ofs += msg->len;
+ return 1;
+}
+
+static imap_server_conf_t server =
+{
+ NULL, /* name */
+ NULL, /* tunnel */
+ NULL, /* host */
+ 0, /* port */
+ NULL, /* user */
+ NULL, /* pass */
+#if HAVE_LIBSSL
+ NULL, /* cert_file */
+ 0, /* use_imaps */
+ 0, /* require_ssl */
+ 0, /* use_sslv2 */
+ 0, /* use_sslv3 */
+ 0, /* use_tlsv1 */
+ 0, /* require_cram */
+#endif
+};
+
+static char *imap_folder;
+
+static int
+git_imap_config(const char *key, const char *val)
+{
+ char imap_key[] = "imap.";
+
+ if (strncmp( key, imap_key, sizeof imap_key - 1 ))
+ return 0;
+ key += sizeof imap_key - 1;
+
+ if (!strcasecmp( "Folder", key )) {
+ imap_folder = strdup( val );
+ } else if (!strcasecmp( "Host", key )) {
+#if HAVE_LIBSSL
+ if (!memcmp( "imaps:", val, 6 )) {
+ val += 6;
+ server.use_imaps = 1;
+ server.use_sslv2 = 1;
+ server.use_sslv3 = 1;
+ if (!server.port)
+ server.port = 993;
+ } else
+#endif
+ {
+ if (!memcmp( "imap:", val, 5 ))
+ val += 5;
+ if (!server.port)
+ server.port = 143;
+ }
+ if (!memcmp( "//", val, 2 ))
+ val += 2;
+ server.host = strdup( val );
+ }
+ else if (!strcasecmp( "User", key ))
+ server.user = strdup( val );
+ else if (!strcasecmp( "Pass", key ))
+ server.pass = strdup( val );
+ else if (!strcasecmp( "Port", key ))
+ server.port = git_config_int( key, val );
+ else if (!strcasecmp( "Tunnel", key ))
+ server.tunnel = strdup( val );
+#if HAVE_LIBSSL
+ else if (!strcasecmp( "CertificateFile", key ))
+ server.cert_file = expand_strdup( val );
+ else if (!strcasecmp( "RequireSSL", key ))
+ server.require_ssl = git_config_bool( key, val );
+ else if (!strcasecmp( "UseSSLv2", key ))
+ server.use_sslv2 = git_config_bool( key, val );
+ else if (!strcasecmp( "UseSSLv3", key ))
+ server.use_sslv3 = git_config_bool( key, val );
+ else if (!strcasecmp( "UseTLSv1", key ))
+ server.use_tlsv1 = git_config_bool( key, val );
+ else if (!strcasecmp( "RequireCRAM", key ))
+ server.require_cram = git_config_bool( key, val );
+#endif
+ return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+ msg_data_t all_msgs, msg;
+ store_t *ctx = 0;
+ int uid = 0;
+ int ofs = 0;
+ int r;
+ int total, n = 0;
+
+ /* init the random number generator */
+ arc4_init();
+
+ git_config( git_imap_config );
+
+ if (!imap_folder) {
+ fprintf( stderr, "no imap store specified\n" );
+ return 1;
+ }
+
+ /* read the messages */
+ if (!read_message( stdin, &all_msgs )) {
+ fprintf(stderr,"nothing to send\n");
+ return 1;
+ }
+
+ /* write it to the imap server */
+ ctx = imap_open_store( &server );
+ if (!ctx) {
+ fprintf(stderr,"failed to open store\n");
+ return 1;
+ }
+
+ total = count_messages( &all_msgs );
+ fprintf( stderr, "sending %d messages\n", total );
+ ctx->name = imap_folder;
+ while (1) {
+ fprintf( stderr, "sent (%d/%d)\r", n, total );
+ if (!split_msg( &all_msgs, &msg, &ofs ))
+ break;
+ r = imap_store_msg( ctx, &msg, &uid );
+ if (r != DRV_OK) break;
+ n++;
+ }
+ fprintf( stderr,"\n" );
+
+ imap_close_store( ctx );
+
+ return 0;
+}
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-06 13:09 [PATCH] Add git-imap-send Mike McCormack
@ 2006-03-09 10:35 ` Junio C Hamano
2006-03-09 11:30 ` Johannes Schindelin
2006-03-10 4:58 ` Mike McCormack
0 siblings, 2 replies; 13+ messages in thread
From: Junio C Hamano @ 2006-03-09 10:35 UTC (permalink / raw)
To: Mike McCormack; +Cc: git
Mike McCormack <mike@codeweavers.com> writes:
> This probably needs a bit more work, but I'll solicit comments and
> flames anyway...
OK, then please work a bit more ;-)
I kind of like this approach and even wish my e-mail workflow
involved an imap server with draft folders. Currently I do
everything in Gnus and my drafts are on local disk.
> The target IMAP folder:
>
> [imap]
> Folder = "INBOX.Drafts"
>
> A command to open an ssh tunnel to the imap mail server.
The .git/config file is a good choice for storing this
information, because you might even use different draft folders
for different projects, i.e. the configuration is
per-repository.
> [imap]
> Tunnel = "ssh -q user@imap.server.com /usr/bin/imapd ./Maildir
> 2> /dev/null"
> [imap]
> Host = imap.server.com
> User = bob
> Password = pwd
> Port = 143
These I am not so sure. It _might_ make sense to have something
like this under $HOME/. Isn't there an established convention
for storing something like this for existing MUAs?
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *...
> + * As a special exception, mbsync may be linked with the OpenSSL library,
> + * despite that library's more restrictive license.
Hmmm.
> +#include <sys/types.h>
>...
> +#include <pwd.h>
> +
> +#include "cache.h"
If you are including "cache.h", you probably do not need to
include many of the standard include files.
> +#define as(ar) (sizeof(ar)/sizeof(ar[0]))
We have something like this in apply.c, exec_cmd.c and git.c;
probably we would want a macro in "cache.h".
git.c:326:#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
> +static int
> +git_imap_config(const char *key, const char *val)
> +{
>...
> + if (!strcasecmp( "Folder", key )) {
git_config calls you after downcasing the keys, so you do not
need to do strcasecmp. Just spell things out in lowercase.
> + if (!memcmp( "imaps:", val, 6 )) {
> + if (!memcmp( "imap:", val, 5 ))
Is val always longer than 5 or 6 bytes here?
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 10:35 ` Junio C Hamano
@ 2006-03-09 11:30 ` Johannes Schindelin
2006-03-09 11:39 ` Andreas Ericsson
2006-03-09 16:41 ` Linus Torvalds
2006-03-10 4:58 ` Mike McCormack
1 sibling, 2 replies; 13+ messages in thread
From: Johannes Schindelin @ 2006-03-09 11:30 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Mike McCormack, git
Hi,
On Thu, 9 Mar 2006, Junio C Hamano wrote:
> Mike McCormack <mike@codeweavers.com> writes:
>
> > + if (!memcmp( "imaps:", val, 6 )) {
> > + if (!memcmp( "imap:", val, 5 ))
>
> Is val always longer than 5 or 6 bytes here?
That does not matter, since they are strings, and the memcmp should not
look further if they are shorter (because the comparison to '\0' failed
already).
However, if !memcmp("imaps:", val, 6), it means that val starts with
the string "imaps:", right? Then !memcmp("imap:", val, 5) must always
fail, no?
Ciao,
Dscho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 11:30 ` Johannes Schindelin
@ 2006-03-09 11:39 ` Andreas Ericsson
2006-03-09 11:44 ` Johannes Schindelin
2006-03-09 16:41 ` Linus Torvalds
1 sibling, 1 reply; 13+ messages in thread
From: Andreas Ericsson @ 2006-03-09 11:39 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, Mike McCormack, git
Johannes Schindelin wrote:
> Hi,
>
> On Thu, 9 Mar 2006, Junio C Hamano wrote:
>
>
>>Mike McCormack <mike@codeweavers.com> writes:
>>
>>
>>>+ if (!memcmp( "imaps:", val, 6 )) {
>>>+ if (!memcmp( "imap:", val, 5 ))
>>
>>Is val always longer than 5 or 6 bytes here?
>
>
> That does not matter, since they are strings, and the memcmp should not
> look further if they are shorter (because the comparison to '\0' failed
> already).
>
That's what strcmp() does. memcmp() walks the lenghth even if it
encounters nul bytes. Perhaps you confuse it with strncmp()?
> However, if !memcmp("imaps:", val, 6), it means that val starts with
> the string "imaps:", right? Then !memcmp("imap:", val, 5) must always
> fail, no?
>
Yes. Recent gcc's will recognize it as dead code and remove it. It's
still ugly though.
--
Andreas Ericsson andreas.ericsson@op5.se
OP5 AB www.op5.se
Tel: +46 8-230225 Fax: +46 8-230231
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 11:39 ` Andreas Ericsson
@ 2006-03-09 11:44 ` Johannes Schindelin
2006-03-09 13:26 ` Mark Wooding
0 siblings, 1 reply; 13+ messages in thread
From: Johannes Schindelin @ 2006-03-09 11:44 UTC (permalink / raw)
To: Andreas Ericsson; +Cc: Junio C Hamano, Mike McCormack, git
Hi,
On Thu, 9 Mar 2006, Andreas Ericsson wrote:
> Johannes Schindelin wrote:
> > Hi,
> >
> > On Thu, 9 Mar 2006, Junio C Hamano wrote:
> >
> >
> > > Mike McCormack <mike@codeweavers.com> writes:
> > >
> > >
> > > > + if (!memcmp( "imaps:", val, 6 )) {
> > > > + if (!memcmp( "imap:", val, 5 ))
> > >
> > > Is val always longer than 5 or 6 bytes here?
> >
> >
> > That does not matter, since they are strings, and the memcmp should not look
> > further if they are shorter (because the comparison to '\0' failed already).
> >
>
> That's what strcmp() does. memcmp() walks the lenghth even if it encounters
> nul bytes. Perhaps you confuse it with strncmp()?
Sorry, I was unclear. Of course, memcmp() does not stop on NUL. But it
stops when that NUL is different from what the other pointer has. Which is
the case here.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 11:44 ` Johannes Schindelin
@ 2006-03-09 13:26 ` Mark Wooding
2006-03-09 13:47 ` Johannes Schindelin
0 siblings, 1 reply; 13+ messages in thread
From: Mark Wooding @ 2006-03-09 13:26 UTC (permalink / raw)
To: git
Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> Sorry, I was unclear. Of course, memcmp() does not stop on NUL. But it
> stops when that NUL is different from what the other pointer has. Which is
> the case here.
Does it really? My copy of the spec doesn't say that. It says only
this:
: 7.21.4.1 The memcmp function
:
: Synopsis
:
: #include <string.h>
: int memcmp(const void *s1, const void *s2, size_t n);
:
: Description
:
: The memcmp() function compares the first n characters of the object
: pointed to by s1 to the first n characters of the object pointed to by
: s2.262)
:
: Returns
:
: The memcmp function returns an integer greater than, equal to, or less
: than zero, accordingly as the object pointed to by s1 is greater than,
: equal to, or less than the object pointed to by s2.
:
: 262) The contents of ``holes'' used as padding for purposes of
: alignment within structure objects are indeterminate. Strings shorter
: than their allocated space and unions may also cause problems in
: comparison.
There are reasons for which it'd be desirable that memcmp really compare
all the bytes, even if it can in theory stop early: in particular, there
are cases where early exit can leak timing information which makes it
possible to attack cryptographic protocols.
I'd have to recommend strncmp for this job.
-- [mdw]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 13:26 ` Mark Wooding
@ 2006-03-09 13:47 ` Johannes Schindelin
2006-03-10 0:38 ` Mark Wooding
0 siblings, 1 reply; 13+ messages in thread
From: Johannes Schindelin @ 2006-03-09 13:47 UTC (permalink / raw)
To: Mark Wooding; +Cc: git
Hi,
On Thu, 9 Mar 2006, Mark Wooding wrote:
> There are reasons for which it'd be desirable that memcmp really compare
> all the bytes, even if it can in theory stop early: in particular, there
> are cases where early exit can leak timing information which makes it
> possible to attack cryptographic protocols.
I would be astonished if memcmp has to be timing proof for *all*
applications, just to keep crypto people happy. I don't *want* a slow-down
in my super-duper 3d shooter.
> I'd have to recommend strncmp for this job.
Fully agree.
Ciao,
Dscho
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 11:30 ` Johannes Schindelin
2006-03-09 11:39 ` Andreas Ericsson
@ 2006-03-09 16:41 ` Linus Torvalds
2006-03-09 18:09 ` Junio C Hamano
1 sibling, 1 reply; 13+ messages in thread
From: Linus Torvalds @ 2006-03-09 16:41 UTC (permalink / raw)
To: Johannes Schindelin; +Cc: Junio C Hamano, Mike McCormack, git
On Thu, 9 Mar 2006, Johannes Schindelin wrote:
> >
> > > + if (!memcmp( "imaps:", val, 6 )) {
> > > + if (!memcmp( "imap:", val, 5 ))
> >
> > Is val always longer than 5 or 6 bytes here?
>
> That does not matter, since they are strings, and the memcmp should not
> look further if they are shorter (because the comparison to '\0' failed
> already).
No.
It's true that any sane memcmp() will stop when it notices a difference,
and it's also true that the return value semantics of memcmp() means that
it has to walk beginning-to-end.
HOWEVER. The key phrase is "_when_ it notices a difference".
It's quite common for optimized memcmp()'s to do things like loading
several words from both the source and the destinations, and testing them
together, and only start doing the byte-by-byte comparison when the "big"
comparison has failed.
So when you do a
if (!memcmp(string, mystring, mystringlength))
...
it's entirely possible that it will load bytes from "string" _past_ the
end of the string because of an unrolled inner loop that does things
multiple bytes at a time. They won't be used in the eventual result, but
just the fact that they are loaded from memory can mean that your program
takes a SIGSEGV, for example, becaue it turns out "string" was just a
single NUL byte at the end of a page, and there's nothing after it.
IOW, it's a bad optimization.
Use "strncmp()" instead. Yes, it can be slower, exactly because it has to
check more, but it checks more exactly because memcmp() can cause
undefined behaviour by running off the end of a string.
Linus
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 16:41 ` Linus Torvalds
@ 2006-03-09 18:09 ` Junio C Hamano
2006-03-09 18:21 ` Linus Torvalds
0 siblings, 1 reply; 13+ messages in thread
From: Junio C Hamano @ 2006-03-09 18:09 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git, Mike McCormack
Linus Torvalds <torvalds@osdl.org> writes:
> it's entirely possible that it will load bytes from "string" _past_ the
> end of the string because of an unrolled inner loop that does things
> multiple bytes at a time. They won't be used in the eventual result, but
> just the fact that they are loaded from memory can mean that your program
> takes a SIGSEGV, for example, becaue it turns out "string" was just a
> single NUL byte at the end of a page, and there's nothing after it.
Funny. I've seen this exact bug in memcmp and strcmp in earlier
SunOS (pre Solaris) libc when I was working on something like
Valgrind in my previous life.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 18:09 ` Junio C Hamano
@ 2006-03-09 18:21 ` Linus Torvalds
2006-03-09 18:49 ` Junio C Hamano
0 siblings, 1 reply; 13+ messages in thread
From: Linus Torvalds @ 2006-03-09 18:21 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Mike McCormack
On Thu, 9 Mar 2006, Junio C Hamano wrote:
>
> Linus Torvalds <torvalds@osdl.org> writes:
>
> > it's entirely possible that it will load bytes from "string" _past_ the
> > end of the string because of an unrolled inner loop that does things
> > multiple bytes at a time. They won't be used in the eventual result, but
> > just the fact that they are loaded from memory can mean that your program
> > takes a SIGSEGV, for example, becaue it turns out "string" was just a
> > single NUL byte at the end of a page, and there's nothing after it.
>
> Funny. I've seen this exact bug in memcmp and strcmp in earlier
> SunOS (pre Solaris) libc when I was working on something like
> Valgrind in my previous life.
Well, Valgrind actually can complain for no good reason.
System libraries often take advantage of knowing how the CPU and the
system memory layout works. For example, just from knowing that pages are
always aligned to a certain (largish) boundary, you can know that it's
perfectly safe to do certain optimizations and prefetch past the end of an
object, as long as it's in the same page (and the easiest way to verify
that is to just do it when something is aligned).
So Valgrind will sometimes complain about perfectly bug-free code, just
because the bug-free code accesses outside the "strictly allowable"
region because it knows it can.
Valgrind can be taught about system libraries like that, but especially if
it's an early port to a new architecture or OS, valgrind will often
complain unnecessarily.
Linus
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 18:21 ` Linus Torvalds
@ 2006-03-09 18:49 ` Junio C Hamano
0 siblings, 0 replies; 13+ messages in thread
From: Junio C Hamano @ 2006-03-09 18:49 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
Linus Torvalds <torvalds@osdl.org> writes:
>> > ... They won't be used in the eventual result, but
>> > just the fact that they are loaded from memory can mean that your program
>> > takes a SIGSEGV, for example, becaue it turns out "string" was just a
>> > single NUL byte at the end of a page, and there's nothing after it.
>>
>> Funny. I've seen this exact bug in memcmp and strcmp in earlier
>> SunOS (pre Solaris) libc when I was working on something like
>> Valgrind in my previous life.
>
> Well, Valgrind actually can complain for no good reason.
To clarify, it was _not_ Valgrind I was mucking with. And the
optimization used in the system library was wrong -- it stepped
over the page boundary without checking. I found that it was
fixed in later releases (that was all before Sun started
calling their system Solaris).
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 13:47 ` Johannes Schindelin
@ 2006-03-10 0:38 ` Mark Wooding
0 siblings, 0 replies; 13+ messages in thread
From: Mark Wooding @ 2006-03-10 0:38 UTC (permalink / raw)
To: git
Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> I would be astonished if memcmp has to be timing proof for *all*
> applications, just to keep crypto people happy. I don't *want* a
> slow-down in my super-duper 3d shooter.
Oh, obviously that would be lunatic! ;-) Just making the point that it's
not /completely/ obvious that early stopping is what everyone wants...
-- [mdw]
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH] Add git-imap-send.
2006-03-09 10:35 ` Junio C Hamano
2006-03-09 11:30 ` Johannes Schindelin
@ 2006-03-10 4:58 ` Mike McCormack
1 sibling, 0 replies; 13+ messages in thread
From: Mike McCormack @ 2006-03-10 4:58 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
Junio C Hamano wrote:
>>[imap]
>> Host = imap.server.com
>> User = bob
>> Password = pwd
>> Port = 143
>
> These I am not so sure. It _might_ make sense to have something
> like this under $HOME/. Isn't there an established convention
> for storing something like this for existing MUAs?
It seems better to keep all the configuration in the same place. It's
possible (although unlikely) that somebody uses two different IMAP
servers to send mail... maybe one for work and one for play?
I think it would also be good to be able to specify these as command
line options, as some people might be uncomfortable with having their
password in a file (though it would be better to use an ssh tunnel).
>>+ * As a special exception, mbsync may be linked with the OpenSSL library,
>>+ * despite that library's more restrictive license.
> Hmmm.
I'll remove this, and all the SSL specific code, as it also complicates
the Makefile and adds an extra dependency.
I've fixed all the other issues you pointed out, and will resend the patch.
Mike
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2006-03-10 5:02 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-03-06 13:09 [PATCH] Add git-imap-send Mike McCormack
2006-03-09 10:35 ` Junio C Hamano
2006-03-09 11:30 ` Johannes Schindelin
2006-03-09 11:39 ` Andreas Ericsson
2006-03-09 11:44 ` Johannes Schindelin
2006-03-09 13:26 ` Mark Wooding
2006-03-09 13:47 ` Johannes Schindelin
2006-03-10 0:38 ` Mark Wooding
2006-03-09 16:41 ` Linus Torvalds
2006-03-09 18:09 ` Junio C Hamano
2006-03-09 18:21 ` Linus Torvalds
2006-03-09 18:49 ` Junio C Hamano
2006-03-10 4:58 ` Mike McCormack
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).