Просмотр исходного кода

move socket code to a separate file

this makes the layering more obvious
Oswald Buddenhagen 14 лет назад
Родитель
Сommit
ef41349035
4 измененных файлов с 590 добавлено и 517 удалено
  1. 1 1
      src/Makefile.am
  2. 25 516
      src/drv_imap.c
  3. 50 0
      src/isync.h
  4. 514 0
      src/socket.c

+ 1 - 1
src/Makefile.am

@@ -5,7 +5,7 @@ SUBDIRS = $(compat_dir)
 
 bin_PROGRAMS = mbsync mdconvert
 
-mbsync_SOURCES = main.c sync.c config.c util.c drv_imap.c drv_maildir.c
+mbsync_SOURCES = main.c sync.c config.c util.c socket.c drv_imap.c drv_maildir.c
 mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS)
 noinst_HEADERS = isync.h
 

+ 25 - 516
src/drv_imap.c

@@ -22,21 +22,10 @@
  * despite that library's more restrictive license.
  */
 
-/* This must come before isync.h to avoid our #define S messing up
- * blowfish.h on MacOS X. */
-#include <config.h>
-#ifdef HAVE_LIBSSL
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-# include <openssl/hmac.h>
-#endif
-
 #include "isync.h"
 
 #include <assert.h>
 #include <unistd.h>
-#include <sys/mman.h>
-#include <sys/time.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <stddef.h>
@@ -44,33 +33,16 @@
 #include <errno.h>
 #include <string.h>
 #include <ctype.h>
-#include <sys/socket.h>
-#include <sys/ioctl.h>
-#ifdef HAVE_SYS_FILIO_H
-# include <sys/filio.h>
-#endif
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <netdb.h>
 
 typedef struct imap_server_conf {
 	struct imap_server_conf *next;
 	char *name;
-	char *tunnel;
-	char *host;
-	int port;
+	server_conf_t sconf;
 	char *user;
 	char *pass;
 #ifdef 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;
-	X509_STORE *cert_store;
 #endif
 } imap_server_conf_t;
 
@@ -94,20 +66,6 @@ typedef struct _list {
 	int len;
 } list_t;
 
