changeset 615:0d852af6842e HEAD

Master process generates DH/RSA parameters now and stores them into file which login processes read. Added setting for regeneration interval. Some other SSL cleanups. Also fixed default login process path to be ../run/dovecot/login/ like example config file said, instead of just ../run/dovecot/ which it actually was until now.
author Timo Sirainen <tss@iki.fi>
date Sun, 17 Nov 2002 11:42:07 +0200
parents e60620644af3
children c7a01d999962
files dovecot-example.conf src/login/ssl-proxy.c src/master/Makefile.am src/master/common.h src/master/login-process.c src/master/login-process.h src/master/main.c src/master/settings.c src/master/settings.h
diffstat 9 files changed, 225 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Sat Nov 16 07:57:20 2002 +0200
+++ b/dovecot-example.conf	Sun Nov 17 11:42:07 2002 +0200
@@ -27,6 +27,15 @@
 #ssl_cert_file = /etc/ssl/certs/imapd.pem
 #ssl_key_file = /etc/ssl/private/imapd.pem
 
+# SSL parameter file. Master process generates this file for login processes.
+# It contains Diffie Hellman and RSA parameters.
+#ssl_parameters_file = /var/run/dovecot/ssl-parameters.dat
+
+# How often to regenerate the SSL parameters file. Generation is quite CPU
+# intensive operation. The value is in hours, 0 disables regeneration
+# entirely.
+#ssl_parameters_regenerate = 24
+
 # Disable LOGIN command and all other plaintext authentications unless
 # SSL/TLS is used (LOGINDISABLED capability)
 #disable_plaintext_auth = no
--- a/src/login/ssl-proxy.c	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/login/ssl-proxy.c	Sun Nov 17 11:42:07 2002 +0200
@@ -9,13 +9,17 @@
 
 #ifdef HAVE_SSL
 
+#include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <gcrypt.h>
 #include <gnutls/gnutls.h>
 
 typedef struct {
 	int refcount;
 
-	GNUTLS_STATE state;
+	gnutls_session session;
 	int fd_ssl, fd_plain;
 	IO io_ssl, io_plain;
 	int io_ssl_dir;
@@ -26,21 +30,23 @@
 	size_t send_left_ssl, send_left_plain;
 } SSLProxy;
 
-#define DH_BITS 1024
-
 const int protocol_priority[] =
 	{ GNUTLS_TLS1, GNUTLS_SSL3, 0 };
 const int kx_priority[] =
-	{ GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
+	{ GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 };
 const int cipher_priority[] =
-	{ GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC, 0 };
+	{ GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC,
+	  GNUTLS_CIPHER_ARCFOUR_128, GNUTLS_CIPHER_ARCFOUR_40, 0 };
 const int comp_priority[] =
-	{ GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
+	{ GNUTLS_COMP_LZO, GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 };
 const int mac_priority[] =
 	{ GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 };
+const int cert_type_priority[] =
+	{ GNUTLS_CRT_X509, 0 };
 
-static GNUTLS_CERTIFICATE_SERVER_CREDENTIALS x509_cred;
-static GNUTLS_DH_PARAMS dh_params;
+static gnutls_certificate_credentials x509_cred;
+static gnutls_dh_params dh_params;
+static gnutls_rsa_params rsa_params;
 
 static void ssl_input(void *context, int handle, IO io);
 static void plain_input(void *context, int handle, IO io);
@@ -48,7 +54,7 @@
 
 static const char *get_alert_text(SSLProxy *proxy)
 {
-	return gnutls_alert_get_name(gnutls_alert_get(proxy->state));
+	return gnutls_alert_get_name(gnutls_alert_get(proxy->session));
 }
 
 static int handle_ssl_error(SSLProxy *proxy, int error)
@@ -69,6 +75,8 @@
 		i_warning("Error reading from SSL client: %s",
 			  gnutls_strerror(error));
 	}
+
+        gnutls_alert_send_appropriate(proxy->session, error);
 	ssl_proxy_destroy(proxy);
 	return -1;
 }
