changeset 2027:dc5d0da1abe9 HEAD

Added ssl_require_client_cert auth-specific setting. Hide ssl_verify_client_cert from default config file as it's automatically set if needed and there's not much point in forcing it.
author Timo Sirainen <tss@iki.fi>
date Mon, 17 May 2004 04:32:16 +0300
parents 53585aa87f9c
children ec22548b6124
files dovecot-example.conf src/auth/auth-client-interface.h src/auth/mech.c src/auth/mech.h src/imap-login/client-authenticate.c src/imap-login/client.c src/lib-auth/auth-client.h src/lib-auth/auth-server-request.c src/login-common/client-common.h src/login-common/main.c src/login-common/ssl-proxy-openssl.c src/login-common/ssl-proxy.h src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h src/pop3-login/client-authenticate.c src/pop3-login/client.c
diffstat 17 files changed, 144 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Mon May 17 02:29:27 2004 +0300
+++ b/dovecot-example.conf	Mon May 17 04:32:16 2004 +0300
@@ -37,7 +37,7 @@
 # File containing trusted SSL certificate authorities. Usually not needed.
 #ssl_ca_file = 
 
-# Require client to send a valid certificate, otherwise fail the SSL handshake.
+# Request client to send a certificate.
 #ssl_verify_client_cert = no
 
 # SSL parameter file. Master process generates this file for login processes.
@@ -312,10 +312,9 @@
 #umask = 0077
 
 # Drop all privileges before exec()ing the mail process. This is mostly
-# meant for debugging, otherwise you don't get core dumps. Note that setting
-# this to yes means that log file is opened as the logged in user, which
-# might not work. It could also be a small security risk if you use single UID
-# for multiple users, as the users could ptrace() each others processes then.
+# meant for debugging, otherwise you don't get core dumps. It could be a small
+# security risk if you use single UID for multiple users, as the users could
+# ptrace() each others processes then.
 #mail_drop_priv_before_exec = no
 
 # Set max. process size in megabytes. Most of the memory goes to mmap()ing
@@ -437,6 +436,9 @@
 
   # Number of authentication processes to create
   #count = 1
+
+  # Require a valid SSL client certificate or the authentication fails.
+  #ssl_require_client_cert = no
 }
 
 # PAM doesn't provide a way to get uid, gid or home directory. If you don't
--- a/src/auth/auth-client-interface.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/auth/auth-client-interface.h	Mon May 17 04:32:16 2004 +0300
@@ -22,6 +22,10 @@
 	AUTH_PROTOCOL_POP3	= 0x02
 };
 
+enum auth_client_request_new_flags {
+	AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT = 0x01
+};
+
 enum auth_client_request_type {
 	AUTH_CLIENT_REQUEST_NEW = 1,
         AUTH_CLIENT_REQUEST_CONTINUE
@@ -51,6 +55,7 @@
 
 	enum auth_mech mech;
 	enum auth_protocol protocol;
+        enum auth_client_request_new_flags flags;
 };
 
 /* Continue authentication request */
--- a/src/auth/mech.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/auth/mech.c	Mon May 17 04:32:16 2004 +0300
@@ -22,6 +22,7 @@
 char username_chars[256];
 
 static int set_use_cyrus_sasl;
+static int ssl_require_client_cert;
 static struct mech_module_list *mech_modules;
 static struct auth_client_request_reply failure_reply;
 
@@ -75,6 +76,16 @@
 		return;
 	}
 
+	if (ssl_require_client_cert &&
+	    (request->flags & AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT) == 0) {
+		/* we fail without valid certificate */
+		if (verbose)
+			i_info("Client didn't present valid SSL certificate");
+		failure_reply.id = request->id;
+		callback(&failure_reply, NULL, conn);
+		return;
+	}
+
 #ifdef USE_CYRUS_SASL2
 	if (set_use_cyrus_sasl) {
 		auth_request = mech_cyrus_sasl_new(conn, request, callback);
@@ -291,6 +302,7 @@
 	if (set_use_cyrus_sasl)
 		mech_cyrus_sasl_init_lib();
 #endif
+        ssl_require_client_cert = getenv("SSL_REQUIRE_CLIENT_CERT") != NULL;
 }
 
 void mech_deinit(void)
