changeset 9985:ea36bad4d9da HEAD

Added ssl-params binary that login process uses to read SSL parameters.
author Timo Sirainen <tss@iki.fi>
date Wed, 07 Oct 2009 17:47:01 -0400
parents 097588a7903c
children 79917d163689
files configure.in doc/example-config/conf.d/master.conf src/Makefile.am src/ssl-params/Makefile.am src/ssl-params/main.c src/ssl-params/ssl-params src/ssl-params/ssl-params-openssl.c src/ssl-params/ssl-params-settings.c src/ssl-params/ssl-params-settings.h src/ssl-params/ssl-params.c src/ssl-params/ssl-params.h
diffstat 11 files changed, 530 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/configure.in	Wed Oct 07 17:46:14 2009 -0400
+++ b/configure.in	Wed Oct 07 17:47:01 2009 -0400
@@ -2507,6 +2507,7 @@
 src/master/Makefile
 src/pop3/Makefile
 src/pop3-login/Makefile
+src/ssl-params/Makefile
 src/util/Makefile
 src/plugins/Makefile
 src/plugins/acl/Makefile
--- a/doc/example-config/conf.d/master.conf	Wed Oct 07 17:46:14 2009 -0400
+++ b/doc/example-config/conf.d/master.conf	Wed Oct 07 17:47:01 2009 -0400
@@ -153,3 +153,12 @@
     mode = 0666
   }
 }
+
+service ssl-params {
+  executable = ssl-params
+
+  unix_listener {
+    path = login/ssl-params
+    mode = 0666
+  }
+}
--- a/src/Makefile.am	Wed Oct 07 17:46:14 2009 -0400
+++ b/src/Makefile.am	Wed Oct 07 17:47:01 2009 -0400
@@ -33,4 +33,5 @@
 	config \
 	util \
 	dsync \
