diff src/login-common/ssl-proxy-openssl.c @ 8985:f43bebab3dac HEAD

imap/pop3 proxy: Support SSL/TLS connections to remote servers. passdb can return ssl=yes, ssl=any-cert and starttls options.
author Timo Sirainen <tss@iki.fi>
date Tue, 28 Apr 2009 22:31:40 -0400
parents 1c6361f7111d
children d475e17d01a3
line wrap: on
line diff
--- a/src/login-common/ssl-proxy-openssl.c	Tue Apr 28 19:57:10 2009 -0400
+++ b/src/login-common/ssl-proxy-openssl.c	Tue Apr 28 22:31:40 2009 -0400
@@ -49,11 +49,15 @@
 	unsigned char sslout_buf[1024];
 	unsigned int sslout_size;
 
+	ssl_handshake_callback_t *handshake_callback;
+	void *handshake_callback_context;
+
 	char *last_error;
 	unsigned int handshaked:1;
 	unsigned int destroyed:1;
 	unsigned int cert_received:1;
 	unsigned int cert_broken:1;
+	unsigned int client:1;
 };
 
 struct ssl_parameters {
@@ -65,7 +69,8 @@
 };
 
 static int extdata_index;
-static SSL_CTX *ssl_ctx;
+static SSL_CTX *ssl_server_ctx;
+static SSL_CTX *ssl_client_ctx;
 static unsigned int ssl_proxy_count;
 static struct ssl_proxy *ssl_proxies;
 static struct ssl_parameters ssl_params;
@@ -396,16 +401,27 @@
 {
 	int ret;
 
-	ret = SSL_accept(proxy->ssl);
-	if (ret != 1)
-		ssl_handle_error(proxy, ret, "SSL_accept()");
-	else {
-		i_free_and_null(proxy->last_error);
-		proxy->handshaked = TRUE;
+	if (proxy->client) {
+		ret = SSL_connect(proxy->ssl);
+		if (ret != 1) {
+			ssl_handle_error(proxy, ret, "SSL_connect()");
+			return;
+		}
+	} else {
+		ret = SSL_accept(proxy->ssl);
+		if (ret != 1) {
+			ssl_handle_error(proxy, ret, "SSL_accept()");
+			return;
+		}
+	}
+	i_free_and_null(proxy->last_error);
+	proxy->handshaked = TRUE;
 
-		ssl_set_io(proxy, SSL_ADD_INPUT);
-		plain_block_input(proxy, FALSE);
-	}
+	ssl_set_io(proxy, SSL_ADD_INPUT);
+	plain_block_input(proxy, FALSE);
+
+	if (proxy->handshake_callback(proxy->handshake_callback_context) < 0)
+		ssl_proxy_destroy(proxy);
 }
 
 static void ssl_read(struct ssl_proxy *proxy)
@@ -473,7 +489,8 @@
 	ssl_proxy_unref(proxy);
 }
 
-int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r)
+static int ssl_proxy_new_common(SSL_CTX *ssl_ctx, int fd, struct ip_addr *ip,
+				struct ssl_proxy **proxy_r)
 {
 	struct ssl_proxy *proxy;
 	SSL *ssl;
@@ -523,13 +540,39 @@
 	ssl_proxy_count++;
 	DLLIST_PREPEND(&ssl_proxies, proxy);
 
-	ssl_step(proxy);
 	main_ref();
 
 	*proxy_r = proxy;
 	return sfd[1];
 }
 
+int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r)
+{
+	int ret;
+
+	if ((ret = ssl_proxy_new_common(ssl_server_ctx, fd, ip, proxy_r)) < 0)
+		return -1;
+
+	ssl_step(*proxy_r);
+	return ret;
+}
+
+int ssl_proxy_client_new(int fd, struct ip_addr *ip,
+			 ssl_handshake_callback_t *callback, void *context,
+			 struct ssl_proxy **proxy_r)
+{
+	int ret;
+
+	if ((ret = ssl_proxy_new_common(ssl_client_ctx, fd, ip, proxy_r)) < 0)
+		return -1;
+
+	(*proxy_r)->handshake_callback = callback;
+	(*proxy_r)->handshake_callback_context = context;
+	(*proxy_r)->client = TRUE;
+	ssl_step(*proxy_r);
+	return ret;
+}
+
 bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy)
 {
 	return proxy->cert_received && !proxy->cert_broken;
@@ -737,53 +780,63 @@
 	return strstr(buf, "PRIVATE KEY---") != NULL;
 }
 