@@ -77,7 +85,7 @@
 {
 	int rcvd;
 
-	rcvd = gnutls_record_recv(proxy->state, data, size);
+	rcvd = gnutls_record_recv(proxy->session, data, size);
 	if (rcvd > 0)
 		return rcvd;
 
@@ -96,7 +104,7 @@
 {
 	int sent;
 
-	sent = gnutls_record_send(proxy->state, data, size);
+	sent = gnutls_record_send(proxy->session, data, size);
 	if (sent >= 0)
 		return sent;
 
@@ -114,7 +122,7 @@
 	if (--proxy->refcount > 0)
 		return TRUE;
 
-	gnutls_deinit(proxy->state);
+	gnutls_deinit(proxy->session);
 
 	(void)net_disconnect(proxy->fd_ssl);
 	(void)net_disconnect(proxy->fd_plain);
@@ -215,7 +223,7 @@
 	rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf));
 	if (rcvd < 0) {
 		/* disconnected */
-		gnutls_bye(proxy->state, 1);
+		gnutls_bye(proxy->session, 1);
 		ssl_proxy_destroy(proxy);
 		return;
 	}
@@ -238,7 +246,7 @@
 	SSLProxy *proxy = context;
 	int ret, dir;
 
-        ret = gnutls_handshake(proxy->state);
+        ret = gnutls_handshake(proxy->session);
 	if (ret >= 0) {
 		/* handshake done, now we can start reading */
 		if (proxy->io_ssl != NULL)
@@ -255,7 +263,7 @@
 		return;
 
 	/* i/o interrupted */
-	dir = gnutls_handshake_get_direction(proxy->state) == 0 ?
+	dir = gnutls_handshake_get_direction(proxy->session) == 0 ?
 		IO_READ : IO_WRITE;
 	if (proxy->io_ssl_dir != dir) {
 		if (proxy->io_ssl != NULL)
@@ -266,41 +274,38 @@
 	}
 }
 
-static GNUTLS_STATE initialize_state(void)
+static gnutls_session initialize_state(void)
 {
-	GNUTLS_STATE state;
+	gnutls_session session;
 
-	gnutls_init(&state, GNUTLS_SERVER);
+	gnutls_init(&session, GNUTLS_SERVER);
 
-	gnutls_protocol_set_priority(state, protocol_priority);
-	gnutls_cipher_set_priority(state, cipher_priority);
-	gnutls_compression_set_priority(state, comp_priority);
-	gnutls_kx_set_priority(state, kx_priority);
-	gnutls_mac_set_priority(state, mac_priority);
+	gnutls_protocol_set_priority(session, protocol_priority);
+	gnutls_cipher_set_priority(session, cipher_priority);
+	gnutls_compression_set_priority(session, comp_priority);
+	gnutls_kx_set_priority(session, kx_priority);
+	gnutls_mac_set_priority(session, mac_priority);
+	gnutls_cert_type_set_priority(session, cert_type_priority);
 
-	gnutls_cred_set(state, GNUTLS_CRD_CERTIFICATE, x509_cred);
-
-	/*gnutls_certificate_server_set_request(state, GNUTLS_CERT_REQUEST);*/
-
-	gnutls_dh_set_prime_bits(state, DH_BITS);
-	return state;
+	gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
+	return session;
 }
 
 int ssl_proxy_new(int fd)
 {
         SSLProxy *proxy;
-	GNUTLS_STATE state;
+	gnutls_session session;
 	int sfd[2];
 
 	if (!ssl_initialized)
 		return -1;
 
-	state = initialize_state();
-	gnutls_transport_set_ptr(state, fd);
+	session = initialize_state();
+	gnutls_transport_set_ptr(session, fd);
 
 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) {
 		i_error("socketpair() failed: %m");
-		gnutls_deinit(state);
+		gnutls_deinit(session);
 		return -1;
 	}
 
@@ -309,7 +314,7 @@
 
 	proxy = i_new(SSLProxy, 1);
 	proxy->refcount = 1;
-	proxy->state = state;
+	proxy->session = session;
 	proxy->fd_ssl = fd;
 	proxy->fd_plain = sfd[0];
 
@@ -322,45 +327,153 @@
 	return sfd[1];
 }
 