-typedef struct {
-	int fd;
-#ifdef HAVE_LIBSSL
-	SSL *ssl;
-#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? */
 
@@ -124,9 +82,6 @@ typedef struct imap_store {
 	/* command queue */
 	int nexttag, num_in_progress, literal_pending;
 	struct imap_cmd *in_progress, **in_progress_append;
-#ifdef HAVE_LIBSSL
-	SSL_CTX *SSLContext;
-#endif
 
 	/* Used during sequential operations like connect */
 	enum { GreetingPending = 0, GreetingBad, GreetingOk, GreetingPreauth } greeting;
@@ -231,305 +186,6 @@ static const char *Flags[] = {
 	"Deleted",
 };
 
-#ifdef HAVE_LIBSSL
-/* Some of this code is inspired by / lifted from mutt. */
-
-static int
-compare_certificates( X509 *cert, X509 *peercert,
-                      unsigned char *peermd, unsigned peermdlen )
-{
-	unsigned char md[EVP_MAX_MD_SIZE];
-	unsigned mdlen;
-
-	/* Avoid CPU-intensive digest calculation if the certificates are
-	 * not even remotely equal. */
-	if (X509_subject_name_cmp( cert, peercert ) ||
-	    X509_issuer_name_cmp( cert, peercert ))
-		return -1;
-
-	if (!X509_digest( cert, EVP_sha1(), md, &mdlen ) ||
-	    peermdlen != mdlen || memcmp( peermd, md, mdlen ))
-		return -1;
-
-	return 0;
-}
-
-#if OPENSSL_VERSION_NUMBER >= 0x00904000L
-#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 )
-#else
-#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0 )
-#endif
-
-/* this gets called when a certificate is to be verified */
-static int
-verify_cert( imap_store_t *ctx )
-{
-	imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
-	SSL *ssl = ctx->buf.sock.ssl;
-	X509 *cert, *lcert;
-	BIO *bio;
-	FILE *fp;
-	int err;
-	unsigned n, i;
-	X509_STORE_CTX xsc;
-	char buf[256];
-	unsigned char md[EVP_MAX_MD_SIZE];
-
-	cert = SSL_get_peer_certificate( ssl );
-	if (!cert) {
-		error( "Error, no server certificate\n" );
-		return -1;
-	}
-
-	while (srvc->cert_file) { // So break works
-		if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) {
-			error( "Server certificate is not yet valid" );
-			break;
-		}
-		if (X509_cmp_current_time( X509_get_notAfter( cert )) <= 0) {
-			error( "Server certificate has expired" );
-			break;
-		}
-		if (!X509_digest( cert, EVP_sha1(), md, &n )) {
-			error( "*** Unable to calculate digest\n" );
-			break;
-		}
-		if (!(fp = fopen( srvc->cert_file, "rt" ))) {
-			error( "Unable to load CertificateFile '%s': %s\n",
-			       srvc->cert_file, strerror( errno ) );
-			return 0;
-		}
-		err = -1;
-		for (lcert = 0; READ_X509_KEY( fp, &lcert ); )
-			if (!(err = compare_certificates( lcert, cert, md, n )))
-				break;
-		X509_free( lcert );
-		fclose( fp );
-		if (!err)
-			return 0;
-		break;
-	}
-
-	if (!srvc->cert_store) {
-		if (!(srvc->cert_store = X509_STORE_new())) {
-			error( "Error creating certificate store\n" );
-			return -1;
-		}
-		if (!X509_STORE_set_default_paths( srvc->cert_store ))
-			warn( "Error while loading default certificate files: %s\n",
-			      ERR_error_string( ERR_get_error(), 0 ) );
-		if (!srvc->cert_file) {
-			info( "Note: CertificateFile not defined\n" );
-		} else if (!X509_STORE_load_locations( srvc->cert_store, srvc->cert_file, 0 )) {
-			error( "Error while loading certificate file '%s': %s\n",
-			       srvc->cert_file, ERR_error_string( ERR_get_error(), 0 ) );
-			return -1;
-		}
-	}
-
-	X509_STORE_CTX_init( &xsc, srvc->cert_store, cert, 0 );
-	err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc );
-	X509_STORE_CTX_cleanup( &xsc );
-	if (!err)
-		return 0;
-	error( "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 );
-	if (!X509_digest( cert, EVP_md5(), md, &n )) {
-		error( "*** Unable to calculate fingerprint\n" );
-	} else {
-		info( "Fingerprint: " );
-		for (i = 0; i < n; i += 2)
-			info( "%02X%02X ", md[i], md[i + 1] );
-		info( "\n" );
-	}
-
-	fputs( "\nAccept certificate? [y/N]: ",  stderr );
-	if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y'))
-		return 0;
-	return -1;
-}
-
-static int
-init_ssl_ctx( imap_store_t *ctx )
-{
-	imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
-	const 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();
-	ctx->SSLContext = SSL_CTX_new( method );
-
-	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( ctx->SSLContext, options );
-
-	/* we check the result of the verification after SSL_connect() */
-	SSL_CTX_set_verify( ctx->SSLContext, SSL_VERIFY_NONE, 0 );
-	return 0;
-}
-#endif /* HAVE_LIBSSL */
-
-static void
-socket_perror( const char *func, Socket_t *sock, int ret )
-{
-#ifdef HAVE_LIBSSL
-	int err;
-
-	if (sock->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)
-					error( "SSL_%s: got EOF\n", func );
-				else
-					error( "SSL_%s: %s\n", func, strerror(errno) );
-			} else
-				error( "SSL_%s: %s\n", func, ERR_error_string( err, 0 ) );
-			return;
-		default:
-			error( "SSL_%s: unhandled SSL error %d\n", func, err );
-			break;
-		}
-		return;
-	}
-#else
-	(void)sock;
-#endif
-	if (ret < 0)
-		perror( func );
-	else
-		error( "%s: unexpected EOF\n", func );
-}
-
-static int
-socket_read( Socket_t *sock, char *buf, int len )
-{
-	int n;
-
-	assert( sock->fd >= 0 );
-	n =
-#ifdef HAVE_LIBSSL
-		sock->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;
-
-	assert( sock->fd >= 0 );
-	n =
-#ifdef HAVE_LIBSSL
-		sock->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;
-}
-
-static int
-socket_pending( Socket_t *sock )
-{
-	int num = -1;
-
-	if (ioctl( sock->fd, FIONREAD, &num ) < 0)
-		return -1;
-	if (num > 0)
-		return num;
-#ifdef HAVE_LIBSSL
-	if (sock->ssl)
-		return SSL_pending( sock->ssl );
-#endif
-	return 0;
-}
-
-/* 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)
-					memmove( b->buf, b->buf + start, n );
-				b->offset -= start;
-				b->bytes = n;
-				start = 0;
-			}
-
-			n = socket_read( &b->sock, b->buf + b->bytes,
-			                 sizeof(b->buf) - b->bytes );
-
-			if (n <= 0)
-				return -1;
-
-			b->bytes += n;
-		}
-
-		if (b->buf[b->offset] == '\r') {
-			assert( b->offset + 1 < b->bytes );
-			if (b->buf[b->offset + 1] == '\n') {
-				b->buf[b->offset] = 0;  /* terminate the string */
-				b->offset += 2; /* next line */
-				if (DFlags & VERBOSE)
-					puts( *s );
-				return 0;
-			}
-		}
-
-		b->offset++;
-	}
-	/* not reached */
-}
-
 static struct imap_cmd *
 new_imap_cmd( int size )
 {
@@ -1270,16 +926,9 @@ imap_cancel_store( store_t *gctx )
 {
 	imap_store_t *ctx = (imap_store_t *)gctx;
 
+	socket_close( &ctx->buf.sock );
 	free_generic_messages( ctx->gen.msgs );
 	free_string_list( ctx->gen.boxes );
-	if (ctx->buf.sock.fd >= 0)
-		close( ctx->buf.sock.fd );
-#ifdef HAVE_LIBSSL
-	if (ctx->buf.sock.ssl)
-		SSL_free( ctx->buf.sock.ssl );
-	if (ctx->SSLContext)
-		SSL_CTX_free( ctx->SSLContext );
-#endif
 	free_list( ctx->ns_personal );
 	free_list( ctx->ns_other );
 	free_list( ctx->ns_shared );
@@ -1369,87 +1018,6 @@ imap_cleanup_p2( imap_store_t *ctx,
 /******************* imap_open_store *******************/
 
 #ifdef HAVE_LIBSSL
-static int
-start_tls( imap_store_t *ctx )
-{
-	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;
-
-	ctx->buf.sock.ssl = SSL_new( ctx->SSLContext );
-	SSL_set_fd( ctx->buf.sock.ssl, ctx->buf.sock.fd );
-	if ((ret = SSL_connect( ctx->buf.sock.ssl )) <= 0) {
-		socket_perror( "connect", &ctx->buf.sock, ret );
-		return 1;
-	}
-
-	/* verify the server certificate */
-	if (verify_cert( ctx ))
-		return 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);
-}
-
-static void
-cram( const char *challenge, const char *user, const char *pass, char **_final, int *_finallen )
-{
-	unsigned char *response, *final;
-	unsigned hashlen;
-	int i, clen, rlen, blen, flen, olen;
-	unsigned char hash[16];
-	char buf[256], hex[33];
-	HMAC_CTX hmac;
-
-	HMAC_Init( &hmac, (unsigned char *)pass, strlen( pass ), EVP_md5() );
-
-	clen = strlen( challenge );
-	/* response will always be smaller than challenge because we are decoding. */
-	response = nfcalloc( 1 + clen );
-	rlen = EVP_DecodeBlock( response, (unsigned char *)challenge, clen );
-	HMAC_Update( &hmac, response, rlen );
-	free( response );
-
-	hashlen = sizeof(hash);
-	HMAC_Final( &hmac, hash, &hashlen );
-	assert( hashlen == sizeof(hash) );
-
-	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 );
-	}
-
-	blen = nfsnprintf( buf, sizeof(buf), "%s %s", user, hex );
-
-	flen = ENCODED_SIZE( blen );
-	final = nfmalloc( flen + 1 );
-	final[flen] = 0;
-	olen = EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, blen );
-	assert( olen == flen );
-
-	*_final = (char *)final;
-	*_finallen = flen;
-}
-
 static int
 do_cram_auth( imap_store_t *ctx, struct imap_cmd *cmdp, const char *prompt )
 {
@@ -1495,9 +1063,6 @@ imap_open_store( store_conf_t *conf,
 	imap_server_conf_t *srvc = cfg->server;
 	imap_store_t *ctx;
 	store_t **ctxp;
-	struct hostent *he;
-	struct sockaddr_in addr;
-	int s, a[2];
 
 	for (ctxp = &unowned; (ctx = (imap_store_t *)*ctxp); ctxp = &ctx->gen.next)
 		if (((imap_store_conf_t *)ctx->gen.conf)->server == srvc) {
@@ -1524,68 +1089,12 @@ imap_open_store( store_conf_t *conf,
 	set_bad_callback( &ctx->gen, (void (*)(void *))imap_open_store_bail, ctx );
 	ctx->in_progress_append = &ctx->in_progress;
 
-	/* open connection to IMAP server */
-	if (srvc->tunnel) {
-		infon( "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, (char *)0 );
-			_exit( 127 );
-		}
-
-		close (a[0]);
-
-		ctx->buf.sock.fd = a[1];
-
-		info( "ok\n" );
-	} else {
-		memset( &addr, 0, sizeof(addr) );
-		addr.sin_port = srvc->port ? htons( srvc->port ) :
-#ifdef HAVE_LIBSSL
-		                srvc->use_imaps ? htons( 993 ) :
-#endif
-		                htons( 143 );
-		addr.sin_family = AF_INET;
-
-		infon( "Resolving %s... ", srvc->host );
-		he = gethostbyname( srvc->host );
-		if (!he) {
-			error( "IMAP error: Cannot resolve server '%s'\n", srvc->host );
-			goto bail;
-		}
-		info( "ok\n" );
-
-		addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
-
-		s = socket( PF_INET, SOCK_STREAM, 0 );
-		if (s < 0) {
-			perror( "socket" );
-			exit( 1 );
-		}
-
-		infon( "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" );
-
-		ctx->buf.sock.fd = s;
-	}
+	if (!socket_connect( &srvc->sconf, &ctx->buf.sock ))
+		goto bail;
 
 #ifdef HAVE_LIBSSL
-	if (srvc->use_imaps) {
-		if (start_tls( ctx )) {
+	if (srvc->sconf.use_imaps) {
+		if (socket_start_tls( &srvc->sconf, &ctx->buf.sock )) {
 			imap_open_store_ssl_bail( ctx );
 			return;
 		}
@@ -1630,7 +1139,8 @@ imap_open_store_authenticate( imap_store_t *ctx )
 		imap_store_conf_t *cfg = (imap_store_conf_t *)ctx->gen.conf;
 		imap_server_conf_t *srvc = cfg->server;
 
-		if (!srvc->use_imaps && (srvc->use_sslv2 || srvc->use_sslv3 || srvc->use_tlsv1)) {
+		if (!srvc->sconf.use_imaps &&
+		    (srvc->sconf.use_sslv2 || srvc->sconf.use_sslv3 || srvc->sconf.use_tlsv1)) {
 			/* always try to select SSL support if available */
 			if (CAP(STARTTLS)) {
 				imap_exec( ctx, 0, imap_open_store_authenticate_p2, "STARTTLS" );
@@ -1658,7 +1168,7 @@ imap_open_store_authenticate_p2( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UN
 {
 	if (response != RESP_OK)
 		imap_open_store_bail( ctx );
-	else if (start_tls( ctx ))
+	else if (socket_start_tls( &((imap_server_conf_t *)ctx->gen.conf)->sconf, &ctx->buf.sock ))
 		imap_open_store_ssl_bail( ctx );
 	else
 		imap_exec( ctx, 0, imap_open_store_authenticate_p3, "CAPABILITY" );
@@ -1797,8 +1307,7 @@ static void
 imap_open_store_ssl_bail( imap_store_t *ctx )
 {
 	/* This avoids that we try to send LOGOUT to an unusable socket. */
-	close( ctx->buf.sock.fd );
-	ctx->buf.sock.fd = -1;
+	socket_close( &ctx->buf.sock );
 	imap_open_store_bail( ctx );
 }
 #endif
@@ -2197,7 +1706,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
 	 * case people forget to turn it on
 	 */
 	server->require_ssl = 1;
-	server->use_tlsv1 = 1;
+	server->sconf.use_tlsv1 = 1;
 #endif
 
 	while (getcline( cfg ) && cfg->cmd) {
@@ -2206,9 +1715,9 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
 #ifdef HAVE_LIBSSL
 			if (!memcmp( "imaps:", cfg->val, 6 )) {
 				cfg->val += 6;
-				server->use_imaps = 1;
-				server->use_sslv2 = 1;
-				server->use_sslv3 = 1;
+				server->sconf.use_imaps = 1;
+				server->sconf.use_sslv2 = 1;
+				server->sconf.use_sslv3 = 1;
 			} else
 #endif
 			{
@@ -2217,37 +1726,37 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
 			}
 			if (!memcmp( "//", cfg->val, 2 ))
 				cfg->val += 2;
-			server->host = nfstrdup( cfg->val );
+			server->sconf.host = nfstrdup( cfg->val );
 		}
 		else if (!strcasecmp( "User", cfg->cmd ))
 			server->user = nfstrdup( cfg->val );
 		else if (!strcasecmp( "Pass", cfg->cmd ))
 			server->pass = nfstrdup( cfg->val );
 		else if (!strcasecmp( "Port", cfg->cmd ))
-			server->port = parse_int( cfg );
+			server->sconf.port = parse_int( cfg );
 #ifdef HAVE_LIBSSL
 		else if (!strcasecmp( "CertificateFile", cfg->cmd )) {
-			server->cert_file = expand_strdup( cfg->val );
-			if (access( server->cert_file, R_OK )) {
+			server->sconf.cert_file = expand_strdup( cfg->val );
+			if (access( server->sconf.cert_file, R_OK )) {
 				error( "%s:%d: CertificateFile '%s': %s\n",
-				       cfg->file, cfg->line, server->cert_file, strerror( errno ) );
+				       cfg->file, cfg->line, server->sconf.cert_file, strerror( errno ) );
 				*err = 1;
 			}
 		} else if (!strcasecmp( "RequireSSL", cfg->cmd ))
 			server->require_ssl = parse_bool( cfg );
 		else if (!strcasecmp( "UseIMAPS", cfg->cmd ))
-			server->use_imaps = parse_bool( cfg );
+			server->sconf.use_imaps = parse_bool( cfg );
 		else if (!strcasecmp( "UseSSLv2", cfg->cmd ))
-			server->use_sslv2 = parse_bool( cfg );
+			server->sconf.use_sslv2 = parse_bool( cfg );
 		else if (!strcasecmp( "UseSSLv3", cfg->cmd ))
-			server->use_sslv3 = parse_bool( cfg );
+			server->sconf.use_sslv3 = parse_bool( cfg );
 		else if (!strcasecmp( "UseTLSv1", cfg->cmd ))
-			server->use_tlsv1 = parse_bool( cfg );
+			server->sconf.use_tlsv1 = parse_bool( cfg );
 		else if (!strcasecmp( "RequireCRAM", cfg->cmd ))
 			server->require_cram = parse_bool( cfg );
 #endif
 		else if (!strcasecmp( "Tunnel", cfg->cmd ))
-			server->tunnel = nfstrdup( cfg->val );
+			server->sconf.tunnel = nfstrdup( cfg->val );
 		else if (store) {
 			if (!strcasecmp( "Account", cfg->cmd )) {
 				for (srv = servers; srv; srv = srv->next)
@@ -2273,7 +1782,7 @@ imap_parse_store( conffile_t *cfg, store_conf_t **storep, int *err )
 		acc_opt = 1;
 	}
 	if (!store || !store->server) {
-		if (!server->tunnel && !server->host) {
+		if (!server->sconf.tunnel && !server->sconf.host) {
 			if (store)
 				error( "IMAP store '%s' has incomplete/missing connection details\n", store->gen.name );
 			else

+ 50 - 0
src/isync.h

@@ -52,6 +52,41 @@
 
 #define EXE "mbsync"
 
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_st X509_STORE;
+
+typedef struct server_conf {
+	char *tunnel;
+	char *host;
+	int port;
+#ifdef HAVE_LIBSSL
+	char *cert_file;
+	unsigned use_imaps:1;
+	unsigned use_sslv2:1;
+	unsigned use_sslv3:1;
+	unsigned use_tlsv1:1;
+
+	/* these are actually variables and are leaked at the end */
+	SSL_CTX *SSLContext;
+	X509_STORE *cert_store;
+#endif
+} server_conf_t;
+
+typedef struct {
+	int fd;
+#ifdef HAVE_LIBSSL
+	SSL *ssl;
+#endif
+} Socket_t;
+
+typedef struct {
+	Socket_t sock;
+	int bytes;
+	int offset;
+	char buf[1024];
+} buffer_t;
+
 typedef struct {
 	const char *file;
 	FILE *fp;
@@ -295,6 +330,21 @@ extern char Hostname[256];
 extern const char *Home;
 
 
+/* socket.c */
+
+int socket_connect( const server_conf_t *conf, Socket_t *sock );
+int socket_start_tls( const server_conf_t *conf, Socket_t *sock );
+void socket_close( Socket_t *sock );
+int socket_read( Socket_t *sock, char *buf, int len );
+int socket_write( Socket_t *sock, char *buf, int len );
+int socket_pending( Socket_t *sock );
+
+int buffer_gets( buffer_t *b, char **s );
+
+void cram( const char *challenge, const char *user, const char *pass,
+           char **_final, int *_finallen );
+
+
 /* util.c */
 
 #define DEBUG        1

+ 514 - 0
src/socket.c

@@ -0,0 +1,514 @@
+/*
+ * mbsync - mailbox synchronizer
+ * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
+ * Copyright (C) 2002-2006,2008,2010,2011 Oswald Buddenhagen <ossi@users.sf.net>
+ * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
+ *
+ *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * As a special exception, mbsync may be linked with the OpenSSL library,
+ * despite that library's more restrictive license.
+ */
+
+/* This must come before isync.h to avoid our #define S messing up
+ * blowfish.h on MacOS X. */
+#include <config.h>
+#ifdef HAVE_LIBSSL
+# include <openssl/ssl.h>
+# include <openssl/err.h>
+# include <openssl/hmac.h>
+#endif
+
+#include "isync.h"
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+static void
+socket_perror( const char *func, Socket_t *sock, int ret )
+{
+#ifdef HAVE_LIBSSL
+	int err;
+
+	if (sock->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)
+					error( "SSL_%s: got EOF\n", func );
+				else
+					error( "SSL_%s: %s\n", func, strerror(errno) );
+			} else
+				error( "SSL_%s: %s\n", func, ERR_error_string( err, 0 ) );
+			return;
+		default:
+			error( "SSL_%s: unhandled SSL error %d\n", func, err );
+			break;
+		}
+		return;
+	}
+#else
+	(void)sock;
+#endif
+	if (ret < 0)
+		perror( func );
+	else
+		error( "%s: unexpected EOF\n", func );
+}
+
+#ifdef HAVE_LIBSSL
+/* Some of this code is inspired by / lifted from mutt. */
+
+static int
+compare_certificates( X509 *cert, X509 *peercert,
+                      unsigned char *peermd, unsigned peermdlen )
+{
+	unsigned char md[EVP_MAX_MD_SIZE];
+	unsigned mdlen;
+
+	/* Avoid CPU-intensive digest calculation if the certificates are
+	 * not even remotely equal. */
+	if (X509_subject_name_cmp( cert, peercert ) ||
+	    X509_issuer_name_cmp( cert, peercert ))
+		return -1;
+
+	if (!X509_digest( cert, EVP_sha1(), md, &mdlen ) ||
+	    peermdlen != mdlen || memcmp( peermd, md, mdlen ))
+		return -1;
+
+	return 0;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x00904000L
+#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0, 0 )
+#else
+#define READ_X509_KEY(fp, key) PEM_read_X509( fp, key, 0 )
+#endif
+
+/* this gets called when a certificate is to be verified */
+static int
+verify_cert( const server_conf_t *conf, Socket_t *sock )
+{
+	server_conf_t *mconf = (server_conf_t *)conf;
+	SSL *ssl = sock->ssl;
+	X509 *cert, *lcert;
+	BIO *bio;
+	FILE *fp;
+	int err;
+	unsigned n, i;
+	X509_STORE_CTX xsc;
+	char buf[256];
+	unsigned char md[EVP_MAX_MD_SIZE];
+
+	cert = SSL_get_peer_certificate( ssl );
+	if (!cert) {
+		error( "Error, no server certificate\n" );
+		return -1;
+	}
+
+	while (conf->cert_file) { /* while() instead of if() so break works */
+		if (X509_cmp_current_time( X509_get_notBefore( cert )) >= 0) {
+			error( "Server certificate is not yet valid" );
+			break;
+		}
+		if (X509_cmp_current_time( X509_get_notAfter( cert )) <= 0) {
+			error( "Server certificate has expired" );
+			break;
+		}
+		if (!X509_digest( cert, EVP_sha1(), md, &n )) {
+			error( "*** Unable to calculate digest\n" );
+			break;
+		}
+		if (!(fp = fopen( conf->cert_file, "rt" ))) {
+			error( "Unable to load CertificateFile '%s': %s\n",
+			       conf->cert_file, strerror( errno ) );
+			return 0;
+		}
+		err = -1;
+		for (lcert = 0; READ_X509_KEY( fp, &lcert ); )
+			if (!(err = compare_certificates( lcert, cert, md, n )))
+				break;
+		X509_free( lcert );
+		fclose( fp );
+		if (!err)
+			return 0;
+		break;
+	}
+
+	if (!mconf->cert_store) {
+		if (!(mconf->cert_store = X509_STORE_new())) {
+			error( "Error creating certificate store\n" );
+			return -1;
+		}
+		if (!X509_STORE_set_default_paths( mconf->cert_store ))
+			warn( "Error while loading default certificate files: %s\n",
+			      ERR_error_string( ERR_get_error(), 0 ) );
+		if (!conf->cert_file) {
+			info( "Note: CertificateFile not defined\n" );
+		} else if (!X509_STORE_load_locations( mconf->cert_store, conf->cert_file, 0 )) {
+			error( "Error while loading certificate file '%s': %s\n",
+			       conf->cert_file, ERR_error_string( ERR_get_error(), 0 ) );
+			return -1;
+		}
+	}
+
+	X509_STORE_CTX_init( &xsc, mconf->cert_store, cert, 0 );
+	err = X509_verify_cert( &xsc ) > 0 ? 0 : X509_STORE_CTX_get_error( &xsc );
+	X509_STORE_CTX_cleanup( &xsc );
+	if (!err)
+		return 0;
+	error( "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 );
+	if (!X509_digest( cert, EVP_md5(), md, &n )) {
+		error( "*** Unable to calculate fingerprint\n" );
+	} else {
+		info( "Fingerprint: " );
+		for (i = 0; i < n; i += 2)
+			info( "%02X%02X ", md[i], md[i + 1] );
+		info( "\n" );
+	}
+
+	fputs( "\nAccept certificate? [y/N]: ",  stderr );
+	if (fgets( buf, sizeof(buf), stdin ) && (buf[0] == 'y' || buf[0] == 'Y'))
+		return 0;
+	return -1;
+}
+
+static int
+init_ssl_ctx( const server_conf_t *conf )
+{
+	server_conf_t *mconf = (server_conf_t *)conf;
+	const SSL_METHOD *method;
+	int options = 0;
+
+	if (conf->use_tlsv1 && !conf->use_sslv2 && !conf->use_sslv3)
+		method = TLSv1_client_method();
+	else
+		method = SSLv23_client_method();
+	mconf->SSLContext = SSL_CTX_new( method );
+
+	if (!conf->use_sslv2)
+		options |= SSL_OP_NO_SSLv2;
+	if (!conf->use_sslv3)
+		options |= SSL_OP_NO_SSLv3;
+	if (!conf->use_tlsv1)
+		options |= SSL_OP_NO_TLSv1;
+
+	SSL_CTX_set_options( mconf->SSLContext, options );
+
+	/* we check the result of the verification after SSL_connect() */
+	SSL_CTX_set_verify( mconf->SSLContext, SSL_VERIFY_NONE, 0 );
+	return 0;
+}
+
+int
+socket_start_tls( const server_conf_t *conf, Socket_t *sock )
+{
+	int ret;
+	static int ssl_inited;
+
+	if (!ssl_inited) {
+		SSL_library_init();
+		SSL_load_error_strings();
+		ssl_inited = 1;
+	}
+
+	if (!conf->SSLContext && init_ssl_ctx( conf ))
+		return 1;
+
+	sock->ssl = SSL_new( ((server_conf_t *)conf)->SSLContext );
+	SSL_set_fd( sock->ssl, sock->fd );
+	if ((ret = SSL_connect( sock->ssl )) <= 0) {
+		socket_perror( "connect", sock, ret );
+		return 1;
+	}
+
+	/* verify the server certificate */
+	if (verify_cert( conf, sock ))
+		return 1;
+
+	info( "Connection is now encrypted\n" );
+	return 0;
+}
+
+#endif /* HAVE_LIBSSL */
+
+int
+socket_connect( const server_conf_t *conf, Socket_t *sock )
+{
+	struct hostent *he;
+	struct sockaddr_in addr;
+	int s, a[2];
+
+	/* open connection to IMAP server */
+	if (conf->tunnel) {
+		infon( "Starting tunnel '%s'... ", conf->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", conf->tunnel, (char *)0 );
+			_exit( 127 );
+		}
+
+		close( a[0] );
+		sock->fd = a[1];
+	} else {
+		memset( &addr, 0, sizeof(addr) );
+		addr.sin_port = conf->port ? htons( conf->port ) :
+#ifdef HAVE_LIBSSL
+		                conf->use_imaps ? htons( 993 ) :
+#endif
+		                htons( 143 );
+		addr.sin_family = AF_INET;
+
+		infon( "Resolving %s... ", conf->host );
+		he = gethostbyname( conf->host );
+		if (!he) {
+			error( "IMAP error: Cannot resolve server '%s'\n", conf->host );
+			return -1;
+		}
+		info( "ok\n" );
+
+		addr.sin_addr.s_addr = *((int *)he->h_addr_list[0]);
+
+		s = socket( PF_INET, SOCK_STREAM, 0 );
+		if (s < 0) {
+			perror( "socket" );
+			exit( 1 );
+		}
+
+		infon( "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" );
+			return -1;
+		}
+
+		sock->fd = s;
+	}
+	info( "ok\n" );
+	return 0;
+}
+
+void
+socket_close( Socket_t *sock )
+{
+	if (sock->fd >= 0) {
+		close( sock->fd );
+		sock->fd = -1;
+	}
+#ifdef HAVE_LIBSSL
+	if (sock->ssl) {
+		SSL_free( sock->ssl );
+		sock->ssl = 0;
+	}
+#endif
+}
+
+int
+socket_read( Socket_t *sock, char *buf, int len )
+{
+	int n;
+
+	assert( sock->fd >= 0 );
+	n =
+#ifdef HAVE_LIBSSL
+		sock->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;
+}
+
+int
+socket_write( Socket_t *sock, char *buf, int len )
+{
+	int n;
+
+	assert( sock->fd >= 0 );
+	n =
+#ifdef HAVE_LIBSSL
+		sock->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;
+}
+
+int
+socket_pending( Socket_t *sock )
+{
+	int num = -1;
+
+	if (ioctl( sock->fd, FIONREAD, &num ) < 0)
+		return -1;
+	if (num > 0)
+		return num;
+#ifdef HAVE_LIBSSL
+	if (sock->ssl)
+		return SSL_pending( sock->ssl );
+#endif
+	return 0;
+}
+
+/* simple line buffering */
+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)
+					memmove( b->buf, b->buf + start, n );
+				b->offset -= start;
+				b->bytes = n;
+				start = 0;
+			}
+
+			n = socket_read( &b->sock, b->buf + b->bytes,
+			                 sizeof(b->buf) - b->bytes );
+
+			if (n <= 0)
+				return -1;
+
+			b->bytes += n;
+		}
+
+		if (b->buf[b->offset] == '\r') {
+			assert( b->offset + 1 < b->bytes );
+			if (b->buf[b->offset + 1] == '\n') {
+				b->buf[b->offset] = 0;  /* terminate the string */
+				b->offset += 2; /* next line */
+				if (DFlags & VERBOSE)
+					puts( *s );
+				return 0;
+			}
+		}
+
+		b->offset++;
+	}
+	/* not reached */
+}
+
+#ifdef HAVE_LIBSSL
+/* this isn't strictly socket code, but let's have all OpenSSL use in one file. */
+
+#define ENCODED_SIZE(n) (4*((n+2)/3))
+
+static char
+hexchar( unsigned int b )
+{
+	if (b < 10)
+		return '0' + b;
+	return 'a' + (b - 10);
+}
+
+void
+cram( const char *challenge, const char *user, const char *pass, char **_final, int *_finallen )
+{
+	unsigned char *response, *final;
+	unsigned hashlen;
+	int i, clen, rlen, blen, flen, olen;
+	unsigned char hash[16];
+	char buf[256], hex[33];
+	HMAC_CTX hmac;
+
+	HMAC_Init( &hmac, (unsigned char *)pass, strlen( pass ), EVP_md5() );
+
+	clen = strlen( challenge );
+	/* response will always be smaller than challenge because we are decoding. */
+	response = nfcalloc( 1 + clen );
+	rlen = EVP_DecodeBlock( response, (unsigned char *)challenge, clen );
+	HMAC_Update( &hmac, response, rlen );
+	free( response );
+
+	hashlen = sizeof(hash);
+	HMAC_Final( &hmac, hash, &hashlen );
+	assert( hashlen == sizeof(hash) );
+
+	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 );
+	}
+
+	blen = nfsnprintf( buf, sizeof(buf), "%s %s", user, hex );
+
+	flen = ENCODED_SIZE( blen );
+	final = nfmalloc( flen + 1 );
+	final[flen] = 0;
+	olen = EVP_EncodeBlock( (unsigned char *)final, (unsigned char *)buf, blen );
+	assert( olen == flen );
+
+	*_final = (char *)final;
+	*_finallen = flen;
+}
+#endif