-void ssl_proxy_init(void)
+static void ssl_proxy_ctx_init(SSL_CTX *ssl_ctx)
 {
-	static char dovecot[] = "dovecot";
-	const char *cafile, *certfile, *keyfile, *cipher_list, *username_field;
-	char *password;
-	unsigned char buf;
-	unsigned long err;
-
-	memset(&ssl_params, 0, sizeof(ssl_params));
-
-	cafile = getenv("SSL_CA_FILE");
-	certfile = getenv("SSL_CERT_FILE");
-	keyfile = getenv("SSL_KEY_FILE");
-	ssl_params.fname = getenv("SSL_PARAM_FILE");
-	password = getenv("SSL_KEY_PASSWORD");
-
-	if (certfile == NULL || keyfile == NULL || ssl_params.fname == NULL) {
-		/* SSL support is disabled */
-		return;
-	}
-
-	SSL_library_init();
-	SSL_load_error_strings();
-
-	extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
-
-	if ((ssl_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL)
-		i_fatal("SSL_CTX_new() failed");
+	const char *cafile;
 
 	SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
 
-	cipher_list = getenv("SSL_CIPHER_LIST");
-	if (cipher_list == NULL)
-		cipher_list = DOVECOT_SSL_DEFAULT_CIPHER_LIST;
-	if (SSL_CTX_set_cipher_list(ssl_ctx, cipher_list) != 1) {
-		i_fatal("Can't set cipher list to '%s': %s",
-			cipher_list, ssl_last_error());
-	}
-
+	cafile = getenv("SSL_CA_FILE");
 	if (cafile != NULL) {
 		if (SSL_CTX_load_verify_locations(ssl_ctx, cafile, NULL) != 1) {
 			i_fatal("Can't load CA file %s: %s",
 				cafile, ssl_last_error());
 		}
 	}
+	if (verbose_ssl)
+		SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
+	if (SSL_CTX_need_tmp_RSA(ssl_ctx))
+		SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
+	SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
+}
 
-	if (SSL_CTX_use_certificate_chain_file(ssl_ctx, certfile) != 1) {
+static void ssl_proxy_ctx_verify_client(SSL_CTX *ssl_ctx)
+{
+	const char *cafile;
+#if OPENSSL_VERSION_NUMBER >= 0x00907000L
+	X509_STORE *store;
+
+	store = SSL_CTX_get_cert_store(ssl_ctx);
+	X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
+			     X509_V_FLAG_CRL_CHECK_ALL);
+#endif
+	cafile = getenv("SSL_CA_FILE");
+	SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE,
+			   ssl_verify_client_cert);
+	SSL_CTX_set_client_CA_list(ssl_ctx, SSL_load_client_CA_file(cafile));
+}
+
+static void ssl_proxy_init_server(const char *certfile, const char *keyfile)
+{
+	const char *cipher_list, *username_field;
+	char *password;
+	unsigned long err;
+
+	password = getenv("SSL_KEY_PASSWORD");
+
+	if ((ssl_server_ctx = SSL_CTX_new(SSLv23_server_method())) == NULL)
+		i_fatal("SSL_CTX_new() failed");
+	ssl_proxy_ctx_init(ssl_server_ctx);
+
+	cipher_list = getenv("SSL_CIPHER_LIST");
+	if (cipher_list == NULL)
+		cipher_list = DOVECOT_SSL_DEFAULT_CIPHER_LIST;
+	if (SSL_CTX_set_cipher_list(ssl_server_ctx, cipher_list) != 1) {
+		i_fatal("Can't set cipher list to '%s': %s",
+			cipher_list, ssl_last_error());
+	}
+
+	if (SSL_CTX_use_certificate_chain_file(ssl_server_ctx, certfile) != 1) {
 		err = ERR_peek_error();
 		if (ERR_GET_LIB(err) != ERR_LIB_PEM ||
 		    ERR_GET_REASON(err) != PEM_R_NO_START_LINE) {
@@ -801,35 +854,16 @@
 		}
 	}
 
-        SSL_CTX_set_default_passwd_cb(ssl_ctx, pem_password_callback);
-        SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, password);
-	if (SSL_CTX_use_PrivateKey_file(ssl_ctx, keyfile,
+        SSL_CTX_set_default_passwd_cb(ssl_server_ctx, pem_password_callback);
+        SSL_CTX_set_default_passwd_cb_userdata(ssl_server_ctx, password);
+	if (SSL_CTX_use_PrivateKey_file(ssl_server_ctx, keyfile,
 					SSL_FILETYPE_PEM) != 1) {
 		i_fatal("Can't load private key file %s: %s",
 			keyfile, ssl_last_error());
 	}
 
-	if (SSL_CTX_need_tmp_RSA(ssl_ctx))
-		SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
-	SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
-
-	if (verbose_ssl)
-		SSL_CTX_set_info_callback(ssl_ctx, ssl_info_callback);
-
-	if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) {
-#if OPENSSL_VERSION_NUMBER >= 0x00907000L
-		X509_STORE *store;
-
-		store = SSL_CTX_get_cert_store(ssl_ctx);
-		X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
-				     X509_V_FLAG_CRL_CHECK_ALL);
-#endif
-		SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER |
-				   SSL_VERIFY_CLIENT_ONCE,
-				   ssl_verify_client_cert);
-		SSL_CTX_set_client_CA_list(ssl_ctx,
-					   SSL_load_client_CA_file(cafile));
-	}
+	if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL)
+		ssl_proxy_ctx_verify_client(ssl_server_ctx);
 
 	username_field = getenv("SSL_CERT_USERNAME_FIELD");
 	if (username_field == NULL)