--- a/src/auth/mech.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/auth/mech.h	Mon May 17 04:32:16 2004 +0300
@@ -43,6 +43,7 @@
 extern const char *default_realm;
 extern const char *anonymous_username;
 extern char username_chars[256];
+extern int ssl_require_client_cert;
 
 void mech_register_module(struct mech_module *module);
 void mech_unregister_module(struct mech_module *module);
--- a/src/imap-login/client-authenticate.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/imap-login/client-authenticate.c	Mon May 17 04:32:16 2004 +0300
@@ -11,6 +11,7 @@
 #include "imap-parser.h"
 #include "auth-client.h"
 #include "../auth/auth-mech-desc.h"
+#include "ssl-proxy.h"
 #include "client.h"
 #include "client-authenticate.h"
 #include "auth-common.h"
@@ -160,6 +161,17 @@
 	}
 }
 
+static enum auth_client_request_new_flags
+client_get_auth_flags(struct imap_client *client)
+{
+        enum auth_client_request_new_flags auth_flags = 0;
+
+	if (client->common.proxy != NULL &&
+	    ssl_proxy_has_valid_client_cert(client->common.proxy))
+		auth_flags |= AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT;
+	return auth_flags;
+}
+
 int cmd_login(struct imap_client *client, struct imap_arg *args)
 {
 	const char *user, *pass, *error;
@@ -193,10 +205,12 @@
 	buffer_append(client->plain_login, pass, strlen(pass));
 
 	client_ref(client);
+
 	client->common.auth_request =
 		auth_client_request_new(auth_client, AUTH_MECH_PLAIN,
-					AUTH_PROTOCOL_IMAP, login_callback,
-					client, &error);
+					AUTH_PROTOCOL_IMAP,
+					client_get_auth_flags(client),
+					login_callback, client, &error);
 	if (client->common.auth_request == NULL) {
 		client_send_tagline(client, t_strconcat(
 			"NO Login failed: ", error, NULL));
@@ -324,6 +338,7 @@
 	client->common.auth_request =
 		auth_client_request_new(auth_client, mech->mech,
 					AUTH_PROTOCOL_IMAP,
+					client_get_auth_flags(client),
 					authenticate_callback,
 					client, &error);
 	if (client->common.auth_request != NULL) {
--- a/src/imap-login/client.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/imap-login/client.c	Mon May 17 04:32:16 2004 +0300
@@ -124,7 +124,8 @@
 		client->common.io = NULL;
 	}
 
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip);
+	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
+			       &client->common.proxy);
 	if (fd_ssl != -1) {
 		client->tls = TRUE;
 		client->secured = TRUE;
@@ -421,6 +422,8 @@
 		client->common.fd = -1;
 	}
 
+	if (client->common.proxy != NULL)
+		ssl_proxy_free(client->common.proxy);
 	client_unref(client);
 }
 
--- a/src/lib-auth/auth-client.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/lib-auth/auth-client.h	Mon May 17 04:32:16 2004 +0300
@@ -31,6 +31,7 @@
 struct auth_request *
 auth_client_request_new(struct auth_client *client,
 			enum auth_mech mech, enum auth_protocol protocol,
+			enum auth_client_request_new_flags flags,
 			auth_request_callback_t *callback, void *context,
 			const char **error_r);
 
--- a/src/lib-auth/auth-server-request.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/lib-auth/auth-server-request.c	Mon May 17 04:32:16 2004 +0300
@@ -11,7 +11,8 @@
         struct auth_server_connection *conn;
 
 	enum auth_mech mech;
-        enum auth_protocol protocol;
+	enum auth_protocol protocol;
+	enum auth_client_request_new_flags flags;
 
 	unsigned int id;
 
@@ -35,6 +36,7 @@
 	auth_request.id = request->id;
 	auth_request.protocol = request->protocol;
 	auth_request.mech = request->mech;
