--- contrib/mod_tls.c 2009-12-21 09:09:45.000000000 -0500 +++ contrib/mod_tls.c.oden 2009-12-21 09:10:06.000000000 -0500 @@ -53,7 +53,7 @@ # include <sys/mman.h> #endif -#define MOD_TLS_VERSION "mod_tls/2.2.1" +#define MOD_TLS_VERSION "mod_tls/2.2.2" /* Make sure the version of proftpd is as necessary. */ #if PROFTPD_VERSION_NUMBER < 0x0001021001 @@ -368,16 +368,16 @@ static int tls_required_on_data = 0; static unsigned char *tls_authenticated = NULL; /* mod_tls session flags */ -#define TLS_SESS_ON_CTRL 0x0001 -#define TLS_SESS_ON_DATA 0x0002 -#define TLS_SESS_PBSZ_OK 0x0004 -#define TLS_SESS_TLS_REQUIRED 0x0010 -#define TLS_SESS_VERIFY_CLIENT 0x0020 -#define TLS_SESS_NO_PASSWD_NEEDED 0x0040 -#define TLS_SESS_NEED_DATA_PROT 0x0100 -#define TLS_SESS_CTRL_RENEGOTIATING 0x0200 -#define TLS_SESS_DATA_RENEGOTIATING 0x0400 -#define TLS_SESS_HAVE_CCC 0x0800 +#define TLS_SESS_ON_CTRL 0x0001 +#define TLS_SESS_ON_DATA 0x0002 +#define TLS_SESS_PBSZ_OK 0x0004 +#define TLS_SESS_TLS_REQUIRED 0x0010 +#define TLS_SESS_VERIFY_CLIENT 0x0020 +#define TLS_SESS_NO_PASSWD_NEEDED 0x0040 +#define TLS_SESS_NEED_DATA_PROT 0x0100 +#define TLS_SESS_CTRL_RENEGOTIATING 0x0200 +#define TLS_SESS_DATA_RENEGOTIATING 0x0400 +#define TLS_SESS_HAVE_CCC 0x0800 /* mod_tls option flags */ #define TLS_OPT_NO_CERT_REQUEST 0x0001 @@ -388,6 +388,7 @@ static unsigned char *tls_authenticated #define TLS_OPT_STD_ENV_VARS 0x0020 #define TLS_OPT_ALLOW_PER_USER 0x0040 #define TLS_OPT_ENABLE_DIAGS 0x0080 +#define TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS 0x0400 /* mod_tls cleanup flags */ #define TLS_CLEANUP_FL_SESS_INIT 0x0001 @@ -465,11 +466,15 @@ static int tls_verify_crl(int, X509_STOR static int tls_verify_ocsp(int, X509_STORE_CTX *); static char *tls_x509_name_oneline(X509_NAME *); +static int tls_need_init_handshake = TRUE; + static void tls_diags_cb(const SSL *ssl, int where, int ret) { - const char *str; + const char *str = "(unknown)"; int w; - w = where &~ SSL_ST_MASK; + pr_signals_handle(); + + w = where & ~SSL_ST_MASK; if (w & SSL_ST_CONNECT) { str = "connecting"; @@ -478,25 +483,138 @@ static void tls_diags_cb(const SSL *ssl, str = "accepting"; } else { - str = "(unknown)"; + int ssl_state; + + ssl_state = SSL_get_state(ssl); + if (ssl_state == SSL_ST_OK) { + str = "ok"; + } } - if (where & SSL_CB_LOOP) { - tls_log("[info] %s: %s", str, SSL_state_string_long(ssl)); + if (where & SSL_CB_ACCEPT_LOOP) { + int ssl_state; + + ssl_state = SSL_get_state(ssl); + + if (ssl_state == SSL3_ST_SR_CLNT_HELLO_A || + ssl_state == SSL23_ST_SR_CLNT_HELLO_A) { + + /* If we have already completed our initial handshake, then this might + * a session renegotiation. + */ + if (!tls_need_init_handshake) { + + /* Yes, this is indeed a session renegotiation. If it's a + * renegotiation that we requested, allow it. Otherwise, it's a + * client-initiated renegotiation, and we probably don't want to + * allow it. + */ + + if (!(tls_flags & TLS_SESS_CTRL_RENEGOTIATING) && + !(tls_flags & TLS_SESS_DATA_RENEGOTIATING)) { + + if (!(tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS)) { + tls_log("warning: client-initiated session renegotiation " + "detected, aborting connection"); + pr_log_pri(PR_LOG_NOTICE, MOD_TLS_VERSION + ": client-initiated session renegotiation detected, " + "aborting connection"); + + tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0); + tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data = + ctrl_ssl = NULL; + + end_login(1); + } + } + } + +#if OPENSSL_VERSION_NUMBER >= 0x009080cfL + } else if (ssl_state & SSL_ST_RENEGOTIATE) { + if (!tls_need_init_handshake) { + + if (ssl == ctrl_ssl && + !(tls_flags & TLS_SESS_CTRL_RENEGOTIATING) && + !(tls_flags & TLS_SESS_DATA_RENEGOTIATING)) { + + /* In OpenSSL-0.9.8l and later, SSL session renegotiations are + * automatically disabled. Thus if the admin has not explicitly + * configured support for client-initiated renegotations via the + * AllowClientRenegotiations TLSOption, then we need to disconnect + * the client here. Otherwise, the client would hang (up to the + * TLSTimeoutHandshake limit). Since we know, right now, that the + * handshake won't succeed, just drop the connection. + */ + + if (!(tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS)) { + tls_log("warning: client-initiated session renegotiation detected, " + "aborting connection"); + pr_log_pri(PR_LOG_NOTICE, MOD_TLS_VERSION + ": client-initiated session renegotiation detected, " + "aborting connection"); + + tls_end_sess(ctrl_ssl, PR_NETIO_STRM_CTRL, 0); + tls_ctrl_rd_nstrm->strm_data = tls_ctrl_wr_nstrm->strm_data = + ctrl_ssl = NULL; + + end_login(1); + } + } + } +#endif + } + + if (tls_opts & TLS_OPT_ENABLE_DIAGS) { + tls_log("[info] %s: %s", str, SSL_state_string_long(ssl)); + } + + } else if (where & SSL_CB_HANDSHAKE_DONE) { + if (!tls_need_init_handshake) { + /* If this is an accepted renegotiation, log the possibly-changed + * ciphersuite et al. + */ + tls_log("%s renegotiation accepted, using cipher %s (%d bits)", + SSL_get_cipher_version(ssl), SSL_get_cipher_name(ssl), + SSL_get_cipher_bits(ssl, NULL)); + } + + tls_need_init_handshake = FALSE; + + /* Clear the flags set for server-requested renegotiations. */ + if (tls_flags & TLS_SESS_CTRL_RENEGOTIATING) { + tls_flags &= ~TLS_SESS_CTRL_RENEGOTIATING; + } + + if (tls_flags & ~TLS_SESS_DATA_RENEGOTIATING) { + tls_flags &= ~TLS_SESS_DATA_RENEGOTIATING; + } + + if (tls_opts & TLS_OPT_ENABLE_DIAGS) { + tls_log("[info] %s: %s", str, SSL_state_string_long(ssl)); + } + + } else if (where & SSL_CB_LOOP) { + if (tls_opts & TLS_OPT_ENABLE_DIAGS) { + tls_log("[info] %s: %s", str, SSL_state_string_long(ssl)); + } } else if (where & SSL_CB_ALERT) { - str = (where & SSL_CB_READ) ? "reading" : "writing"; - tls_log("[info] %s: SSL/TLS alert %s: %s", str, - SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + if (tls_opts & TLS_OPT_ENABLE_DIAGS) { + str = (where & SSL_CB_READ) ? "reading" : "writing"; + tls_log("[info] %s: SSL/TLS alert %s: %s", str, + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + } } else if (where & SSL_CB_EXIT) { - if (ret == 0) { - tls_log("[info] %s: failed in %s: %s", str, + if (tls_opts & TLS_OPT_ENABLE_DIAGS) { + if (ret == 0) { + tls_log("[info] %s: failed in %s: %s", str, SSL_state_string_long(ssl), tls_get_errors()); - - } else if (ret < 0 && errno != 0) { - tls_log("[info] %s: error in %s (errno %d: %s)", - str, SSL_state_string_long(ssl), errno, strerror(errno)); + + } else if (ret < 0 && errno != 0) { + tls_log("[info] %s: error in %s (errno %d: %s)", + str, SSL_state_string_long(ssl), errno, strerror(errno)); + } } } } @@ -1520,17 +1638,30 @@ static int tls_renegotiate_timeout_cb(CA static int tls_ctrl_renegotiate_cb(CALLBACK_FRAME) { if (tls_flags & TLS_SESS_ON_CTRL) { - tls_log("%s", "requesting TLS renegotiation on control channel"); - SSL_renegotiate(ctrl_ssl); - /* SSL_do_handshake(ctrl_ssl); */ - pr_timer_add(tls_renegotiate_timeout, 0, &tls_module, - tls_renegotiate_timeout_cb, "SSL/TLS renegotation"); + if (TRUE +#if OPENSSL_VERSION_NUMBER >= 0x009080cfL + /* In OpenSSL-0.9.8l and later, SSL session renegotiations + * (both client- and server-initiated) are automatically disabled. + * Unless the admin explicitly configured support for + * client-initiated renegotations via the AllowClientRenegotiations + * TLSOption, we can't request renegotiations ourselves. + */ + && (tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS) +#endif + ) { + tls_flags |= TLS_SESS_CTRL_RENEGOTIATING; - tls_flags |= TLS_SESS_CTRL_RENEGOTIATING; + tls_log("%s", "requesting TLS renegotiation on control channel"); + SSL_renegotiate(ctrl_ssl); + /* SSL_do_handshake(ctrl_ssl); */ - /* Restart the timer. */ - return 1; + pr_timer_add(tls_renegotiate_timeout, 0, &tls_module, + tls_renegotiate_timeout_cb, "SSL/TLS renegotation"); + + /* Restart the timer. */ + return 1; + } } return 0; @@ -2122,6 +2253,17 @@ static int tls_accept(conn_t *conn, unsi } else if (conn == session.d) tls_data_rd_nstrm->strm_data = tls_data_wr_nstrm->strm_data = (void *) ssl; +#if OPENSSL_VERSION_NUMBER == 0x009080cfL + /* In OpenSSL-0.9.8l, SSL session renegotiations are automatically + * disabled. Thus if the admin explicitly configured support for + * client-initiated renegotations via the AllowClientRenegotiations + * TLSOption, then we need to do some hackery to enable renegotiations. + */ + if (tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS) { + ssl->s3->flags |= SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; + } +#endif + /* TLS handshake on the control channel... */ if (!on_data) { tls_log("%s connection accepted, using cipher %s (%d bits)", @@ -3038,7 +3180,7 @@ static void tls_setup_environ(SSL *ssl) } /* Process the SSL cipher-related environ variables. */ - cipher = SSL_get_current_cipher(ssl); + cipher = (SSL_CIPHER *) SSL_get_current_cipher(ssl); if (cipher) { char buf[10] = {'\0'}; int cipher_bits_used = 0, cipher_bits_possible = 0; @@ -4089,15 +4231,27 @@ static int tls_netio_write_cb(pr_netio_s #if OPENSSL_VERSION_NUMBER > 0x000907000L if (tls_data_renegotiate_limit && - session.xfer.total_bytes >= tls_data_renegotiate_limit) { + session.xfer.total_bytes >= tls_data_renegotiate_limit + +#if OPENSSL_VERSION_NUMBER >= 0x009080cfL + /* In OpenSSL-0.9.8l and later, SSL session renegotiations + * (both client- and server-initiated) are automatically disabled. + * Unless the admin explicitly configured support for + * client-initiated renegotations via the AllowClientRenegotiations + * TLSOption, we can't request renegotiations ourselves. + */ + && (tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS) +#endif + ) { + + tls_flags |= TLS_SESS_DATA_RENEGOTIATING; + tls_log("%s", "requesting TLS renegotiation on data channel"); SSL_renegotiate((SSL *) nstrm->strm_data); /* SSL_do_handshake((SSL *) nstrm->strm_data); */ pr_timer_add(tls_renegotiate_timeout, 0, &tls_module, tls_renegotiate_timeout_cb, "SSL/TLS renegotiation"); - - tls_flags |= TLS_SESS_DATA_RENEGOTIATING; } #endif @@ -4893,6 +5047,10 @@ MODRET set_tlsoptions(cmd_rec *cmd) { } else if (strcmp(cmd->argv[i], "AllowPerUser") == 0) { opts |= TLS_OPT_ALLOW_PER_USER; + } else if (strcmp(cmd->argv[i], "AllowClientRenegotiation") == 0 || + strcmp(cmd->argv[i], "AllowClientRenegotiations") == 0) { + opts |= TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS; + } else if (strcmp(cmd->argv[i], "EnableDiags") == 0) { opts |= TLS_OPT_ENABLE_DIAGS; @@ -5490,6 +5648,12 @@ static int tls_sess_init(void) { unsigned long *opts = NULL; config_rec *c = NULL; + /* Unregister the listener for the 'core.exit' event that was registered + * for the daemon process; we inherited it due to the fork, but we don't + * want that listener being invoked when we exit. + */ + pr_event_unregister(&tls_module, "core.exit", tls_daemon_exit_ev); + /* First, check to see whether mod_tls is even enabled. */ tmp = get_param_ptr(main_server->conf, "TLSEngine", FALSE); if (tmp != NULL && @@ -5538,6 +5702,21 @@ static int tls_sess_init(void) { if (opts != NULL) tls_opts = *opts; +#if OPENSSL_VERSION_NUMBER > 0x009080cfL + /* The OpenSSL team realized that the flag added in 0.9.8l, the + * SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION flag, was a bad idea. + * So in later versions, it was changed to a context flag, + * SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION. + */ + if (tls_opts & TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS) { + int ssl_opts; + + ssl_opts = SSL_CTX_get_options(ssl_ctx); + ssl_opts |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; + SSL_CTX_set_options(ssl_ctx, ssl_opts); + } +#endif + tmp = get_param_ptr(main_server->conf, "TLSVerifyClient", FALSE); if (tmp!= NULL && *tmp == TRUE) { @@ -5634,14 +5813,20 @@ static int tls_sess_init(void) { SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *) tls_pkey); } - /* Install a callback for logging OpenSSL diagnostic information, if - * requested. + /* We always install an info callback, in order to watch for + * client-initiated session renegotiations (Bug#3324). If EnableDiags + * is enabled, that info callback will also log the OpenSSL diagnostic + * information. + */ + SSL_CTX_set_info_callback(ssl_ctx, tls_diags_cb); + +#if OPENSSL_VERSION_NUMBER > 0x000907000L + /* Install a callback for logging OpenSSL message information, + * if requested. */ if (tls_opts & TLS_OPT_ENABLE_DIAGS) { tls_log("%s", "TLSOption EnableDiags enabled, setting diagnostics callback"); - SSL_CTX_set_info_callback(ssl_ctx, tls_diags_cb); -#if OPENSSL_VERSION_NUMBER > 0x000907000L SSL_CTX_set_msg_callback(ssl_ctx, tls_msg_cb); #endif }