+	ssl-params \
 	plugins
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/Makefile.am	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,21 @@
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = ssl-params
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-master \
+	-I$(top_srcdir)/src/lib-settings \
+	-DPKG_STATEDIR=\""$(statedir)"\"
+
+ssl_params_LDADD = $(LIBDOVECOT) $(SSL_LIBS)
+ssl_params_DEPENDENCIES = $(LIBDOVECOT) $(SSL_LIBS)
+ssl_params_SOURCES = \
+	main.c \
+	ssl-params.c \
+	ssl-params-openssl.c \
+	ssl-params-settings.c
+
+noinst_HEADERS = \
+	ssl-params.h \
+	ssl-params-settings.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/main.c	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,136 @@
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "lib-signals.h"
+#include "array.h"
+#include "ostream.h"
+#include "master-service.h"
+#include "ssl-params-settings.h"
+#include "ssl-params.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+#define SSL_BUILD_PARAM_FNAME "ssl-parameters.dat"
+
+struct client {
+	int fd;
+	struct ostream *output;
+};
+
+static ARRAY_DEFINE(delayed_fds, int);
+struct ssl_params *param;
+static buffer_t *ssl_params;
+
+static int client_output_flush(struct ostream *output)
+{
+	if (o_stream_flush(output) == 0) {
+		/* more to come */
+		return 0;
+	}
+	/* finished / disconnected */
+	o_stream_destroy(&output);
+	return -1;
+}
+
+static void client_handle(int fd)
+{
+	struct ostream *output;
+
+	output = o_stream_create_fd(fd, (size_t)-1, TRUE);
+	o_stream_send(output, ssl_params->data, ssl_params->used);
+
+	if (o_stream_get_buffer_used_size(output) == 0)
+		o_stream_destroy(&output);
+	else {
+		o_stream_set_flush_callback(output, client_output_flush,
+					    output);
+	}
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+	if (ssl_params->used == 0) {
+		/* waiting for parameter building to finish */
+		if (!array_is_created(&delayed_fds))
+			i_array_init(&delayed_fds, 32);
+		array_append(&delayed_fds, &conn->fd, 1);
+	}
+	client_handle(conn->fd);
+}
+
+static void ssl_params_callback(const unsigned char *data, size_t size)
+{
+	const int *fds;
+	unsigned int i, count;
+
+	buffer_set_used_size(ssl_params, 0);
+	buffer_append(ssl_params, data, size);
+
+	if (!array_is_created(&delayed_fds))
+		return;
+
+	fds = array_get(&delayed_fds, &count);
+	for (i = 0; i < count; i++)
+		client_handle(fds[i]);
+	array_free(&delayed_fds);
+}
+
+static void sig_chld(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
+{
+	int status;
+
+	if (waitpid(-1, &status, WNOHANG) < 0)
+		i_error("waitpid() failed: %m");
+	else if (status != 0)
+		i_error("child process failed with status %d", status);
+	else {
+		/* params should have been created now. try refreshing. */
+		ssl_params_refresh(param);
+	}
+}
+
+static void main_init(const struct ssl_params_settings *set)
+{
+	lib_signals_set_handler(SIGCHLD, TRUE, sig_chld, NULL);
+
+	ssl_params = buffer_create_dynamic(default_pool, 1024);
+	param = ssl_params_init(PKG_STATEDIR"/"SSL_BUILD_PARAM_FNAME,
+				ssl_params_callback, set);
+}
+
+static void main_deinit(void)
+{
+	ssl_params_deinit(&param);
+	if (array_is_created(&delayed_fds))
+		array_free(&delayed_fds);
+}
+
+int main(int argc, char *argv[])
+{
+	const struct ssl_params_settings *set;
+	int c;
+
+	master_service = master_service_init("ssl-build-param", 0, argc, argv);
+	master_service_init_log(master_service, "ssl-build-param: ");
+
+	while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
+		if (!master_service_parse_option(master_service, c, optarg))
+			exit(FATAL_DEFAULT);
+	}
+
+	set = ssl_params_settings_read(master_service);
+	master_service_init_finish(master_service);
+
+#ifndef HAVE_SSL
+	i_fatal("Dovecot built without SSL support");
+#endif
+
+	main_init(set);
+	master_service_run(master_service, client_connected);
+	main_deinit();
+
+	master_service_deinit(&master_service);
+        return 0;
+}
Binary file src/ssl-params/ssl-params has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/ssl-params-openssl.c	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,71 @@
+/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "write-full.h"
+#include "ssl-params.h"
+
+#ifdef HAVE_OPENSSL
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+/* 2 or 5. Haven't seen their difference explained anywhere, but 2 is the
+   default.. */
+#define DH_GENERATOR 2
+
+static int dh_param_bitsizes[] = { 512, 1024 };
+
+static const char *ssl_last_error(void)
+{
+	unsigned long err;
+	char *buf;
+	size_t err_size = 256;
+
+	err = ERR_get_error();
+	if (err == 0)
+		return strerror(errno);
+
+	buf = t_malloc(err_size);
+	buf[err_size-1] = '\0';
+	ERR_error_string_n(err, buf, err_size-1);
+	return buf;
+}
+
+static void generate_dh_parameters(int bitsize, int fd, const char *fname)
+{
+        DH *dh = DH_generate_parameters(bitsize, DH_GENERATOR, NULL, NULL);
+	unsigned char *buf, *p;
+	int len;
+
+	if (dh == NULL) {
+		i_fatal("DH_generate_parameters(bits=%d, gen=%d) failed: %s",
+			bitsize, DH_GENERATOR, ssl_last_error());
+	}
+
+	len = i2d_DHparams(dh, NULL);
+	if (len < 0)
+		i_fatal("i2d_DHparams() failed: %s", ssl_last_error());
+
+	buf = p = i_malloc(len);
+	len = i2d_DHparams(dh, &p);
+
+	if (write_full(fd, &bitsize, sizeof(bitsize)) < 0 ||
+	    write_full(fd, &len, sizeof(len)) < 0 ||
+	    write_full(fd, buf, len) < 0)
+		i_fatal("write_full() failed for file %s: %m", fname);
+	i_free(buf);
+}
+
+void ssl_generate_parameters(int fd, const char *fname)
+{
+	unsigned int i;
+	int bits;
+
+	for (i = 0; i < N_ELEMENTS(dh_param_bitsizes); i++)
+		generate_dh_parameters(dh_param_bitsizes[i], fd, fname);
+	bits = 0;
+	if (write_full(fd, &bits, sizeof(bits)) < 0)
+		i_fatal("write_full() failed for file %s: %m", fname);
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/ssl-params-settings.c	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,53 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "settings-parser.h"
+#include "master-service-settings.h"
+#include "ssl-params-settings.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#undef DEF
+#define DEF(type, name) \
+	{ type, #name, offsetof(struct ssl_params_settings, name), NULL }
+
+static struct setting_define ssl_params_setting_defines[] = {
+	DEF(SET_UINT, ssl_parameters_regenerate),
+
+	SETTING_DEFINE_LIST_END
+};
+
+static struct ssl_params_settings ssl_params_default_settings = {
+	MEMBER(ssl_parameters_regenerate) 24*7
+};
+
+struct setting_parser_info ssl_params_setting_parser_info = {
+	MEMBER(defines) ssl_params_setting_defines,
+	MEMBER(defaults) &ssl_params_default_settings,
+
+	MEMBER(parent) NULL,
+	MEMBER(dynamic_parsers) NULL,
+
+	MEMBER(parent_offset) (size_t)-1,
+	MEMBER(type_offset) (size_t)-1,
+	MEMBER(struct_size) sizeof(struct ssl_params_settings)
+};
+
+struct ssl_params_settings *
+ssl_params_settings_read(struct master_service *service)
+{
+	static const struct setting_parser_info *set_roots[] = {
+		&ssl_params_setting_parser_info,
+		NULL
+	};
+	const char *error;
+	void **sets;
+
+	if (master_service_settings_read_simple(service, set_roots, &error) < 0)
+		i_fatal("Error reading configuration: %s", error);
+
+	sets = master_service_settings_get_others(service);
+	return sets[0];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/ssl-params-settings.h	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,13 @@
+#ifndef SSL_PARAMS_SETTINGS_H
+#define SSL_PARAMS_SETTINGS_H
+
+struct master_service;
+
+struct ssl_params_settings {
+	unsigned int ssl_parameters_regenerate;
+};
+
+struct ssl_params_settings *
+ssl_params_settings_read(struct master_service *service);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/ssl-params.c	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,208 @@
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "buffer.h"
+#include "file-lock.h"
+#include "read-full.h"
+#include "master-service-settings.h"
+#include "ssl-params-settings.h"
+#include "ssl-params.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define MAX_PARAM_FILE_SIZE 1024
+#define SSL_BUILD_PARAM_TIMEOUT_SECS (60*30)
+
+struct ssl_params {
+	char *path;
+	struct ssl_params_settings set;
+
+	time_t last_mtime;
+	struct timeout *to_rebuild;
+	ssl_params_callback_t *callback;
+};
+
+static void ssl_params_if_unchanged(const char *path, time_t mtime)
+{
+	const char *temp_path;
+	struct file_lock *lock;
+	struct stat st, st2;
+	mode_t old_mask;
+	int fd, ret;
+
+	temp_path = t_strconcat(path, ".tmp", NULL);
+
+	old_mask = umask(0);
+	fd = open(temp_path, O_WRONLY | O_CREAT, 0644);
+	umask(old_mask);
+
+	if (fd == -1)
+		i_fatal("creat(%s) failed: %m", temp_path);
+
+	/* If multiple dovecot instances are running, only one of them needs
+	   to regenerate this file. */
+	ret = file_wait_lock(fd, temp_path, F_WRLCK,
+			     FILE_LOCK_METHOD_FCNTL,
+			     SSL_BUILD_PARAM_TIMEOUT_SECS, &lock);
+	if (ret < 0)
+		i_fatal("file_try_lock(%s) failed: %m", temp_path);
+	if (ret == 0) {
+		/* someone else is writing this */
+		i_fatal("Timeout while waiting for %s generation to complete",
+			path);
+		return;
+	}
+
+	/* make sure the .tmp file is still the one we created */
+	if (fstat(fd, &st) < 0)
+		i_fatal("fstat(%s) failed: %m", temp_path);
+	if (stat(temp_path, &st2) < 0)
+		i_fatal("stat(%s) failed: %m", temp_path);
+	if (st.st_ino != st2.st_ino) {
+		/* nope. so someone else just generated the file. */
+		(void)close(fd);
+		return;
+	}
+
+	/* check that the parameters file is still the same */
+	if (stat(path, &st) < 0)
+		i_fatal("stat(%s) failed: %m", temp_path);
+	if (st.st_mtime != mtime) {
+		(void)close(fd);
+		return;
+	}
+
+	/* ok, we really want to generate it. */
+	if (ftruncate(fd, 0) < 0)
+		i_fatal("ftruncate(%s) failed: %m", temp_path);
+
+	i_info("Generating SSL parameters");
+#ifdef HAVE_SSL
+	ssl_generate_parameters(fd, temp_path);
+#endif
+
+	if (rename(temp_path, path) < 0)
+		i_fatal("rename(%s, %s) failed: %m", temp_path, path);
+	if (close(fd) < 0)
+		i_fatal("close(%s) failed: %m", temp_path);
+	file_lock_free(&lock);
+
+	i_info("SSL parameters regeneration completed");
+}
+
+static void ssl_params_rebuild(struct ssl_params *param)
+{
+	if (param->to_rebuild != NULL)
+		timeout_remove(&param->to_rebuild);
+
+	switch (fork()) {
+	case -1:
+		i_fatal("fork() failed: %m");
+	case 0:
+		/* child */
+		ssl_params_if_unchanged(param->path, param->last_mtime);
+		exit(0);
+	default:
+		/* parent */
+		break;
+	}
+}
+
+static void ssl_params_set_timeout(struct ssl_params *param)
+{
+	time_t next_rebuild, diff;
+
+	if (param->to_rebuild != NULL)
+		timeout_remove(&param->to_rebuild);
+
+	next_rebuild = param->last_mtime +
+		param->set.ssl_parameters_regenerate * 3600;
+
+	if (ioloop_time >= next_rebuild) {
+		ssl_params_rebuild(param);
+		return;
+	}
+
+	diff = next_rebuild - ioloop_time;
+	if (diff > INT_MAX / 1000)
+		diff = INT_MAX / 1000;
+	param->to_rebuild = timeout_add(diff * 1000, ssl_params_rebuild, param);
+}
+
+static int ssl_params_read(struct ssl_params *param)
+{
+	unsigned char *buffer;
+	struct stat st;
+	int fd, ret;
+
+	fd = open(param->path, O_RDONLY);
+	if (fd == -1) {
+		if (errno != ENOENT)
+			i_error("open(%s) failed: %m", param->path);
+		return -1;
+	}
+
+	if (fstat(fd, &st) < 0) {
+		i_error("stat(%s) failed: %m", param->path);
+		(void)close(fd);
+		return -1;
+	}
+	if (st.st_size == 0 || st.st_size > MAX_PARAM_FILE_SIZE) {
+		i_error("Corrupted file: %s", param->path);
+		(void)unlink(param->path);
+		return -1;
+	}
+
+	buffer = t_malloc(st.st_size);
+	ret = read_full(fd, buffer, st.st_size);
+	if (ret < 0)
+		i_error("read(%s) failed: %m", param->path);
+	else if (ret == 0) {
+		i_error("File unexpectedly shrank: %s", param->path);
+		ret = -1;
+	} else {
+		param->last_mtime = st.st_mtime;
+		ssl_params_set_timeout(param);
+		param->callback(buffer, st.st_size);
+	}
+
+	if (close(fd) < 0)
+		i_error("close(%s) failed: %m", param->path);
+	return ret;
+}
+
+struct ssl_params *
+ssl_params_init(const char *path, ssl_params_callback_t *callback,
+		const struct ssl_params_settings *set)
+{
+	struct ssl_params *param;
+
+	param = i_new(struct ssl_params, 1);
+	param->path = i_strdup(path);
+	param->set = *set;
+	param->callback = callback;
+	ssl_params_refresh(param);
+	return param;
+}
+
+void ssl_params_refresh(struct ssl_params *param)
+{
+	if (ssl_params_read(param) < 0)
+		ssl_params_rebuild(param);
+}
+
+void ssl_params_deinit(struct ssl_params **_param)
+{
+	struct ssl_params *param = *_param;
+
+	*_param = NULL;
+	if (param->to_rebuild != NULL)
+		timeout_remove(&param->to_rebuild);
+	i_free(param->path);
+	i_free(param);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ssl-params/ssl-params.h	Wed Oct 07 17:47:01 2009 -0400
@@ -0,0 +1,17 @@
+#ifndef SSL_BUILD_PARAMS_H
+#define SSL_BUILD_PARAMS_H
+
+struct ssl_params_settings;
+
+typedef void ssl_params_callback_t(const unsigned char *data, size_t size);
+
+struct ssl_params *
+ssl_params_init(const char *path, ssl_params_callback_t *callback,
+		const struct ssl_params_settings *set);
+void ssl_params_deinit(struct ssl_params **param);
+
+void ssl_params_refresh(struct ssl_params *param);
+
+void ssl_generate_parameters(int fd, const char *fname);
+
+#endif