-static void generate_dh_primes(void)
+static void read_next_field(int fd, gnutls_datum *datum,
+			    const char *fname, const char *field_name)
 {
-	gnutls_datum prime, generator;
-	int ret;
+        ssize_t ret;
+
+	/* get size */
+	ret = read(fd, &datum->size, sizeof(datum->size));
+	if (ret < 0)
+		i_fatal("read() failed for %s: %m", fname);
+
+	if (ret != sizeof(datum->size)) {
+		(void)unlink(fname);
+		i_fatal("Corrupted SSL parameter file %s: File too small",
+			fname);
+	}
 
-	/* Generate Diffie Hellman parameters - for use with DHE
-	   kx algorithms. These should be discarded and regenerated
-	   once a day, once a week or once a month. Depends on the
-	   security requirements. */
+	if (datum->size > 10240) {
+		(void)unlink(fname);
+		i_fatal("Corrupted SSL parameter file %s: "
+			"Field '%s' too large (%u)",
+			fname, field_name, datum->size);
+	}
+
+	/* read the actual data */
+	datum->data = t_malloc(datum->size);
+	ret = read(fd, datum->data, datum->size);
+	if (ret < 0)
+		i_fatal("read() failed for %s: %m", fname);
+
+	if ((size_t)ret != datum->size) {
+		(void)unlink(fname);
+		i_fatal("Corrupted SSL parameter file %s: "
+			"Field '%s' not fully in file (%u < %u)",
+			fname, field_name, datum->size - ret, datum->size);
+	}
+}
+
+static void read_dh_parameters(int fd, const char *fname)
+{
+	gnutls_datum dbits, prime, generator;
+	int ret, bits;
+
 	if ((ret = gnutls_dh_params_init(&dh_params)) < 0) {
 		i_fatal("gnutls_dh_params_init() failed: %s",
 			gnutls_strerror(ret));
 	}
 
-	ret = gnutls_dh_params_generate(&prime, &generator, DH_BITS);
-	if (ret < 0) {
-		i_fatal("gnutls_dh_params_generate() failed: %s",
+	/* read until bits field is 0 */
+	for (;;) {
+		read_next_field(fd, &dbits, fname, "DH bits");
+
+		if (dbits.size != sizeof(int)) {
+			(void)unlink(fname);
+			i_fatal("Corrupted SSL parameter file %s: "
+				"Field 'DH bits' has invalid size %u",
+				fname, dbits.size);
+		}
+
+		bits = *((int *) dbits.data);
+		if (bits == 0)
+			break;
+
+		read_next_field(fd, &prime, fname, "DH prime");
+		read_next_field(fd, &generator, fname, "DH generator");
+
+		ret = gnutls_dh_params_set(dh_params, prime, generator, bits);
+		if (ret < 0) {
+			i_fatal("gnutls_dh_params_set() failed: %s",
+				gnutls_strerror(ret));
+		}
+	}
+}
+
+static void read_rsa_parameters(int fd, const char *fname)
+{
+	gnutls_datum m, e, d, p, q, u;
+	int ret;
+
+	read_next_field(fd, &m, fname, "RSA m");
+	read_next_field(fd, &e, fname, "RSA e");
+	read_next_field(fd, &d, fname, "RSA d");
+	read_next_field(fd, &p, fname, "RSA p");
+	read_next_field(fd, &q, fname, "RSA q");
+	read_next_field(fd, &u, fname, "RSA u");
+
+	if ((ret = gnutls_rsa_params_init(&rsa_params)) < 0) {
+		i_fatal("gnutls_rsa_params_init() failed: %s",
 			gnutls_strerror(ret));
 	}
 
-	ret = gnutls_dh_params_set(dh_params, prime, generator, DH_BITS);
+	/* only 512bit is allowed */
+	ret = gnutls_rsa_params_set(rsa_params, m, e, d, p, q, u, 512);
 	if (ret < 0) {
-		i_fatal("gnutls_dh_params_set() failed: %s",
+		i_fatal("gnutls_rsa_params_set() failed: %s",
 			gnutls_strerror(ret));
 	}
+}
 
-	free(prime.data);
-	free(generator.data);
+static void read_parameters(const char *fname)
+{
+	int fd;
+
+	/* we'll wait until parameter file exists */
+	for (;;) {
+		fd = open(fname, O_RDONLY);
+		if (fd != -1)
+			break;
+
+		if (errno != ENOENT)
+			i_fatal("Can't open SSL parameter file %s: %m", fname);
+
+		sleep(1);
+	}
+
+	read_dh_parameters(fd, fname);
+	read_rsa_parameters(fd, fname);
+
+	(void)close(fd);
+}
+
+static void gcrypt_log_handler(void *context __attr_unused__, int level,
+			       const char *fmt, va_list args)
+{
+	char *buf;
+
+	t_push();
+
+	buf = t_malloc(printf_string_upper_bound(fmt, args));
+	vsprintf(buf, fmt, args);
+
+	if (level == GCRY_LOG_FATAL)
+		i_error("gcrypt fatal: %s", buf);
+
+	t_pop();
 }
 
 void ssl_proxy_init(void)
 {
-	const char *certfile, *keyfile;
+	const char *certfile, *keyfile, *paramfile;
+	char buf[4];
 	int ret;
 
 	certfile = getenv("SSL_CERT_FILE");
 	keyfile = getenv("SSL_KEY_FILE");
+	paramfile = getenv("SSL_PARAM_FILE");
 
-	if (certfile == NULL || keyfile == NULL) {
+	if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
 		/* SSL support is disabled */
 		return;
 	}
@@ -370,6 +483,13 @@
 			gnutls_strerror(ret));
 	}
 
+	/* gcrypt initialization - set log handler and make sure randomizer
+	   opens /dev/urandom now instead of after we've chrooted */
+	gcry_set_log_handler(gcrypt_log_handler, NULL);
+	gcry_randomize(buf, sizeof(buf), GCRY_STRONG_RANDOM);
+
+	read_parameters(paramfile);
+
 	if ((ret = gnutls_certificate_allocate_cred(&x509_cred)) < 0) {
 		i_fatal("gnutls_certificate_allocate_cred() failed: %s",
 			gnutls_strerror(ret));
@@ -382,8 +502,12 @@
 			certfile, keyfile, gnutls_strerror(ret));
 	}
 
-	generate_dh_primes();
-	gnutls_certificate_set_dh_params(x509_cred, dh_params);
+        ret = gnutls_certificate_set_dh_params(x509_cred, dh_params);
+	if (ret < 0)
+		i_fatal("Can't set DH parameters: %s", gnutls_strerror(ret));
+	ret = gnutls_certificate_set_rsa_params(x509_cred, rsa_params);
+	if (ret < 0)
+		i_fatal("Can't set RSA parameters: %s", gnutls_strerror(ret));
 
 	ssl_initialized = TRUE;
 }
