diff --git a/contrib/mod_tls.c b/contrib/mod_tls.c index 912cfa4c8e..eab957cdea 100644 --- a/contrib/mod_tls.c +++ b/contrib/mod_tls.c @@ -7828,7 +7828,7 @@ static int tls_accept(conn_t *conn, unsigned char on_data) { ssl_opts = SSL_get_options(ssl); -#if defined(SSL_OP_NO_SSLv2) +#if SSL_OP_NO_SSLv2 if (ssl_opts & SSL_OP_NO_SSLv2) { proto_str = pstrcat(tmp_pool, proto_str, *proto_str ? ", " : "", "SSLv2", NULL); @@ -7902,6 +7902,60 @@ static int tls_accept(conn_t *conn, unsigned char on_data) { break; } +#if defined(SSL_R_VERSION_TOO_LOW) + case SSL_R_VERSION_TOO_LOW: { + int client_version; + + client_version = SSL_client_version(ssl); + switch (client_version) { +# if defined(SSL3_VERSION) && defined(OPENSSL_NO_SSL3) + case SSL3_VERSION: + tls_log("%s: %s lacks support for client requested TLS " + "protocol version: %s", msg, OPENSSL_VERSION_TEXT, + SSL_get_version(ssl)); + break; +# endif /* SSLv3 and OPENSSL_NO_SSL3 */ + +# if defined(TLS1_VERSION) && defined(OPENSSL_NO_TLS1) + case TLS1_VERSION: + tls_log("%s: %s lacks support for client requested TLS " + "protocol version: %s", msg, OPENSSL_VERSION_TEXT, + SSL_get_version(ssl)); + break; +# endif /* TLSv1 and OPENSSL_NO_TLS1 */ + +# if defined(TLS1_1_VERSION) && defined(OPENSSL_NO_TLS1_1) + case TLS1_1_VERSION: + tls_log("%s: %s lacks support for client requested TLS " + "protocol version: %s", msg, OPENSSL_VERSION_TEXT, + SSL_get_version(ssl)); + break; +# endif /* TLSv1.1 and OPENSSL_NO_TLS1_1 */ + +# if defined(TLS1_2_VERSION) && defined(OPENSSL_NO_TLS1_2) + case TLS1_2_VERSION: + tls_log("%s: %s lacks support for client requested TLS " + "protocol version: %s", msg, OPENSSL_VERSION_TEXT, + SSL_get_version(ssl)); + break; +# endif /* TLSv1.2 and OPENSSL_NO_TLS1_2 */ + +# if defined(TLS1_3_VERSION) && defined(OPENSSL_NO_TLS1_3) + case TLS1_3_VERSION: + tls_log("%s: %s lacks support for client requested TLS " + "protocol version: %s", msg, OPENSSL_VERSION_TEXT, + SSL_get_version(ssl)); + break; +# endif /* TLSv1.3 and OPENSSL_NO_TLS1_3 */ + + default: + tls_log("%s: perhaps client requested unsupported TLS protocol " + "version: %s", msg, SSL_get_version(ssl)); + } + break; + } +#endif /* SSL_R_VERSION_TOO_LOW */ + default: break; } diff --git a/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm b/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm index c06d02c4b5..d5d0a825e9 100644 --- a/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm +++ b/tests/t/lib/ProFTPD/Tests/Modules/mod_tls.pm @@ -9,6 +9,7 @@ use File::Copy; use File::Path qw(mkpath); use File::Spec; use IO::Handle; +use IPC::Open3; use Socket; use ProFTPD::TestSuite::FTP; @@ -499,6 +500,10 @@ my $TESTS = { test_class => [qw(bug forking)], }, + tls_old_protocols_issue1273 => { + order => ++$order, + test_class => [qw(bug forking)], + }, }; sub new { @@ -14237,4 +14242,175 @@ sub tls_fxp_issue618 { test_cleanup($setup->{log_file}, $ex); } +sub tls_old_protocols_issue1273 { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'tls'); + + my $cert_file = File::Spec->rel2abs('t/etc/modules/mod_tls/server-cert.pem'); + my $ca_file = File::Spec->rel2abs('t/etc/modules/mod_tls/ca-cert.pem'); + + my $tls_opts = 'NoSessionReuseRequired UseImplicitSSL'; + if ($ENV{TEST_VERBOSE}) { + $tls_opts .= ' EnableDiags'; + } + + my $timeout_idle = 15; + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'command:20 response:20 data:20 netio:20 tls:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + + AllowForeignAddress => 'on', + AllowOverwrite => 'on', + TimeoutIdle => $timeout_idle, + + IfModules => { + 'mod_delay.c' => { + DelayEngine => 'off', + }, + + 'mod_tls.c' => { + TLSEngine => 'on', + TLSLog => $setup->{log_file}, + TLSProtocol => 'SSLv23', + TLSRequired => 'on', + TLSRSACertificateFile => $cert_file, + TLSCACertificateFile => $ca_file, + TLSOptions => $tls_opts, + }, + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + # Open pipes, for use between the parent and child processes. Specifically, + # the child will indicate when it's done with its test by writing a message + # to the parent. + my ($rfh, $wfh); + unless (pipe($rfh, $wfh)) { + die("Can't open pipe: $!"); + } + + my $ex; + + # Fork child + $self->handle_sigchld(); + defined(my $pid = fork()) or die("Can't fork: $!"); + if ($pid) { + eval { + sleep(2); + + # We use an older OpenSSL version for the older protocols. + # Allow server to start up + my $openssl = '/Users/tj/local/openssl-0.9.8d/bin/openssl'; + + # Explicitly use SSLv3, which has been disabled by default in + # OpenSSL-1.1.x; see: + # https://github.com/openssl/openssl/issues/4989 + + my @cmd = ( + $openssl, + 's_client', + '-connect', + "127.0.0.1:$port", + '-ssl3', + ); + + my $tls_rh = IO::Handle->new(); + my $tls_wh = IO::Handle->new(); + my $tls_eh = IO::Handle->new(); + + $tls_wh->autoflush(1); + + local $SIG{CHLD} = 'DEFAULT'; + + if ($ENV{TEST_VERBOSE}) { + print STDERR "Executing: ", join(' ', @cmd), "\n"; + } + + my $tls_pid = open3($tls_wh, $tls_rh, $tls_eh, @cmd); + print $tls_wh "quit\n"; + waitpid($tls_pid, 0); + + my ($res, $cipher_str, $err_str, $out_str); + if ($? >> 8) { + $err_str = join('', <$tls_eh>); + $res = 0; + + } else { + my $output = [<$tls_rh>]; + + if ($ENV{TEST_VERBOSE}) { + $out_str = join('', @$output); + print STDERR "Stdout: $out_str\n"; + + $err_str = join('', <$tls_eh>); + print STDERR "Stderr: $err_str\n"; + } + + $res = 1; + } + + unless ($res) { + die("Can't talk to server: $err_str"); + } + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 5) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + test_cleanup($setup->{log_file}, $ex) if $ex; + + eval { + if (open(my $fh, "< $setup->{log_file}")) { + my $seen = 0; + + while (my $line = <$fh>) { + chomp($line); + + if ($line =~ /OpenSSL.*?lacks support for client requested/) { + $seen = 1; + last; + } + } + + close($fh); + + $self->assert($seen, test_msg("Did not see expected log message")); + + } else { + die("Can't read $setup->{log_file}: $!"); + } + }; + if ($@) { + $ex = $@; + } + + test_cleanup($setup->{log_file}, $ex); +} + 1;