diff --git a/mod_proxy.c b/mod_proxy.c index 4bb368e..6cc25ed 100644 --- a/mod_proxy.c +++ b/mod_proxy.c @@ -1,6 +1,6 @@ /* * ProFTPD - mod_proxy - * Copyright (c) 2012-2022 TJ Saunders + * Copyright (c) 2012-2023 TJ Saunders * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -2369,26 +2369,10 @@ static int proxy_data_handle_resp(pool *p, struct proxy_session *proxy_sess, return 0; } -static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, - cmd_rec *cmd, conn_t **frontend, conn_t **backend) { - int res, xerrno = 0; - conn_t *frontend_conn = NULL, *backend_conn = NULL; - - res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, - cmd); - if (res < 0) { - xerrno = errno; - (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, - "error sending %s to backend: %s", (char *) cmd->argv[0], - strerror(xerrno)); - - pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], - strerror(xerrno)); - pr_response_flush(&resp_err_list); - - errno = xerrno; - return -1; - } +static int proxy_data_prepare_backend_conn(struct proxy_session *proxy_sess, + cmd_rec *cmd, conn_t **backend) { + int res, xerrno; + conn_t *backend_conn = NULL; /* XXX Should handle EPSV_ALL here, too. */ if (proxy_sess->backend_sess_flags & SF_PASSIVE) { @@ -2513,6 +2497,17 @@ static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, proxy_sess->backend_data_conn = backend_conn; + /* Note that we delay the post-open on the backend data connection until + * after we have received the backend response. In the case of a 4xx/5xx + * response, then such a post-open (as for a TLS handshake) will fail + * (Issue #244). + */ + + res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd); + if (res < 0) { + return -1; + } + if (proxy_netio_postopen(backend_conn->instrm) < 0) { xerrno = errno; @@ -2541,11 +2536,6 @@ static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, return -1; } - res = proxy_data_handle_resp(cmd->tmp_pool, proxy_sess, cmd); - if (res < 0) { - return -1; - } - (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, "passive backend data connection opened - local : %s:%d", pr_netaddr_get_ipstr(backend_conn->local_addr), @@ -2628,7 +2618,14 @@ static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, backend_conn->remote_port); } - /* Now establish a data connection with the frontend client. */ + *backend = backend_conn; + return 0; +} + +static int proxy_data_prepare_frontend_conn(struct proxy_session *proxy_sess, + cmd_rec *cmd, conn_t **frontend) { + int xerrno; + conn_t *frontend_conn = NULL; if (proxy_sess->frontend_sess_flags & SF_PASSIVE) { pr_trace_msg(trace_channel, 17, @@ -2775,7 +2772,43 @@ static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, } *frontend = frontend_conn; - *backend = backend_conn; + return 0; +} + +static int proxy_data_prepare_conns(struct proxy_session *proxy_sess, + cmd_rec *cmd, conn_t **frontend, conn_t **backend) { + int res, xerrno = 0; + + /* First we proxy the command to the backend server, which starts the + * process. + */ + + res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, + cmd); + if (res < 0) { + xerrno = errno; + (void) pr_log_writefile(proxy_logfd, MOD_PROXY_VERSION, + "error sending %s to backend: %s", (char *) cmd->argv[0], + strerror(xerrno)); + + pr_response_add_err(R_500, _("%s: %s"), (char *) cmd->argv[0], + strerror(xerrno)); + pr_response_flush(&resp_err_list); + + errno = xerrno; + return -1; + } + + res = proxy_data_prepare_backend_conn(proxy_sess, cmd, backend); + if (res < 0) { + return -1; + } + + res = proxy_data_prepare_frontend_conn(proxy_sess, cmd, frontend); + if (res < 0) { + return -1; + } + return 0; } @@ -3274,7 +3307,8 @@ MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) { * a failed transfer. */ /* XXX What about ABOR/aborted transfers? */ - if (resp->num[0] == '4' || resp->num[0] == '5') { + if (resp->num[0] == '4' || + resp->num[0] == '5') { xfer_ok = FALSE; } } diff --git a/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm b/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm index 1ea8d22..aeeafde 100644 --- a/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm +++ b/t/lib/ProFTPD/Tests/Modules/mod_proxy/ssh.pm @@ -9487,7 +9487,7 @@ EOC if ($pid) { eval { # Give the server a chance to start up - sleep(2); + sleep(5); for (my $i = 0; $i < 3; $i++) { my $ssh2 = Net::SSH2->new(); diff --git a/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm b/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm index ac17a55..a617d4d 100644 --- a/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm +++ b/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm @@ -108,6 +108,21 @@ my $TESTS = { test_class => [qw(forking mod_tls reverse)], }, + proxy_reverse_frontend_backend_tls_list_port => { + order => ++$order, + test_class => [qw(forking mod_tls reverse)], + }, + + proxy_reverse_frontend_backend_tls_nlst_pasv_error_issue244 => { + order => ++$order, + test_class => [qw(bug forking mod_tls reverse)], + }, + + proxy_reverse_frontend_backend_tls_nlst_port_error_issue244 => { + order => ++$order, + test_class => [qw(bug forking mod_tls reverse)], + }, + proxy_reverse_frontend_backend_tls_abort => { order => ++$order, test_class => [qw(forking mod_tls reverse)], @@ -1203,7 +1218,6 @@ EOC } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); - unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } @@ -1217,7 +1231,7 @@ EOC die("LIST failed unexpectedly: " . $client->last_message() . "(" . IO::Socket::SSL::errstr() . ")"); } - + my $resp_msg = $client->last_message(); $client->quit(); @@ -3100,16 +3114,685 @@ sub proxy_reverse_frontend_backend_tls_peruser_login_after_host { TraceLog => $log_file, Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', - AuthUserFile => $auth_user_file, - AuthGroupFile => $auth_group_file, - DefaultServer => 'on', - ServerName => '"Default Server"', + AuthUserFile => $auth_user_file, + AuthGroupFile => $auth_group_file, + DefaultServer => 'on', + ServerName => '"Default Server"', + SocketBindTight => 'on', + + IfModules => { + 'mod_tls.c' => { + TLSEngine => 'on', + TLSLog => $log_file, + TLSRequired => 'on', + TLSRSACertificateFile => $cert_file, + TLSCACertificateFile => $ca_file, + TLSOptions => 'NoSessionReuseRequired EnableDiags', + TLSTimeoutHandshake => 5, + TLSVerifyClient => 'off', + TLSVerifyServer => 'off', + }, + + 'mod_delay.c' => { + DelayEngine => 'off', + }, + }, + + Limit => { + LOGIN => { + DenyUser => $user, + }, + }, + }; + + my ($port, $config_user, $config_group) = config_write($config_file, $config); + + if (open(my $fh, ">> $config_file")) { + my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); + my $vhost_port2 = $vhost_port - 7; + + print $fh < + ProxyTables $tables_dir + + + + Port $port + ServerAlias $host + ServerName "Namebased Server" + + AuthUserFile $auth_user_file + AuthGroupFile $auth_group_file + AuthOrder mod_auth_file.c + + + DelayEngine off + + + + ProxyEngine on + ProxyLog $log_file + ProxyRole reverse + ProxyReverseConnectPolicy PerUser + ProxyReverseServers ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2 + ProxyTimeoutConnect 1sec + ProxyTLSEngine auto + ProxyTLSCACertificateFile $ca_file + ProxyTLSOptions EnableDiags + ProxyTLSVerifyServer off + + + + TLSEngine on + TLSLog $log_file + TLSRequired on + TLSRSACertificateFile $cert_file + TLSCACertificateFile $ca_file + + TLSVerifyClient off + TLSVerifyServer off + TLSOptions EnableDiags NoSessionReuseRequired + + + + + Port $vhost_port + ServerName "Real Server" + + AuthUserFile $auth_user_file + AuthGroupFile $auth_group_file + AuthOrder mod_auth_file.c + + AllowOverride off + WtmpLog off + TransferLog none + + + TLSEngine on + TLSLog $log_file + TLSRequired on + TLSRSACertificateFile $cert_file + TLSCACertificateFile $ca_file + + TLSVerifyClient off + TLSVerifyServer off + TLSOptions EnableDiags NoSessionReuseRequired + + + + + Port $vhost_port2 + ServerName "Other Real Server" + + AuthUserFile $auth_user_file + AuthGroupFile $auth_group_file + AuthOrder mod_auth_file.c + + AllowOverride off + WtmpLog off + TransferLog none + + + TLSEngine on + TLSLog $log_file + TLSRequired on + TLSRSACertificateFile $cert_file + TLSCACertificateFile $ca_file + + TLSVerifyClient off + TLSVerifyServer off + TLSOptions EnableDiags NoSessionReuseRequired + + +EOC + unless (close($fh)) { + die("Can't write $config_file: $!"); + } + + } else { + die("Can't open $config_file: $!"); + } + + require Net::FTPSSL; + + # 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 { + # Give the server a chance to start up + sleep(2); + + my $ssl_opts = { + SSL_hostname => $host, + SSL_ca_file => $ca_file, + + # Yes, this is a deliberate choice. Sigh. + SSL_verifycn_scheme => 'none', + }; + + my $client_opts = { + Encryption => 'E', + Port => $port, + SSL_Client_Certificate => $ssl_opts, + }; + + if ($ENV{TEST_VERBOSE}) { + $client_opts->{Debug} = 1; + } + + my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); + unless ($client) { + die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); + } + + unless ($client->quot('HOST', $host)) { + die("HOST failed: " . $client->last_message()); + } + + unless ($client->login($user, $passwd)) { + die("Can't login: " . $client->last_message()); + } + + my $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + my $resp_msg = $client->last_message(); + my $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + + # Do the LIST again; there are some reports that a first transfer + # might succeed, but subsequent ones will fail. + $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + $resp_msg = $client->last_message(); + $client->quit(); + + $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($config_file, $rfh, 20) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($pid_file); + + $self->assert_child_ok($pid); + + if ($ex) { + test_append_logfile($log_file, $ex); + unlink($log_file); + + die($ex); + } + + unlink($log_file); +} + +sub proxy_reverse_frontend_backend_tls_list_pasv { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'proxy'); + + my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); + my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); + + my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); + $vhost_port += 12; + + my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, + $vhost_port); + $proxy_config->{ProxyTLSEngine} = 'auto'; + $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; + + if ($ENV{TEST_VERBOSE}) { + $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + AuthOrder => 'mod_auth_file.c', + + SocketBindTight => 'on', + + IfModules => { + 'mod_proxy.c' => $proxy_config, + + 'mod_tls.c' => { + TLSEngine => 'on', + TLSLog => $setup->{log_file}, + TLSRequired => 'on', + TLSRSACertificateFile => $cert_file, + TLSCACertificateFile => $ca_file, + TLSOptions => 'NoSessionReuseRequired EnableDiags', + TLSTimeoutHandshake => 5, + TLSVerifyClient => 'off', + TLSVerifyServer => 'off', + }, + + 'mod_delay.c' => { + DelayEngine => 'off', + }, + }, + + Limit => { + LOGIN => { + DenyUser => $setup->{user}, + }, + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + if (open(my $fh, ">> $setup->{config_file}")) { + print $fh < + Port $vhost_port + ServerName "Real Server" + + AuthUserFile $setup->{auth_user_file} + AuthGroupFile $setup->{auth_group_file} + AuthOrder mod_auth_file.c + + AllowOverride off + WtmpLog off + TransferLog none + + + TLSEngine on + TLSLog $setup->{log_file} + TLSRequired on + TLSRSACertificateFile $cert_file + TLSCACertificateFile $ca_file + + TLSVerifyClient off + TLSVerifyServer off + TLSOptions EnableDiags NoSessionReuseRequired + + +EOC + unless (close($fh)) { + die("Can't write $setup->{config_file}: $!"); + } + + } else { + die("Can't open $setup->{config_file}: $!"); + } + + require Net::FTPSSL; + + # 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 { + # Give the server a chance to start up + sleep(2); + + my $ssl_opts = { + SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, + }; + + my $client_opts = { + Encryption => 'E', + Port => $port, + SSL_Client_Certificate => $ssl_opts, + }; + + if ($ENV{TEST_VERBOSE}) { + $client_opts->{Debug} = 1; + } + + my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); + unless ($client) { + die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); + } + + unless ($client->login($setup->{user}, $setup->{passwd})) { + die("Can't login: " . $client->last_message()); + } + + my $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + my $resp_msg = $client->last_message(); + my $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + + # Do the LIST again; there are some reports that a first transfer + # might succeed, but subsequent ones will fail. + for (my $i = 0; $i < 3; $i++) { + $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + $resp_msg = $client->last_message(); + + $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + } + + $client->quit(); + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh, 20) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub proxy_reverse_frontend_backend_tls_list_port { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'proxy'); + + my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); + my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); + + my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); + $vhost_port += 12; + + my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, + $vhost_port); + $proxy_config->{ProxyTLSEngine} = 'auto'; + $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; + + # For this test, we want to force the use of PORT for the backend data + # transfers. + $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; + + if ($ENV{TEST_VERBOSE}) { + $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + AuthOrder => 'mod_auth_file.c', + + SocketBindTight => 'on', + + IfModules => { + 'mod_proxy.c' => $proxy_config, + + 'mod_tls.c' => { + TLSEngine => 'on', + TLSLog => $setup->{log_file}, + TLSRequired => 'on', + TLSRSACertificateFile => $cert_file, + TLSCACertificateFile => $ca_file, + TLSOptions => 'NoSessionReuseRequired EnableDiags', + TLSTimeoutHandshake => 5, + TLSVerifyClient => 'off', + TLSVerifyServer => 'off', + }, + + 'mod_delay.c' => { + DelayEngine => 'off', + }, + }, + + Limit => { + LOGIN => { + DenyUser => $setup->{user}, + }, + }, + }; + + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + + if (open(my $fh, ">> $setup->{config_file}")) { + print $fh < + Port $vhost_port + ServerName "Real Server" + + AuthUserFile $setup->{auth_user_file} + AuthGroupFile $setup->{auth_group_file} + AuthOrder mod_auth_file.c + + AllowOverride off + WtmpLog off + TransferLog none + + + TLSEngine on + TLSLog $setup->{log_file} + TLSRequired on + TLSRSACertificateFile $cert_file + TLSCACertificateFile $ca_file + + TLSVerifyClient off + TLSVerifyServer off + TLSOptions EnableDiags NoSessionReuseRequired + + +EOC + unless (close($fh)) { + die("Can't write $setup->{config_file}: $!"); + } + + } else { + die("Can't open $setup->{config_file}: $!"); + } + + require Net::FTPSSL; + + # 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 { + # Give the server a chance to start up + sleep(2); + + my $ssl_opts = { + SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, + }; + + my $client_opts = { + Encryption => 'E', + Port => $port, + SSL_Client_Certificate => $ssl_opts, + }; + + if ($ENV{TEST_VERBOSE}) { + $client_opts->{Debug} = 1; + } + + my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); + unless ($client) { + die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); + } + + unless ($client->login($setup->{user}, $setup->{passwd})) { + die("Can't login: " . $client->last_message()); + } + + my $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + my $resp_msg = $client->last_message(); + my $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + + # Do the LIST again; there are some reports that a first transfer + # might succeed, but subsequent ones will fail. + for (my $i = 0; $i < 3; $i++) { + $res = $client->list(); + unless ($res) { + die("LIST failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + $resp_msg = $client->last_message(); + + $expected = '226 Transfer complete'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); + } + + $client->quit(); + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh, 20) }; + if ($@) { + warn($@); + exit 1; + } + + exit 0; + } + + # Stop server + server_stop($setup->{pid_file}); + $self->assert_child_ok($pid); + + test_cleanup($setup->{log_file}, $ex); +} + +sub proxy_reverse_frontend_backend_tls_nlst_pasv_error_issue244 { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'proxy'); + + my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); + my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); + + my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); + $vhost_port += 12; + + my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, + $vhost_port); + $proxy_config->{ProxyTLSEngine} = 'auto'; + $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; + + if ($ENV{TEST_VERBOSE}) { + $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; + } + + my $config = { + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'netio:20 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.ftp.xfer:20 tls:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + AuthOrder => 'mod_auth_file.c', + SocketBindTight => 'on', IfModules => { + 'mod_proxy.c' => $proxy_config, + 'mod_tls.c' => { TLSEngine => 'on', - TLSLog => $log_file, + TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, @@ -3126,92 +3809,22 @@ sub proxy_reverse_frontend_backend_tls_peruser_login_after_host { Limit => { LOGIN => { - DenyUser => $user, + DenyUser => $setup->{user}, }, }, }; - my ($port, $config_user, $config_group) = config_write($config_file, $config); - - if (open(my $fh, ">> $config_file")) { - my $tables_dir = File::Spec->rel2abs("$tmpdir/var/proxy"); - my $vhost_port2 = $vhost_port - 7; + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); + if (open(my $fh, ">> $setup->{config_file}")) { print $fh < - ProxyTables $tables_dir - - - - Port $port - ServerAlias $host - ServerName "Namebased Server" - - AuthUserFile $auth_user_file - AuthGroupFile $auth_group_file - AuthOrder mod_auth_file.c - - - DelayEngine off - - - - ProxyEngine on - ProxyLog $log_file - ProxyRole reverse - ProxyReverseConnectPolicy PerUser - ProxyReverseServers ftp://127.0.0.1:$vhost_port ftp://127.0.0.1:$vhost_port2 - ProxyTimeoutConnect 1sec - ProxyTLSEngine auto - ProxyTLSCACertificateFile $ca_file - ProxyTLSOptions EnableDiags - ProxyTLSVerifyServer off - - - - TLSEngine on - TLSLog $log_file - TLSRequired on - TLSRSACertificateFile $cert_file - TLSCACertificateFile $ca_file - - TLSVerifyClient off - TLSVerifyServer off - TLSOptions EnableDiags NoSessionReuseRequired - - - Port $vhost_port ServerName "Real Server" - AuthUserFile $auth_user_file - AuthGroupFile $auth_group_file - AuthOrder mod_auth_file.c - - AllowOverride off - WtmpLog off - TransferLog none - - - TLSEngine on - TLSLog $log_file - TLSRequired on - TLSRSACertificateFile $cert_file - TLSCACertificateFile $ca_file - - TLSVerifyClient off - TLSVerifyServer off - TLSOptions EnableDiags NoSessionReuseRequired - - - - - Port $vhost_port2 - ServerName "Other Real Server" - - AuthUserFile $auth_user_file - AuthGroupFile $auth_group_file + AuthUserFile $setup->{auth_user_file} + AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off @@ -3220,7 +3833,7 @@ sub proxy_reverse_frontend_backend_tls_peruser_login_after_host { TLSEngine on - TLSLog $log_file + TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file @@ -3232,11 +3845,11 @@ sub proxy_reverse_frontend_backend_tls_peruser_login_after_host { EOC unless (close($fh)) { - die("Can't write $config_file: $!"); + die("Can't write $setup->{config_file}: $!"); } } else { - die("Can't open $config_file: $!"); + die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; @@ -3260,11 +3873,7 @@ EOC sleep(2); my $ssl_opts = { - SSL_hostname => $host, - SSL_ca_file => $ca_file, - - # Yes, this is a deliberate choice. Sigh. - SSL_verifycn_scheme => 'none', + SSL_verify_mode => $IO::Socket::SSL::SSL_VERIFY_NONE, }; my $client_opts = { @@ -3282,39 +3891,36 @@ EOC die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } - unless ($client->quot('HOST', $host)) { - die("HOST failed: " . $client->last_message()); - } - - unless ($client->login($user, $passwd)) { + unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } - my $res = $client->list(); - unless ($res) { - die("LIST failed unexpectedly: " . $client->last_message() . - "(" . IO::Socket::SSL::errstr() . ")"); + my $bad_path = '/foo/bar/baz'; + + my $res = $client->nlst($bad_path); + if ($res) { + die("NLST $bad_path succeeded unexpectedly"); } my $resp_msg = $client->last_message(); - my $expected = '226 Transfer complete'; + my $expected = "450 $bad_path: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); - # Do the LIST again; there are some reports that a first transfer + # Do the NLST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. - $res = $client->list(); - unless ($res) { - die("LIST failed unexpectedly: " . $client->last_message() . - "(" . IO::Socket::SSL::errstr() . ")"); + for (my $i = 0; $i < 3; $i++) { + $res = $client->nlst($bad_path); + if ($res) { + die("NLST $bad_path succeeded unexpectedly"); + } + + $resp_msg = $client->last_message(); + $self->assert($expected eq $resp_msg, + test_msg("Expected response '$expected', got '$resp_msg'")); } - $resp_msg = $client->last_message(); $client->quit(); - - $expected = '226 Transfer complete'; - $self->assert($expected eq $resp_msg, - test_msg("Expected response '$expected', got '$resp_msg'")); }; if ($@) { $ex = $@; @@ -3324,7 +3930,7 @@ EOC $wfh->flush(); } else { - eval { server_wait($config_file, $rfh, 20) }; + eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; @@ -3334,55 +3940,16 @@ EOC } # Stop server - server_stop($pid_file); - + server_stop($setup->{pid_file}); $self->assert_child_ok($pid); - if ($ex) { - test_append_logfile($log_file, $ex); - unlink($log_file); - - die($ex); - } - - unlink($log_file); + test_cleanup($setup->{log_file}, $ex); } -sub proxy_reverse_frontend_backend_tls_list_pasv { +sub proxy_reverse_frontend_backend_tls_nlst_port_error_issue244 { my $self = shift; my $tmpdir = $self->{tmpdir}; - - my $config_file = "$tmpdir/proxy.conf"; - my $pid_file = File::Spec->rel2abs("$tmpdir/proxy.pid"); - my $scoreboard_file = File::Spec->rel2abs("$tmpdir/proxy.scoreboard"); - - my $log_file = test_get_logfile(); - - my $auth_user_file = File::Spec->rel2abs("$tmpdir/proxy.passwd"); - my $auth_group_file = File::Spec->rel2abs("$tmpdir/proxy.group"); - - my $user = 'proftpd'; - my $passwd = 'test'; - my $group = 'ftpd'; - my $home_dir = File::Spec->rel2abs($tmpdir); - my $uid = 500; - my $gid = 500; - - # Make sure that, if we're running as root, that the home directory has - # permissions/privs set for the account we create - if ($< == 0) { - unless (chmod(0755, $home_dir)) { - die("Can't set perms on $home_dir to 0755: $!"); - } - - unless (chown($uid, $gid, $home_dir)) { - die("Can't set owner of $home_dir to $uid/$gid: $!"); - } - } - - auth_user_write($auth_user_file, $user, $passwd, $uid, $gid, $home_dir, - '/bin/bash'); - auth_group_write($auth_group_file, $group, $gid, $user); + my $setup = test_setup($tmpdir, 'proxy'); my $cert_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/server-cert.pem"); my $ca_file = File::Spec->rel2abs("$ENV{PROFTPD_TEST_DIR}/t/etc/modules/mod_tls/ca-cert.pem"); @@ -3390,23 +3957,30 @@ sub proxy_reverse_frontend_backend_tls_list_pasv { my $vhost_port = ProFTPD::TestSuite::Utils::get_high_numbered_port(); $vhost_port += 12; - my $proxy_config = get_reverse_proxy_config($tmpdir, $log_file, $vhost_port); + my $proxy_config = get_reverse_proxy_config($tmpdir, $setup->{log_file}, + $vhost_port); $proxy_config->{ProxyTLSEngine} = 'auto'; $proxy_config->{ProxyTLSCACertificateFile} = $ca_file; + # For this test, we want to force the use of PORT for the backend data + # transfers. + $proxy_config->{ProxyDataTransferPolicy} = 'PORT'; + if ($ENV{TEST_VERBOSE}) { $proxy_config->{ProxyTLSOptions} = 'EnableDiags'; } my $config = { - PidFile => $pid_file, - ScoreboardFile => $scoreboard_file, - SystemLog => $log_file, - TraceLog => $log_file, - Trace => 'DEFAULT:10 event:0 lock:0 netio:20 scoreboard:0 signal:0 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 tls:20', + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, + Trace => 'netio:20 proxy:20 proxy.netio:20 proxy.db:20 proxy.reverse:20 proxy.tls:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20 proxy.ftp.sess:20 proxy.ftp.xfer:20 tls:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + AuthOrder => 'mod_auth_file.c', - AuthUserFile => $auth_user_file, - AuthGroupFile => $auth_group_file, SocketBindTight => 'on', IfModules => { @@ -3414,7 +3988,7 @@ sub proxy_reverse_frontend_backend_tls_list_pasv { 'mod_tls.c' => { TLSEngine => 'on', - TLSLog => $log_file, + TLSLog => $setup->{log_file}, TLSRequired => 'on', TLSRSACertificateFile => $cert_file, TLSCACertificateFile => $ca_file, @@ -3431,21 +4005,22 @@ sub proxy_reverse_frontend_backend_tls_list_pasv { Limit => { LOGIN => { - DenyUser => $user, + DenyUser => $setup->{user}, }, }, }; - my ($port, $config_user, $config_group) = config_write($config_file, $config); + my ($port, $config_user, $config_group) = config_write($setup->{config_file}, + $config); - if (open(my $fh, ">> $config_file")) { + if (open(my $fh, ">> $setup->{config_file}")) { print $fh < Port $vhost_port ServerName "Real Server" - AuthUserFile $auth_user_file - AuthGroupFile $auth_group_file + AuthUserFile $setup->{auth_user_file} + AuthGroupFile $setup->{auth_group_file} AuthOrder mod_auth_file.c AllowOverride off @@ -3454,7 +4029,7 @@ sub proxy_reverse_frontend_backend_tls_list_pasv { TLSEngine on - TLSLog $log_file + TLSLog $setup->{log_file} TLSRequired on TLSRSACertificateFile $cert_file TLSCACertificateFile $ca_file @@ -3466,11 +4041,11 @@ sub proxy_reverse_frontend_backend_tls_list_pasv { EOC unless (close($fh)) { - die("Can't write $config_file: $!"); + die("Can't write $setup->{config_file}: $!"); } } else { - die("Can't open $config_file: $!"); + die("Can't open $setup->{config_file}: $!"); } require Net::FTPSSL; @@ -3508,38 +4083,35 @@ EOC } my $client = Net::FTPSSL->new('127.0.0.1', %$client_opts); - unless ($client) { die("Can't connect to FTPS server: " . IO::Socket::SSL::errstr()); } - unless ($client->login($user, $passwd)) { + unless ($client->login($setup->{user}, $setup->{passwd})) { die("Can't login: " . $client->last_message()); } - my $res = $client->list(); - unless ($res) { - die("LIST failed unexpectedly: " . $client->last_message() . - "(" . IO::Socket::SSL::errstr() . ")"); + my $bad_path = '/foo/bar/baz'; + + my $res = $client->nlst($bad_path); + if ($res) { + die("NLST $bad_path succeeded unexpectedly"); } - + my $resp_msg = $client->last_message(); - my $expected = '226 Transfer complete'; + my $expected = "450 $bad_path: No such file or directory"; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); - # Do the LIST again; there are some reports that a first transfer + # Do the NLST again; there are some reports that a first transfer # might succeed, but subsequent ones will fail. for (my $i = 0; $i < 3; $i++) { - $res = $client->list(); - unless ($res) { - die("LIST failed unexpectedly: " . $client->last_message() . - "(" . IO::Socket::SSL::errstr() . ")"); + $res = $client->nlst($bad_path); + if ($res) { + die("NLST $bad_path succeeded unexpectedly"); } $resp_msg = $client->last_message(); - - $expected = '226 Transfer complete'; $self->assert($expected eq $resp_msg, test_msg("Expected response '$expected', got '$resp_msg'")); } @@ -3554,7 +4126,7 @@ EOC $wfh->flush(); } else { - eval { server_wait($config_file, $rfh, 20) }; + eval { server_wait($setup->{config_file}, $rfh, 20) }; if ($@) { warn($@); exit 1; @@ -3564,18 +4136,10 @@ EOC } # Stop server - server_stop($pid_file); - + server_stop($setup->{pid_file}); $self->assert_child_ok($pid); - if ($ex) { - test_append_logfile($log_file, $ex); - unlink($log_file); - - die($ex); - } - - unlink($log_file); + test_cleanup($setup->{log_file}, $ex); } sub proxy_reverse_frontend_backend_tls_abort { @@ -7958,7 +8522,7 @@ EOC if ($pid) { eval { # Give the server a chance to start up - sleep(3); + sleep(5); my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, $use_port, 5); $client->login("$setup->{user}\@127.0.0.1:$vhost_port", $setup->{passwd});