--- a/src/master/Makefile.am	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/Makefile.am	Sun Nov 17 11:42:07 2002 +0200
@@ -7,14 +7,16 @@
 	-DPKG_LIBDIR=\""$(pkglibdir)"\"
 
 imap_master_LDADD = \
-	../lib/liblib.a
+	../lib/liblib.a \
+	$(SSL_LIBS)
 
 imap_master_SOURCES = \
 	auth-process.c \
 	imap-process.c \
 	login-process.c \
 	main.c \
-	settings.c
+	settings.c \
+	ssl-init.c
 
 noinst_HEADERS = \
 	auth-process.h \
--- a/src/master/common.h	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/common.h	Sun Nov 17 11:42:07 2002 +0200
@@ -13,6 +13,7 @@
 	PROCESS_TYPE_AUTH,
 	PROCESS_TYPE_LOGIN,
 	PROCESS_TYPE_IMAP,
+	PROCESS_TYPE_SSL_PARAM,
 
 	PROCESS_TYPE_MAX
 };
--- a/src/master/login-process.c	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/login-process.c	Sun Nov 17 11:42:07 2002 +0200
@@ -312,6 +312,11 @@
 					    set_ssl_key_file, NULL));
 	}
 
+	if (set_ssl_parameters_file != NULL) {
+		putenv((char *) t_strconcat("SSL_PARAM_FILE=",
+					    set_ssl_parameters_file, NULL));
+	}
+
 	if (set_disable_plaintext_auth)
 		putenv("DISABLE_PLAINTEXT_AUTH=1");
 
@@ -333,6 +338,13 @@
 	return -1;
 }
 
+void login_process_abormal_exit(pid_t pid __attr_unused__)
+{
+	/* don't start raising the process count if they're dying all
+	   the time */
+	wanted_processes_count = 0;
+}
+
 static void login_hash_cleanup(void *key __attr_unused__, void *value,
 			       void *context __attr_unused__)
 {
--- a/src/master/login-process.h	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/login-process.h	Sun Nov 17 11:42:07 2002 +0200
@@ -1,6 +1,7 @@
 #ifndef __CHILD_LOGIN_H
 #define __CHILD_LOGIN_H
 
+void login_process_abormal_exit(pid_t pid);
 void login_processes_cleanup(void);
 
 void login_processes_init(void);
--- a/src/master/main.c	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/main.c	Sun Nov 17 11:42:07 2002 +0200
@@ -7,6 +7,7 @@
 
 #include "auth-process.h"
 #include "login-process.h"
+#include "ssl-init.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -20,7 +21,8 @@
 	"unknown",
 	"auth",
 	"login",
-	"imap"
+	"imap",
+	"ssl-param"
 };
 
 static IOLoop ioloop;
