]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added ssl-params binary that login process uses to read SSL parameters.
authorTimo Sirainen <tss@iki.fi>
Wed, 7 Oct 2009 21:47:01 +0000 (17:47 -0400)
committerTimo Sirainen <tss@iki.fi>
Wed, 7 Oct 2009 21:47:01 +0000 (17:47 -0400)
--HG--
branch : HEAD

configure.in
doc/example-config/conf.d/master.conf
src/Makefile.am
src/ssl-params/Makefile.am [new file with mode: 0644]
src/ssl-params/main.c [new file with mode: 0644]
src/ssl-params/ssl-params [new file with mode: 0755]
src/ssl-params/ssl-params-openssl.c [new file with mode: 0644]
src/ssl-params/ssl-params-settings.c [new file with mode: 0644]
src/ssl-params/ssl-params-settings.h [new file with mode: 0644]
src/ssl-params/ssl-params.c [new file with mode: 0644]
src/ssl-params/ssl-params.h [new file with mode: 0644]

index 17ce44cc3a6263f5c2ea33a6a887f0836ed36154..d6a745ebdcbc77d6dabb5d92e0ff1a63e8cfe09b 100644 (file)
@@ -2507,6 +2507,7 @@ src/login-common/Makefile
 src/master/Makefile
 src/pop3/Makefile
 src/pop3-login/Makefile
+src/ssl-params/Makefile
 src/util/Makefile
 src/plugins/Makefile
 src/plugins/acl/Makefile
index fe94229e9f3d092018e0397d37dc1da6ee172d8e..ae822f125bce9a5c7d87a536b3258e623c50d1c4 100644 (file)
@@ -153,3 +153,12 @@ service dict {
     mode = 0666
   }
 }
+
+service ssl-params {
+  executable = ssl-params
+
+  unix_listener {
+    path = login/ssl-params
+    mode = 0666
+  }
+}
index 6312f2f641126263540175882cbc5d5b2209efcd..e9fa43a001a8b66de4d96ab1957be4ff16dfa69f 100644 (file)
@@ -33,4 +33,5 @@ SUBDIRS = \
        config \
        util \
        dsync \
+       ssl-params \
        plugins
diff --git a/src/ssl-params/Makefile.am b/src/ssl-params/Makefile.am
new file mode 100644 (file)
index 0000000..f9ebe18
--- /dev/null
@@ -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
diff --git a/src/ssl-params/main.c b/src/ssl-params/main.c
new file mode 100644 (file)
index 0000000..3e9bba2
--- /dev/null
@@ -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;
+}
diff --git a/src/ssl-params/ssl-params b/src/ssl-params/ssl-params
new file mode 100755 (executable)
index 0000000..e2f01b6
Binary files /dev/null and b/src/ssl-params/ssl-params differ
diff --git a/src/ssl-params/ssl-params-openssl.c b/src/ssl-params/ssl-params-openssl.c
new file mode 100644 (file)
index 0000000..889ad6a
--- /dev/null
@@ -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
diff --git a/src/ssl-params/ssl-params-settings.c b/src/ssl-params/ssl-params-settings.c
new file mode 100644 (file)
index 0000000..6f0ac38
--- /dev/null
@@ -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];
+}
diff --git a/src/ssl-params/ssl-params-settings.h b/src/ssl-params/ssl-params-settings.h
new file mode 100644 (file)
index 0000000..d539b77
--- /dev/null
@@ -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
diff --git a/src/ssl-params/ssl-params.c b/src/ssl-params/ssl-params.c
new file mode 100644 (file)
index 0000000..95f445b
--- /dev/null
@@ -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);
+}
diff --git a/src/ssl-params/ssl-params.h b/src/ssl-params/ssl-params.h
new file mode 100644 (file)
index 0000000..e5eb070
--- /dev/null
@@ -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