+	auth_request.flags = request->flags;
 
 	if (o_stream_send(conn->output, &auth_request,
 			  sizeof(auth_request)) < 0) {
@@ -177,6 +179,7 @@
 struct auth_request *
 auth_client_request_new(struct auth_client *client,
 			enum auth_mech mech, enum auth_protocol protocol,
+			enum auth_client_request_new_flags flags,
 			auth_request_callback_t *callback, void *context,
 			const char **error_r)
 {
@@ -191,6 +194,7 @@
 	request->conn = conn;
 	request->mech = mech;
 	request->protocol = protocol;
+	request->flags = flags;
 	request->id = ++client->request_id_counter;
 	if (request->id == 0) {
 		/* wrapped - ID 0 not allowed */
--- a/src/login-common/client-common.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/login-common/client-common.h	Mon May 17 04:32:16 2004 +0300
@@ -6,6 +6,7 @@
 
 struct client {
 	struct ip_addr ip;
+	struct ssl_proxy *proxy;
 
 	int fd;
 	struct io *io;
--- a/src/login-common/main.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/login-common/main.c	Mon May 17 04:32:16 2004 +0300
@@ -95,6 +95,8 @@
 static void login_accept_ssl(void *context __attr_unused__)
 {
 	struct ip_addr ip;
+	struct client *client;
+	struct ssl_proxy *proxy;
 	int fd, fd_ssl;
 
 	fd = net_accept(LOGIN_SSL_LISTEN_FD, &ip, NULL);
@@ -107,11 +109,13 @@
 	if (process_per_connection)
 		main_close_listen();
 
-	fd_ssl = ssl_proxy_new(fd, &ip);
+	fd_ssl = ssl_proxy_new(fd, &ip, &proxy);
 	if (fd_ssl == -1)
 		net_disconnect(fd);
-	else
-		(void)client_create(fd_ssl, &ip, TRUE);
+	else {
+		client = client_create(fd_ssl, &ip, TRUE);
+		client->proxy = proxy;
+	}
 }
 
 static void auth_connect_notify(struct auth_client *client __attr_unused__,
@@ -213,6 +217,8 @@
 {
 	const char *name, *group_name;
 	struct ip_addr ip;
+	struct ssl_proxy *proxy = NULL;
+	struct client *client;
 	int i, fd = -1, master_fd = -1;
 
 	is_inetd = getenv("DOVECOT_MASTER") == NULL;
@@ -258,7 +264,7 @@
 		fd = 1;
 		for (i = 1; i < argc; i++) {
 			if (strcmp(argv[i], "--ssl") == 0) {
-				fd = ssl_proxy_new(fd, &ip);
+				fd = ssl_proxy_new(fd, &ip, &proxy);
 				if (fd == -1)
 					i_fatal("SSL initialization failed");
 			} else if (strncmp(argv[i], "--group=", 8) != 0)
@@ -269,8 +275,10 @@
 		closing_down = TRUE;
 	}
 
-	if (fd != -1)
-		(void)client_create(fd, &ip, TRUE);
+	if (fd != -1) {
+		client = client_create(fd, &ip, TRUE);
+		client->proxy = proxy;
+	}
 
 	io_loop_run(ioloop);
 	main_deinit();
--- a/src/login-common/ssl-proxy-openssl.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/login-common/ssl-proxy-openssl.c	Mon May 17 04:32:16 2004 +0300
@@ -41,8 +41,11 @@
 
 	unsigned int handshaked:1;
 	unsigned int destroyed:1;
+	unsigned int cert_received:1;
+	unsigned int cert_broken:1;
 };
 
+static int extdata_index;
 static SSL_CTX *ssl_ctx;
 static struct hash_table *ssl_proxies;
 
@@ -308,12 +311,14 @@
 	ssl_proxy_unref(proxy);
 }
 
-int ssl_proxy_new(int fd, struct ip_addr *ip)
+int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r)
 {
 	struct ssl_proxy *proxy;
 	SSL *ssl;
 	int sfd[2];
 
+	*proxy_r = NULL;
+
 	if (!ssl_initialized)
 		return -1;
 
@@ -340,24 +345,30 @@
 	net_set_nonblock(fd, TRUE);
 
 	proxy = i_new(struct ssl_proxy, 1);
-	proxy->refcount = 1;
+	proxy->refcount = 2;
 	proxy->ssl = ssl;
 	proxy->fd_ssl = fd;
 	proxy->fd_plain = sfd[0];
 	proxy->ip = *ip;
+        SSL_set_ex_data(ssl, extdata_index, proxy);
 
 	hash_insert(ssl_proxies, proxy, proxy);
 
-	proxy->refcount++;
 	ssl_handshake(proxy);
-	if (!ssl_proxy_unref(proxy)) {
-		/* handshake failed. return the disconnected socket anyway
-		   so the caller doesn't try to use the old closed fd */
-		return sfd[1];
-	}
+        main_ref();
+
+	*proxy_r = proxy;
+	return sfd[1];
+}
 
-        main_ref();
-	return sfd[1];
+int ssl_proxy_has_valid_client_cert(struct ssl_proxy *proxy)
+{
+	return proxy->cert_received && !proxy->cert_broken;
+}
+
+void ssl_proxy_free(struct ssl_proxy *proxy)
+{
+	ssl_proxy_unref(proxy);
 }
 
 static int ssl_proxy_unref(struct ssl_proxy *proxy)
@@ -401,6 +412,22 @@
 	return RSA_generate_key(keylength, RSA_F4, NULL, NULL);
 }
 
+static int ssl_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
+{
+	SSL *ssl;
+        struct ssl_proxy *proxy;
+
+	ssl = X509_STORE_CTX_get_ex_data(ctx,
+					 SSL_get_ex_data_X509_STORE_CTX_idx());
+	proxy = SSL_get_ex_data(ssl, extdata_index);
+
+	proxy->cert_received = TRUE;
+	if (!preverify_ok)
+		proxy->cert_broken = TRUE;
+
+	return 1;
+}
+
 void ssl_proxy_init(void)
 {
 	const char *cafile, *certfile, *keyfile, *paramfile, *cipher_list;
@@ -419,6 +446,8 @@
 	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");
 
@@ -455,8 +484,8 @@
 
 	if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) {
 		SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER |
-				   SSL_VERIFY_FAIL_IF_NO_PEER_CERT |
-				   SSL_VERIFY_CLIENT_ONCE, NULL);
+				   SSL_VERIFY_CLIENT_ONCE,
+				   ssl_verify_client_cert);
 	}
 
 	/* PRNG initialization might want to use /dev/urandom, make sure it
--- a/src/login-common/ssl-proxy.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/login-common/ssl-proxy.h	Mon May 17 04:32:16 2004 +0300
@@ -2,13 +2,16 @@
 #define __SSL_PROXY_H
 
 struct ip_addr;
+struct ssl_proxy;
 
 extern int ssl_initialized;
 
 /* establish SSL connection with the given fd, returns a new fd which you
    must use from now on, or -1 if error occured. Unless -1 is returned,
    the given fd must be simply forgotten. */
