git-imap-send: Support SSL
authorRobert Shearman <robertshearman@gmail.com>
Wed, 9 Jul 2008 21:29:00 +0000 (22:29 +0100)
committerJunio C Hamano <gitster@pobox.com>
Sat, 26 Jul 2008 04:51:30 +0000 (21:51 -0700)
Allow SSL to be used when a imaps:// URL is used for the host name.

Also, automatically use TLS when not using imaps:// by using the IMAP
STARTTLS command, if the server supports it.

Tested with Courier and Gimap IMAP servers.

Signed-off-by: Robert Shearman <robertshearman@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-imap-send.txt
Makefile
git-compat-util.h
imap-send.c

index b3d8da33ee64730794821440c287f30c4bb85789..136c82bfdd77a8464483ed0259cec63fe7786c70 100644 (file)
@@ -37,10 +37,11 @@ configuration file (shown with examples):
     Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null"
 
 [imap]
-    Host = imap.server.com
+    Host = imap://imap.example.com
     User = bob
     Pass = pwd
     Port = 143
+    sslverify = false
 ..........................
 
 
index 798a2f2f770fddc59020f8dec60498c3cb724dfe..bb2a9882889f4ea2723b1bde2b0eda1010f39e8b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1208,7 +1208,9 @@ endif
 git-%$X: %.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
-git-imap-send$X: imap-send.o $(LIB_FILE)
+git-imap-send$X: imap-send.o $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+               $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
 
 http.o http-walker.o http-push.o transport.o: http.h
 
index cf89cdf4598b3796724a85aa707f740245155cdc..fbf791a63b95540b3dc9fc94e950acfdf10f12d6 100644 (file)
 #include <iconv.h>
 #endif
 
+#ifndef NO_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
 /* On most systems <limits.h> would have given us this, but
  * not on some systems (e.g. GNU/Hurd).
  */
index 24d76a703161383c69d8984842908a988c54952e..802633494d0223174b140193497b9205fd5a5aa0 100644 (file)
@@ -23,6 +23,9 @@
  */
 
 #include "cache.h"
