{"thread":{"id":"14320","subject":"[PATCH 2/3] git-imap-send: Add support for SSL.","startedAt":"2008-07-07T08:05:29Z","lastAt":"2008-07-07T08:05:29Z","messageCount":1,"participants":["Rob Shearman"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"82429","messageId":"1096648c0807070105m7ac27987i9ba9730a9d0dd19a@mail.gmail.com","threadId":"14320","inReplyTo":null,"subject":"[PATCH 2/3] git-imap-send: Add support for SSL.","fromName":"Rob Shearman","fromEmail":"robertshearman@gmail.com","sentAt":"2008-07-07T08:05:29Z","receivedAt":"2008-07-07T08:05:29Z","isPatch":true,"sender":{"key":"robertshearman@gmail.com","avatar":null},"body":"Allow SSL to be used when a imaps:// URL is used for the host name.\n\nAlso, automatically use TLS when not using imaps:// by using the IMAP\nSTARTTLS command, if the server supports it.\n\nTested with Courier and Gimap IMAP servers.\n---\n Documentation/git-imap-send.txt |    5 +-\n Makefile                        |    4 +-\n imap-send.c                     |  166 +++++++++++++++++++++++++++++++++++----\n 3 files changed, 157 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt\nindex b3d8da3..e4a5873 100644\n--- a/Documentation/git-imap-send.txt\n+++ b/Documentation/git-imap-send.txt\n@@ -37,10 +37,11 @@ configuration file (shown with examples):\n     Tunnel = \"ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null\"\n\n [imap]\n-    Host = imap.server.com\n+    Host = imaps://imap.example.com\n     User = bob\n     Pass = pwd\n-    Port = 143\n+    Port = 993\n+    sslverify = false\n ..........................\n\n\ndiff --git a/Makefile b/Makefile\nindex bddd1a7..d9265f7 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1193,7 +1193,9 @@ endif\n git-%$X: %.o $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n\n-git-imap-send$X: imap-send.o $(LIB_FILE)\n+git-imap-send$X: imap-send.o $(GITLIBS)\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)\n\n http.o http-walker.o http-push.o transport.o: http.h\n\ndiff --git a/imap-send.c b/imap-send.c\nindex 89a1532..d138726 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -23,6 +23,12 @@\n  */\n\n #include \"cache.h\"\n+#ifdef NO_OPENSSL\n+typedef void *SSL;\n+#else\n+# include <openssl/ssl.h>\n+# include <openssl/err.h>\n+#endif\n\n typedef struct store_conf {\n \tchar *name;\n@@ -129,6 +135,8 @@ typedef struct imap_server_conf {\n \tint port;\n \tchar *user;\n \tchar *pass;\n+\tint use_ssl;\n+\tint ssl_verify;\n } imap_server_conf_t;\n\n typedef struct imap_store_conf {\n@@ -148,6 +156,7 @@ typedef struct _list {\n\n typedef struct {\n \tint fd;\n+\tSSL *ssl;\n } Socket_t;\n\n typedef struct {\n@@ -201,6 +210,7 @@ enum CAPABILITY {\n \tUIDPLUS,\n \tLITERALPLUS,\n \tNAMESPACE,\n+\tSTARTTLS,\n };\n\n static const char *cap_list[] = {\n@@ -208,6 +218,7 @@ static const char *cap_list[] = {\n \t\"UIDPLUS\",\n \t\"LITERAL+\",\n \t\"NAMESPACE\",\n+\t\"STARTTLS\",\n };\n\n #define RESP_OK    0\n@@ -225,19 +236,101 @@ static const char *Flags[] = {\n \t\"Deleted\",\n };\n\n+#ifndef NO_OPENSSL\n+static void ssl_socket_perror(const char *func)\n+{\n+\tfprintf(stderr, \"%s: %s\\n\", func, ERR_error_string(ERR_get_error(), 0));\n+}\n+#endif\n+\n static void\n socket_perror( const char *func, Socket_t *sock, int ret )\n {\n-\tif (ret < 0)\n-\t\tperror( func );\n+#ifndef NO_OPENSSL\n+\tif (sock->ssl) {\n+\t\tint sslerr = SSL_get_error(sock->ssl, ret);\n+\t\tswitch (sslerr) {\n+\t\tcase SSL_ERROR_NONE:\n+\t\t\tbreak;\n+\t\tcase SSL_ERROR_SYSCALL:\n+\t\t\tperror(\"SSL_connect\");\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tssl_socket_perror(\"SSL_connect\");\n+\t\t\tbreak;\n+\t\t}\n+\t} else\n+#endif\n+\t{\n+\t\tif (ret < 0)\n+\t\t\tperror(func);\n+\t\telse\n+\t\t\tfprintf(stderr, \"%s: unexpected EOF\\n\", func);\n+\t}\n+}\n+\n+static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify)\n+{\n+#ifdef NO_OPENSSL\n+\tfprintf(stderr, \"SSL requested but SSL support not compiled in\\n\");\n+\treturn -1;\n+#else\n+\tSSL_METHOD *meth;\n+\tSSL_CTX *ctx;\n+\tint ret;\n+\n+\tSSL_library_init();\n+\tSSL_load_error_strings();\n+\n+\tif (use_tls_only)\n+\t\tmeth = TLSv1_method();\n \telse\n-\t\tfprintf( stderr, \"%s: unexpected EOF\\n\", func );\n+\t\tmeth = SSLv23_method();\n+\n+\tif (!meth) {\n+\t\tssl_socket_perror(\"SSLv23_method\");\n+\t\treturn -1;\n+\t}\n+\n+\tctx = SSL_CTX_new(meth);\n+\n+\tif (verify)\n+\t\tSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);\n+\n+\tif (!SSL_CTX_set_default_verify_paths(ctx)) {\n+\t\tssl_socket_perror(\"SSL_CTX_set_default_verify_paths\");\n+\t\treturn -1;\n+\t}\n+\tsock->ssl = SSL_new(ctx);\n+\tif (!sock->ssl) {\n+\t\tssl_socket_perror(\"SSL_new\");\n+\t\treturn -1;\n+\t}\n+\tif (!SSL_set_fd(sock->ssl, sock->fd)) {\n+\t\tssl_socket_perror(\"SSL_set_fd\");\n+\t\treturn -1;\n+\t}\n+\n+\tret = SSL_connect(sock->ssl);\n+\tif (ret <= 0) {\n+\t\tsocket_perror(\"SSL_connect\", sock, ret);\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+#endif\n }\n\n static int\n socket_read( Socket_t *sock, char *buf, int len )\n {\n-\tssize_t n = xread( sock->fd, buf, len );\n+\tssize_t n;\n+#ifndef NO_OPENSSL\n+\tif (sock->ssl)\n+\t\tn = SSL_read(sock->ssl, buf, len);\n+\telse\n+#endif\n+\t\tn = xread( sock->fd, buf, len );\n \tif (n <= 0) {\n \t\tsocket_perror( \"read\", sock, n );\n \t\tclose( sock->fd );\n@@ -249,7 +342,13 @@ socket_read( Socket_t *sock, char *buf, int len )\n static int\n socket_write( Socket_t *sock, const char *buf, int len )\n {\n-\tint n = write_in_full( sock->fd, buf, len );\n+\tint n;\n+#ifndef NO_OPENSSL\n+\tif (sock->ssl)\n+\t\tn = SSL_write(sock->ssl, buf, len);\n+\telse\n+#endif\n+\t\tn = write_in_full( sock->fd, buf, len );\n \tif (n != len) {\n \t\tsocket_perror( \"write\", sock, n );\n \t\tclose( sock->fd );\n@@ -258,6 +357,17 @@ socket_write( Socket_t *sock, const char *buf, int len )\n \treturn n;\n }\n\n+static void socket_shutdown(Socket_t *sock)\n+{\n+#ifndef NO_OPENSSL\n+\tif (sock->ssl) {\n+\t\tSSL_shutdown(sock->ssl);\n+\t\tSSL_free(sock->ssl);\n+\t}\n+#endif\n+\tclose(sock->fd);\n+}\n+\n /* simple line buffering */\n static int\n buffer_gets( buffer_t * b, char **s )\n@@ -875,7 +985,7 @@ imap_close_server( imap_store_t *ictx )\n\n \tif (imap->buf.sock.fd != -1) {\n \t\timap_exec( ictx, NULL, \"LOGOUT\" );\n-\t\tclose( imap->buf.sock.fd );\n+\t\tsocket_shutdown( &imap->buf.sock );\n \t}\n \tfree_list( imap->ns_personal );\n \tfree_list( imap->ns_other );\n@@ -906,6 +1016,7 @@ imap_open_store( imap_server_conf_t *srvc )\n\n \tctx->imap = imap = xcalloc( sizeof(*imap), 1 );\n \timap->buf.sock.fd = -1;\n+\timap->buf.sock.ssl = NULL;\n \timap->in_progress_append = &imap->in_progress;\n\n \t/* open connection to IMAP server */\n@@ -958,10 +1069,15 @@ imap_open_store( imap_server_conf_t *srvc )\n \t\t\tperror( \"connect\" );\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_info( \"ok\\n\" );\n-\n+\t\t\n \t\timap->buf.sock.fd = s;\n\n+\t\tif (srvc->use_ssl &&\n+\t\t    ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) {\n+\t\t\tclose(s);\n+\t\t\tgoto bail;\n+\t\t}\n+\t\timap_info( \"ok\\n\" );\n \t}\n\n \t/* read the greeting string */\n@@ -986,7 +1102,18 @@ imap_open_store( imap_server_conf_t *srvc )\n \t\tgoto bail;\n\n \tif (!preauth) {\n-\n+#ifndef NO_OPENSSL\n+\t\tif (!srvc->use_ssl && CAP(STARTTLS)) {\n+\t\t\tif (imap_exec(ctx, 0, \"STARTTLS\") != RESP_OK)\n+\t\t\t\tgoto bail;\n+\t\t\tif (ssl_socket_connect(&imap->buf.sock, 1,\n+\t\t\t\t\t       srvc->ssl_verify))\n+\t\t\t\tgoto bail;\n+\t\t\t/* capabilities may have changed, so get the new capabilities */\n+\t\t\tif (imap_exec(ctx, 0, \"CAPABILITY\") != RESP_OK)\n+\t\t\t\tgoto bail;\n+\t\t}\n+#endif\n \t\timap_info (\"Logging in...\\n\");\n \t\tif (!srvc->user) {\n \t\t\tfprintf( stderr, \"Skipping server %s, no user\\n\", srvc->host );\n@@ -1014,7 +1141,9 @@ imap_open_store( imap_server_conf_t *srvc )\n \t\t\tfprintf( stderr, \"Skipping account %s@%s, server forbids LOGIN\\n\",\nsrvc->user, srvc->host );\n \t\t\tgoto bail;\n \t\t}\n-\t\timap_warn( \"*** IMAP Warning *** Password is being sent in the clear\\n\" );\n+\t\tif (!imap->buf.sock.ssl)\n+\t\t\timap_warn( \"*** IMAP Warning *** Password is being \"\n+\t\t\t\t   \"sent in the clear\\n\" );\n \t\tif (imap_exec( ctx, NULL, \"LOGIN \\\"%s\\\" \\\"%s\\\"\", srvc->user,\nsrvc->pass ) != RESP_OK) {\n \t\t\tfprintf( stderr, \"IMAP error: LOGIN failed\\n\" );\n \t\t\tgoto bail;\n@@ -1242,6 +1371,8 @@ static imap_server_conf_t server =\n \t0,\t/* port */\n \tNULL,\t/* user */\n \tNULL,\t/* pass */\n+\t0,   \t/* use_ssl */\n+\t1,   \t/* ssl_verify */\n };\n\n static char *imap_folder;\n@@ -1262,11 +1393,11 @@ git_imap_config(const char *key, const char\n*val, void *cb)\n \tif (!strcmp( \"folder\", key )) {\n \t\timap_folder = xstrdup( val );\n \t} else if (!strcmp( \"host\", key )) {\n-\t\t{\n-\t\t\tif (!prefixcmp(val, \"imap:\"))\n-\t\t\t\tval += 5;\n-\t\t\tif (!server.port)\n-\t\t\t\tserver.port = 143;\n+\t\tif (!prefixcmp(val, \"imap:\"))\n+\t\t\tval += 5;\n+\t\telse if (!prefixcmp(val, \"imaps:\")) {\n+\t\t\tval += 6;\n+\t\t\tserver.use_ssl = 1;\n \t\t}\n \t\tif (!prefixcmp(val, \"//\"))\n \t\t\tval += 2;\n@@ -1280,6 +1411,8 @@ git_imap_config(const char *key, const char\n*val, void *cb)\n \t\tserver.port = git_config_int( key, val );\n \telse if (!strcmp( \"tunnel\", key ))\n \t\tserver.tunnel = xstrdup( val );\n+\telse if (!strcmp( \"ssl_verify\", key ))\n+\t\tserver.ssl_verify = git_config_bool( key, val );\n \treturn 0;\n }\n\n@@ -1299,6 +1432,9 @@ main(int argc, char **argv)\n \tsetup_git_directory_gently( NULL );\n \tgit_config(git_imap_config, NULL);\n\n+\tif (!server.port)\n+\t\tserver.port = server.use_ssl ? 993 : 143;\n+\n \tif (!imap_folder) {\n \t\tfprintf( stderr, \"no imap store specified\\n\" );\n \t\treturn 1;\n-- \n1.5.6.GIT\n"}]}