-int ssl_proxy_new(int fd, struct ip_addr *ip);
+int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r);
+int ssl_proxy_has_valid_client_cert(struct ssl_proxy *proxy);
+void ssl_proxy_free(struct ssl_proxy *proxy);
 
 void ssl_proxy_init(void);
 void ssl_proxy_deinit(void);
--- a/src/master/auth-process.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/master/auth-process.c	Mon May 17 04:32:16 2004 +0300
@@ -335,6 +335,8 @@
 		env_put("USE_CYRUS_SASL=1");
 	if (group->set->verbose)
 		env_put("VERBOSE=1");
+	if (group->set->ssl_require_client_cert)
+		env_put("SSL_REQUIRE_CLIENT_CERT=1");
 
 	restrict_process_size(group->set->process_size, (unsigned int)-1);
 
--- a/src/master/master-settings.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/master/master-settings.c	Mon May 17 04:32:16 2004 +0300
@@ -132,6 +132,7 @@
 
 	DEF(SET_BOOL, use_cyrus_sasl),
 	DEF(SET_BOOL, verbose),
+	DEF(SET_BOOL, ssl_require_client_cert),
 
 	DEF(SET_INT, count),
 	DEF(SET_INT, process_size),
@@ -263,6 +264,7 @@
 
 	MEMBER(use_cyrus_sasl) FALSE,
 	MEMBER(verbose) FALSE,