+#ifdef NO_OPENSSL
+typedef void *SSL;
+#endif
 
 typedef struct store_conf {
        char *name;
@@ -129,6 +132,8 @@ typedef struct imap_server_conf {
        int port;
        char *user;
        char *pass;
+       int use_ssl;
+       int ssl_verify;
 } imap_server_conf_t;
 
 typedef struct imap_store_conf {
@@ -148,6 +153,7 @@ typedef struct _list {
 
 typedef struct {
        int fd;
+       SSL *ssl;
 } Socket_t;
 
 typedef struct {
@@ -201,6 +207,7 @@ enum CAPABILITY {
        UIDPLUS,
        LITERALPLUS,
        NAMESPACE,
+       STARTTLS,
 };
 
 static const char *cap_list[] = {
@@ -208,6 +215,7 @@ static const char *cap_list[] = {
        "UIDPLUS",
        "LITERAL+",
        "NAMESPACE",
+       "STARTTLS",
 };
 
 #define RESP_OK    0
@@ -225,19 +233,101 @@ static const char *Flags[] = {
        "Deleted",
 };
 
+#ifndef NO_OPENSSL
+static void ssl_socket_perror(const char *func)
+{
+       fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), 0));
+}
+#endif
+
 static void
 socket_perror( const char *func, Socket_t *sock, int ret )
 {
-       if (ret < 0)
-               perror( func );
+#ifndef NO_OPENSSL
+       if (sock->ssl) {
+               int sslerr = SSL_get_error(sock->ssl, ret);
+               switch (sslerr) {
+               case SSL_ERROR_NONE:
+                       break;
+               case SSL_ERROR_SYSCALL:
+                       perror("SSL_connect");
+                       break;
+               default:
+                       ssl_socket_perror("SSL_connect");
+                       break;
+               }
+       } else
+#endif
+       {
+               if (ret < 0)
+                       perror(func);
+               else
+                       fprintf(stderr, "%s: unexpected EOF\n", func);
+       }
+}
+
+static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify)
+{
+#ifdef NO_OPENSSL
+       fprintf(stderr, "SSL requested but SSL support not compiled in\n");
+       return -1;
+#else
+       SSL_METHOD *meth;
+       SSL_CTX *ctx;
+       int ret;
+
+       SSL_library_init();
+       SSL_load_error_strings();
+
+       if (use_tls_only)
+               meth = TLSv1_method();
        else
-               fprintf( stderr, "%s: unexpected EOF\n", func );
+               meth = SSLv23_method();
+
+       if (!meth) {
+               ssl_socket_perror("SSLv23_method");
+               return -1;
+       }
+
+       ctx = SSL_CTX_new(meth);
+
+       if (verify)
+               SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
+
+       if (!SSL_CTX_set_default_verify_paths(ctx)) {
+               ssl_socket_perror("SSL_CTX_set_default_verify_paths");
+               return -1;
+       }
+       sock->ssl = SSL_new(ctx);
+       if (!sock->ssl) {
+               ssl_socket_perror("SSL_new");
+               return -1;
+       }
+       if (!SSL_set_fd(sock->ssl, sock->fd)) {
+               ssl_socket_perror("SSL_set_fd");
+               return -1;
+       }
+
+       ret = SSL_connect(sock->ssl);
+       if (ret <= 0) {
+               socket_perror("SSL_connect", sock, ret);
+               return -1;
+       }
+
+       return 0;
+#endif
 }
 
 static int
 socket_read( Socket_t *sock, char *buf, int len )
 {
-       ssize_t n = xread( sock->fd, buf, len );
+       ssize_t n;
+#ifndef NO_OPENSSL
+       if (sock->ssl)
+               n = SSL_read(sock->ssl, buf, len);
+       else
+#endif
+               n = xread( sock->fd, buf, len );
        if (n <= 0) {
                socket_perror( "read", sock, n );
                close( sock->fd );
@@ -249,7 +339,13 @@ socket_read( Socket_t *sock, char *buf, int len )
 static int
 socket_write( Socket_t *sock, const char *buf, int len )
 {
-       int n = write_in_full( sock->fd, buf, len );
+       int n;
+#ifndef NO_OPENSSL
+       if (sock->ssl)
+               n = SSL_write(sock->ssl, buf, len);
+       else
+#endif
+               n = write_in_full( sock->fd, buf, len );
        if (n != len) {
                socket_perror( "write", sock, n );
                close( sock->fd );
@@ -258,6 +354,17 @@ socket_write( Socket_t *sock, const char *buf, int len )
        return n;
 }
 
+static void socket_shutdown(Socket_t *sock)
+{
+#ifndef NO_OPENSSL
+       if (sock->ssl) {
+               SSL_shutdown(sock->ssl);
+               SSL_free(sock->ssl);
+       }
+#endif
+       close(sock->fd);
+}
+
 /* simple line buffering */
 static int
 buffer_gets( buffer_t * b, char **s )
@@ -875,7 +982,7 @@ imap_close_server( imap_store_t *ictx )
 
        if (imap->buf.sock.fd != -1) {
                imap_exec( ictx, NULL, "LOGOUT" );
-               close( imap->buf.sock.fd );
+               socket_shutdown( &imap->buf.sock );
        }
        free_list( imap->ns_personal );
        free_list( imap->ns_other );
@@ -958,10 +1065,15 @@ imap_open_store( imap_server_conf_t *srvc )
                        perror( "connect" );
                        goto bail;
                }
-               imap_info( "ok\n" );
 
                imap->buf.sock.fd = s;
 
+               if (srvc->use_ssl &&
+                   ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {
+                       close(s);
+                       goto bail;
+               }
+               imap_info( "ok\n" );
        }
 
        /* read the greeting string */
@@ -986,7 +1098,18 @@ imap_open_store( imap_server_conf_t *srvc )
                goto bail;
 
        if (!preauth) {
-
+#ifndef NO_OPENSSL
+               if (!srvc->use_ssl && CAP(STARTTLS)) {
+                       if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK)
+                               goto bail;
+                       if (ssl_socket_connect(&imap->buf.sock, 1,
+                                              srvc->ssl_verify))
+                               goto bail;
+                       /* capabilities may have changed, so get the new capabilities */
+                       if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK)
+                               goto bail;
+               }
+#endif
                imap_info ("Logging in...\n");
                if (!srvc->user) {
                        fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
@@ -1014,7 +1137,9 @@ imap_open_store( imap_server_conf_t *srvc )
                        fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
                        goto bail;
                }
-               imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+               if (!imap->buf.sock.ssl)
+                       imap_warn( "*** IMAP Warning *** Password is being "
+                                  "sent in the clear\n" );
                if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
                        fprintf( stderr, "IMAP error: LOGIN failed\n" );
                        goto bail;
@@ -1242,6 +1367,8 @@ static imap_server_conf_t server =
        0,      /* port */
        NULL,   /* user */
        NULL,   /* pass */
+       0,      /* use_ssl */
+       1,      /* ssl_verify */
 };
 
 static char *imap_folder;
@@ -1262,11 +1389,11 @@ git_imap_config(const char *key, const char *val, void *cb)
        if (!strcmp( "folder", key )) {
                imap_folder = xstrdup( val );
        } else if (!strcmp( "host", key )) {
-               {
-                       if (!prefixcmp(val, "imap:"))
-                               val += 5;
-                       if (!server.port)
-                               server.port = 143;
+               if (!prefixcmp(val, "imap:"))
+                       val += 5;
+               else if (!prefixcmp(val, "imaps:")) {
+                       val += 6;
+                       server.use_ssl = 1;
                }
                if (!prefixcmp(val, "//"))
                        val += 2;
@@ -1280,6 +1407,8 @@ git_imap_config(const char *key, const char *val, void *cb)
                server.port = git_config_int( key, val );
        else if (!strcmp( "tunnel", key ))
                server.tunnel = xstrdup( val );
+       else if (!strcmp( "sslverify", key ))
+               server.ssl_verify = git_config_bool( key, val );
        return 0;
 }
 
@@ -1300,6 +1429,9 @@ main(int argc, char **argv)
        setup_git_directory_gently(&nongit_ok);
        git_config(git_imap_config, NULL);
 
+       if (!server.port)
+               server.port = server.use_ssl ? 993 : 143;
+
        if (!imap_folder) {
                fprintf( stderr, "no imap store specified\n" );
                return 1;