@@ -89,16 +91,20 @@
 
 		if (process_type == PROCESS_TYPE_IMAP)
 			imap_process_destroyed(pid);
+		if (process_type == PROCESS_TYPE_SSL_PARAM)
+			ssl_parameter_process_destroyed(pid);
 
 		/* write errors to syslog */
 		process_type_name = process_names[process_type];
 		if (WIFEXITED(status)) {
 			status = WEXITSTATUS(status);
 			if (status != 0) {
+				login_process_abormal_exit(pid);
 				i_error("child %d (%s) returned error %d",
 					(int)pid, process_type_name, status);
 			}
 		} else if (WIFSIGNALED(status)) {
+			login_process_abormal_exit(pid);
 			i_error("child %d (%s) killed with signal %d",
 				(int)pid, process_type_name, WTERMSIG(status));
 		}
@@ -182,6 +188,7 @@
 	pids = hash_create(default_pool, 128, NULL, NULL);
 	to_children = timeout_add(100, children_check_timeout, NULL);
 
+	ssl_init();
 	auth_processes_init();
 	login_processes_init();
 }
@@ -193,6 +200,7 @@
 
 	login_processes_deinit();
 	auth_processes_deinit();
+	ssl_deinit();
 
 	timeout_remove(to_children);
 
--- a/src/master/settings.c	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/settings.c	Sun Nov 17 11:42:07 2002 +0200
@@ -32,6 +32,9 @@
 	{ "imaps_listen",	SET_STR, &set_imaps_listen },
 	{ "ssl_cert_file",	SET_STR, &set_ssl_cert_file },
 	{ "ssl_key_file",	SET_STR, &set_ssl_key_file },
+	{ "ssl_parameters_file",SET_STR, &set_ssl_parameters_file },
+	{ "ssl_parameters_regenerate",
+				SET_INT, &set_ssl_parameters_regenerate },
 	{ "disable_plaintext_auth",
 				SET_BOOL,&set_disable_plaintext_auth },
 
@@ -82,12 +85,14 @@
 
 char *set_ssl_cert_file = "/etc/ssl/certs/imapd.pem";
 char *set_ssl_key_file = "/etc/ssl/private/imapd.pem";
+char *set_ssl_parameters_file = PKG_RUNDIR"/ssl-parameters.dat";
+unsigned int set_ssl_parameters_regenerate = 24;
 int set_disable_plaintext_auth = FALSE;
 
 /* login */
 char *set_login_executable = PKG_LIBDIR "/imap-login";
 char *set_login_user = "imapd";
-char *set_login_dir = PKG_RUNDIR;
+char *set_login_dir = PKG_RUNDIR"/login";
 
 int set_login_chroot = TRUE;
 int set_login_process_per_connection = TRUE;
@@ -174,7 +179,9 @@
 			set_imap_executable);
 	}
 
-	/* since it's under /var/run by default, it may have been deleted */
+	/* since they're under /var/run by default, they may have been
+	   deleted */
+	(void)mkdir(PKG_RUNDIR, 0700);
 	if (mkdir(set_login_dir, 0700) == 0)
 		(void)chown(set_login_dir, set_login_uid, set_login_gid);
 	if (access(set_login_dir, X_OK) < 0)
--- a/src/master/settings.h	Sat Nov 16 07:57:20 2002 +0200
+++ b/src/master/settings.h	Sun Nov 17 11:42:07 2002 +0200
@@ -13,6 +13,8 @@
 
 extern char *set_ssl_cert_file;
 extern char *set_ssl_key_file;
+extern char *set_ssl_parameters_file;
+extern unsigned int set_ssl_parameters_regenerate;
 extern int set_disable_plaintext_auth;
 
 /* login */
@@ -21,7 +23,8 @@
 extern char *set_login_dir;
 extern int set_login_chroot;
 extern int set_login_process_per_connection;
-extern unsigned int set_login_processes_count, set_login_max_processes_count;
+extern unsigned int set_login_processes_count;
+extern unsigned int set_login_max_processes_count;
 extern unsigned int set_max_logging_users;
 
 extern uid_t set_login_uid;