+	MEMBER(ssl_require_client_cert) FALSE,
 
 	MEMBER(count) 1,
 	MEMBER(process_size) 256,
@@ -334,6 +336,15 @@
 			auth->chroot);
 		return FALSE;
 	}
+
+	if (auth->ssl_require_client_cert) {
+		/* if we require valid cert, make sure we also ask for it */
+		if (auth->parent->pop3 != NULL)
+			auth->parent->pop3->ssl_verify_client_cert = TRUE;
+		if (auth->parent->imap != NULL)
+			auth->parent->imap->ssl_verify_client_cert = TRUE;
+	}
+
 	return TRUE;
 }
 
--- a/src/master/master-settings.h	Mon May 17 02:29:27 2004 +0300
+++ b/src/master/master-settings.h	Mon May 17 04:32:16 2004 +0300
@@ -109,6 +109,7 @@
 	const char *anonymous_username;
 
 	int use_cyrus_sasl, verbose;
+	int ssl_require_client_cert;
 
 	unsigned int count;
 	unsigned int process_size;
--- a/src/pop3-login/client-authenticate.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/pop3-login/client-authenticate.c	Mon May 17 04:32:16 2004 +0300
@@ -11,6 +11,7 @@
 #include "auth-client.h"
 #include "../auth/auth-mech-desc.h"
 #include "../pop3/capability.h"
+#include "ssl-proxy.h"
 #include "master.h"
 #include "auth-common.h"
 #include "client.h"
@@ -132,6 +133,17 @@
 	t_pop();
 }
 
+static enum auth_client_request_new_flags
+client_get_auth_flags(struct pop3_client *client)
+{
+        enum auth_client_request_new_flags auth_flags = 0;
+
+	if (client->common.proxy != NULL &&
+	    ssl_proxy_has_valid_client_cert(client->common.proxy))
+		auth_flags |= AUTH_CLIENT_FLAG_SSL_VALID_CLIENT_CERT;
+	return auth_flags;
+}
+
 static void login_callback(struct auth_request *request,
 			   struct auth_client_request_reply *reply,
 			   const unsigned char *data, void *context)
@@ -196,6 +208,7 @@
 	client->common.auth_request =
 		auth_client_request_new(auth_client, AUTH_MECH_PLAIN,
 					AUTH_PROTOCOL_POP3,
+                                        client_get_auth_flags(client),
 					login_callback, client, &error);
 	if (client->common.auth_request != NULL) {
 		/* don't read any input from client until login is finished */
@@ -305,6 +318,7 @@
 	client->common.auth_request =
 		auth_client_request_new(auth_client, mech->mech,
 					AUTH_PROTOCOL_POP3,
+                                        client_get_auth_flags(client),
 					authenticate_callback, client, &error);
 	if (client->common.auth_request != NULL) {
 		/* following input data will go to authentication */
--- a/src/pop3-login/client.c	Mon May 17 02:29:27 2004 +0300
+++ b/src/pop3-login/client.c	Mon May 17 04:32:16 2004 +0300
@@ -80,7 +80,8 @@
 		client->common.io = NULL;
 	}
 
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip);
+	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
+			       &client->common.proxy);
 	if (fd_ssl != -1) {
 		client->tls = TRUE;
 		client->secured = TRUE;
@@ -298,6 +299,8 @@
 	net_disconnect(client->common.fd);
 	client->common.fd = -1;
 
+	if (client->common.proxy != NULL)
+		ssl_proxy_free(client->common.proxy);
 	client_unref(client);
 }