@@ -841,6 +875,39 @@
 				username_field);
 		}
 	}
+}
+
+static void ssl_proxy_init_client(void)
+{
+	if ((ssl_client_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL)
+		i_fatal("SSL_CTX_new() failed");
+	ssl_proxy_ctx_init(ssl_client_ctx);
+	ssl_proxy_ctx_verify_client(ssl_client_ctx);
+}
+
+void ssl_proxy_init(void)
+{
+	static char dovecot[] = "dovecot";
+	const char *certfile, *keyfile;
+	unsigned char buf;
+
+	memset(&ssl_params, 0, sizeof(ssl_params));
+
+	certfile = getenv("SSL_CERT_FILE");
+	keyfile = getenv("SSL_KEY_FILE");
+	ssl_params.fname = getenv("SSL_PARAM_FILE");
+
+	if (certfile == NULL || keyfile == NULL || ssl_params.fname == NULL) {
+		/* SSL support is disabled */
+		return;
+	}
+
+	SSL_library_init();
+	SSL_load_error_strings();
+
+	extdata_index = SSL_get_ex_new_index(0, dovecot, NULL, NULL, NULL);
+	ssl_proxy_init_server(certfile, keyfile);
+	ssl_proxy_init_client();
 
 	/* PRNG initialization might want to use /dev/urandom, make sure it
 	   does it before chrooting. We might not have enough entropy at
@@ -862,7 +929,8 @@
 		ssl_proxy_destroy(ssl_proxies);
 
 	ssl_free_parameters(&ssl_params);
-	SSL_CTX_free(ssl_ctx);
+	SSL_CTX_free(ssl_server_ctx);
+	SSL_CTX_free(ssl_client_ctx);
 	EVP_cleanup();
 	ERR_free_strings();
 }