# HG changeset patch # User Timo Sirainen # Date 1037526127 -7200 # Node ID 0d852af6842ee168523186cc16637190321d428f # Parent e60620644af3bfc8e9ff6cc00fce2c63158043b0 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. diff -r e60620644af3 -r 0d852af6842e dovecot-example.conf --- 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 diff -r e60620644af3 -r 0d852af6842e src/login/ssl-proxy.c --- 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 #include +#include +#include +#include #include 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; } diff -r e60620644af3 -r 0d852af6842e src/master/Makefile.am --- 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 \ diff -r e60620644af3 -r 0d852af6842e src/master/common.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 }; diff -r e60620644af3 -r 0d852af6842e src/master/login-process.c --- 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__) { diff -r e60620644af3 -r 0d852af6842e src/master/login-process.h --- 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); diff -r e60620644af3 -r 0d852af6842e src/master/main.c --- 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 #include @@ -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); diff -r e60620644af3 -r 0d852af6842e src/master/settings.c --- 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) diff -r e60620644af3 -r 0d852af6842e src/master/settings.h --- 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;