commit ef413490357b55ed16ab795f05e337c4d6497df4
parent d1ee94f02cb84a43a95b0411368e1effca6bd877
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date: Sun, 23 Jan 2011 13:43:00 +0100
move socket code to a separate file
this makes the layering more obvious
Diffstat:
M | src/Makefile.am | | | 2 | +- |
M | src/drv_imap.c | | | 541 | ++++--------------------------------------------------------------------------- |
M | src/isync.h | | | 50 | ++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/socket.c | | | 514 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 590 insertions(+), 517 deletions(-)
diff --git a/src/Makefile.am b/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
diff --git a/src/drv_imap.c b/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 );
@@ -1370,87 +1019,6 @@ imap_cleanup_p2( imap_store_t *ctx,
#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 )
{
imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
@@ -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
diff --git a/src/isync.h b/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
diff --git a/src/socket.c b/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