diff --git a/include/proxy/ftp/ctrl.h b/include/proxy/ftp/ctrl.h index 9a64796..8c1ace6 100644 --- a/include/proxy/ftp/ctrl.h +++ b/include/proxy/ftp/ctrl.h @@ -1,6 +1,6 @@ /* * ProFTPD - mod_proxy FTP control conn API - * Copyright (c) 2012-2016 TJ Saunders + * Copyright (c) 2012-2020 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 @@ -35,6 +35,7 @@ int proxy_ftp_ctrl_handle_async(pool *p, conn_t *backend_conn, pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn, unsigned int *resp_nlines, int flags); +int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd); int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd); int proxy_ftp_ctrl_send_resp(pool *p, conn_t *ctrl_conn, pr_response_t *resp, unsigned int resp_nlines); diff --git a/lib/proxy/ftp/ctrl.c b/lib/proxy/ftp/ctrl.c index 5a09de0..45cba34 100644 --- a/lib/proxy/ftp/ctrl.c +++ b/lib/proxy/ftp/ctrl.c @@ -1,6 +1,6 @@ /* * ProFTPD - mod_proxy FTP control conn routines - * Copyright (c) 2012-2016 TJ Saunders + * Copyright (c) 2012-2020 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 @@ -26,6 +26,7 @@ #include "proxy/netio.h" #include "proxy/ftp/ctrl.h" +#include "proxy/tls.h" static const char *trace_channel = "proxy.ftp.ctrl"; @@ -366,6 +367,84 @@ pr_response_t *proxy_ftp_ctrl_recv_resp(pool *p, conn_t *ctrl_conn, return resp; } +#ifndef TELNET_DM +# define TELNET_DM 242 +#endif /* TELNET_DM */ + +#ifndef TELNET_IAC +# define TELNET_IAC 255 +#endif /* TELNET_IAC */ + +#ifndef TELNET_IP +# define TELNET_IP 244 +#endif /* TELNET_IP */ + +int proxy_ftp_ctrl_send_abort(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) { + int fd, res, use_tls, xerrno; + unsigned char buf[7]; + + if (p == NULL || + ctrl_conn == NULL || + cmd == NULL) { + errno = EINVAL; + return -1; + } + + /* If we are proxying the ABOR command, preface it with the Telnet "Sync" + * mechanism, using OOB data. If the receiving server supports this, it can + * generate a signal to interrupt any IO occurring on the backend server + * (such as when sendfile(2) is used). + * + * Note that such Telnet codes can only be used if we are NOT using TLS + * on the backend control connection. + */ + use_tls = proxy_tls_using_tls(); + if (use_tls != PROXY_TLS_ENGINE_OFF) { + return proxy_ftp_ctrl_send_cmd(p, ctrl_conn, cmd); + } + + fd = PR_NETIO_FD(ctrl_conn->outstrm); + + buf[0] = TELNET_IAC; + buf[1] = TELNET_IP; + buf[2] = TELNET_IAC; + + pr_trace_msg(trace_channel, 9, + "sending Telnet abort code out-of-band to backend"); + res = send(fd, &buf, 3, MSG_OOB); + xerrno = errno; + + if (res < 0) { + pr_trace_msg(trace_channel, 1, + "error sending Telnet abort code out-of-band to backend: %s", + strerror(xerrno)); + errno = xerrno; + return -1; + } + + buf[0] = TELNET_DM; + buf[1] = 'A'; + buf[2] = 'B'; + buf[3] = 'O'; + buf[4] = 'R'; + buf[5] = '\r'; + buf[6] = '\n'; + + pr_trace_msg(trace_channel, 9, + "proxied %s command from frontend to backend", (char *) cmd->argv[0]); + res = send(fd, &buf, 7, 0); + xerrno = errno; + + if (res < 0) { + pr_trace_msg(trace_channel, 1, + "error sending Telnet DM code to backend: %s", strerror(xerrno)); + errno = xerrno; + return -1; + } + + return 0; +} + int proxy_ftp_ctrl_send_cmd(pool *p, conn_t *ctrl_conn, cmd_rec *cmd) { int res; diff --git a/mod_proxy.c b/mod_proxy.c index c1cde81..9d99312 100644 --- a/mod_proxy.c +++ b/mod_proxy.c @@ -84,28 +84,12 @@ static void proxy_timeoutidle_ev(const void *, void *); static void proxy_timeoutnoxfer_ev(const void *, void *); static void proxy_timeoutstalled_ev(const void *, void *); -MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess, +static int recv_resp(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_t **rp) { int res, xerrno = 0; pr_response_t *resp; unsigned int resp_nlines = 0; - 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 PR_ERROR(cmd); - } - resp = proxy_ftp_ctrl_recv_resp(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, &resp_nlines, 0); if (resp == NULL) { @@ -133,7 +117,7 @@ MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_flush(&resp_err_list); errno = xerrno; - return PR_ERROR(cmd); + return -1; } res = proxy_ftp_ctrl_send_resp(cmd->tmp_pool, proxy_sess->frontend_ctrl_conn, @@ -143,15 +127,90 @@ MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess, pr_response_block(TRUE); errno = xerrno; + return -1; + } + + if (rp != NULL) { + *rp = resp; + } + + return 0; +} + +MODRET proxy_cmd(cmd_rec *cmd, struct proxy_session *proxy_sess, + pr_response_t **rp) { + int res, xerrno = 0; + + res = proxy_ftp_ctrl_send_cmd(cmd->tmp_pool, proxy_sess->backend_ctrl_conn, + cmd); + xerrno = errno; + + 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 PR_ERROR(cmd); + } + + if (recv_resp(cmd, proxy_sess, rp) < 0) { return PR_ERROR(cmd); } pr_response_block(TRUE); + return PR_HANDLED(cmd); +} - if (rp != NULL) { - *rp = resp; +MODRET proxy_abort(cmd_rec *cmd, struct proxy_session *proxy_sess, + pr_response_t **rp) { + int res, xerrno = 0; + + res = proxy_ftp_ctrl_send_abort(cmd->tmp_pool, + proxy_sess->backend_ctrl_conn, cmd); + xerrno = errno; + + if (res < 0) { + (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 PR_ERROR(cmd); + } + + if (proxy_sess->backend_data_conn != NULL) { + pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, " + "closing backend data connection"); + proxy_inet_close(session.pool, proxy_sess->backend_data_conn); + proxy_sess->backend_data_conn = NULL; + } + + if (recv_resp(cmd, proxy_sess, rp) < 0) { + return PR_ERROR(cmd); + } + + /* The ABOR command might have two responses, as when there is a data + * transfer in progress: one response for the data transfer, and one for + * handling the ABOR command itself. + */ + if ((proxy_sess->frontend_sess_flags & SF_XFER) || + (proxy_sess->backend_sess_flags & SF_XFER)) { + if (recv_resp(cmd, proxy_sess, rp) < 0) { + return PR_ERROR(cmd); + } } + pr_response_block(TRUE); return PR_HANDLED(cmd); } @@ -2525,19 +2584,28 @@ MODRET proxy_data(struct proxy_session *proxy_sess, cmd_rec *cmd) { continue; } -#if 0 /* Any commands from the frontend client take priority */ - - /* NOTE: This is temporarily disabled, until I can better handle an - * ABOR command on the frontend control connection whilst in the middle - * of a data transfer. - */ if (frontend_ctrlfd >= 0 && FD_ISSET(frontend_ctrlfd, &rfds)) { proxy_process_cmd(); pr_response_block(FALSE); + + /* Check for closed frontend/backend data connections, as from an ABOR + * command. + */ + if (session.d == NULL || + proxy_sess->backend_data_conn == NULL) { + if (pr_data_get_timeout(PR_DATA_TIMEOUT_STALLED) > 0) { + pr_timer_remove(PR_TIMER_STALLED, ANY_MODULE); + } + + pr_throttle_pause(bytes_transferred, TRUE); + pr_response_clear(&resp_list); + pr_response_clear(&resp_err_list); + + return PR_HANDLED(cmd); + } } -#endif if (src_data_conn != NULL && datafd >= 0 && @@ -4357,6 +4425,23 @@ MODRET proxy_any(cmd_rec *cmd) { return PR_DECLINED(cmd); } break; + + case PR_CMD_ABOR_ID: + mr = proxy_abort(cmd, proxy_sess, NULL); + if ((proxy_sess->frontend_sess_flags & SF_XFER) || + (proxy_sess->backend_sess_flags & SF_XFER)) { + pr_trace_msg(trace_channel, 19, "received ABOR on frontend connection, " + "closing frontend data connection"); + + if (session.d != NULL) { + pr_inet_close(session.pool, proxy_sess->frontend_data_conn); + proxy_sess->frontend_data_conn = session.d = NULL; + } + + proxy_sess->frontend_sess_flags &= ~SF_XFER; + proxy_sess->backend_sess_flags &= ~SF_XFER; + } + return mr; } /* If we are not connected to a backend server, then don't try to proxy diff --git a/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm b/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm index 920b2b1..f3cb66f 100644 --- a/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm +++ b/t/lib/ProFTPD/Tests/Modules/mod_proxy.pm @@ -91,6 +91,11 @@ my $TESTS = { test_class => [qw(forking reverse)], }, + proxy_reverse_abort => { + order => ++$order, + test_class => [qw(forking reverse)], + }, + proxy_reverse_list_pasv => { order => ++$order, test_class => [qw(forking reverse)], @@ -3011,6 +3016,121 @@ EOC unlink($log_file); } +sub proxy_reverse_abort { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'proxy'); + + 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); + + 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 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', + + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, + SocketBindTight => 'on', + + IfModules => { + 'mod_proxy.c' => $proxy_config, + + 'mod_delay.c' => { + DelayEngine => 'off', + }, + }, + }; + + 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 + +EOC + unless (close($fh)) { + die("Can't write $setup->{config_file}: $!"); + } + + } else { + die("Can't open $setup->{config_file}: $!"); + } + + # 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(1); + + my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); + $client->login($setup->{user}, $setup->{passwd}); + + $client->quote('ABOR'); + my $resp_code = $client->response_code(); + my $resp_msg = $client->response_msg(); + + my $expected = 226; + $self->assert($expected == $resp_code, + test_msg("Expected response code $expected, got $resp_code")); + + $expected = 'Abort successful'; + $self->assert($expected eq $resp_msg, + test_msg("Expected response message '$expected', got '$resp_msg'")); + + $client->quit(); + }; + if ($@) { + $ex = $@; + } + + $wfh->print("done\n"); + $wfh->flush(); + + } else { + eval { server_wait($setup->{config_file}, $rfh) }; + 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_list_pasv { my $self = shift; my $tmpdir = $self->{tmpdir}; @@ -4966,38 +5086,7 @@ EOC sub proxy_reverse_retr_abort { 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 $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); @@ -5015,22 +5104,23 @@ sub proxy_reverse_retr_abort { 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); my $timeout_idle = 10; my $config = { - PidFile => $pid_file, - ScoreboardFile => $scoreboard_file, - SystemLog => $log_file, - TraceLog => $log_file, + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', - AuthUserFile => $auth_user_file, - AuthGroupFile => $auth_group_file, + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, - UseIPv6 => 'on', + TimeoutLinger => 1, + UseIPv6 => 'off', IfModules => { 'mod_proxy.c' => $proxy_config, @@ -5042,37 +5132,38 @@ sub proxy_reverse_retr_abort { 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 TimeoutIdle $timeout_idle + TimeoutLinger 1 TransferLog none WtmpLog off 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}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, @@ -5091,8 +5182,9 @@ EOC if ($pid) { eval { sleep(1); + my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); - $client->login($user, $passwd); + $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->retr_raw($test_file); @@ -5103,7 +5195,7 @@ EOC my $buf; $conn->read($buf, 8192, 30); - eval { $conn->close() }; + eval { $client->quote('ABOR') }; my $resp_code = $client->response_code(); my $resp_msg = $client->response_msg(); @@ -5111,7 +5203,6 @@ EOC $client->quit(); }; - if ($@) { $ex = $@; } @@ -5120,7 +5211,7 @@ EOC $wfh->flush(); } else { - eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; + eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; @@ -5130,18 +5221,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_stor_pasv { @@ -6230,38 +6313,7 @@ EOC sub proxy_reverse_stor_abort { 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 $test_datalen = (4 * 1024 * 1024); my $test_file = File::Spec->rel2abs("$tmpdir/test.txt"); @@ -6269,22 +6321,24 @@ sub proxy_reverse_stor_abort { 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); my $timeout_idle = 10; my $config = { - PidFile => $pid_file, - ScoreboardFile => $scoreboard_file, - SystemLog => $log_file, - TraceLog => $log_file, + PidFile => $setup->{pid_file}, + ScoreboardFile => $setup->{scoreboard_file}, + SystemLog => $setup->{log_file}, + TraceLog => $setup->{log_file}, Trace => 'DEFAULT:10 event:0 lock:0 scoreboard:0 signal:0 proxy:20 proxy.ftp.conn:20 proxy.ftp.ctrl:20 proxy.ftp.data:20 proxy.ftp.msg:20', - AuthUserFile => $auth_user_file, - AuthGroupFile => $auth_group_file, + AuthUserFile => $setup->{auth_user_file}, + AuthGroupFile => $setup->{auth_group_file}, SocketBindTight => 'on', TimeoutIdle => $timeout_idle, - UseIPv6 => 'on', + TimeoutLinger => 1, + UseIPv6 => 'off', IfModules => { 'mod_proxy.c' => $proxy_config, @@ -6296,37 +6350,37 @@ sub proxy_reverse_stor_abort { 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 TimeoutIdle $timeout_idle - + TimeoutLinger 1 TransferLog none WtmpLog off 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}: $!"); } # Open pipes, for use between the parent and child processes. Specifically, @@ -6345,8 +6399,9 @@ EOC if ($pid) { eval { sleep(1); + my $client = ProFTPD::TestSuite::FTP->new('127.0.0.1', $port, 0, 1); - $client->login($user, $passwd); + $client->login($setup->{user}, $setup->{passwd}); $client->type('binary'); my $conn = $client->stor_raw($test_file); @@ -6365,7 +6420,6 @@ EOC $client->quit(); }; - if ($@) { $ex = $@; } @@ -6374,7 +6428,7 @@ EOC $wfh->flush(); } else { - eval { server_wait($config_file, $rfh, $timeout_idle + 2) }; + eval { server_wait($setup->{config_file}, $rfh, $timeout_idle + 2) }; if ($@) { warn($@); exit 1; @@ -6384,18 +6438,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_rest_retr { diff --git a/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm b/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm index 2c48da1..fdeaccf 100644 --- a/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm +++ b/t/lib/ProFTPD/Tests/Modules/mod_proxy/tls.pm @@ -100,6 +100,11 @@ my $TESTS = { test_class => [qw(forking mod_tls reverse)], }, + proxy_reverse_frontend_backend_tls_abort => { + order => ++$order, + test_class => [qw(forking mod_tls reverse)], + }, + proxy_reverse_frontend_tls_json_peruser => { order => ++$order, test_class => [qw(forking mod_tls reverse)], @@ -3323,6 +3328,186 @@ EOC unlink($log_file); } +sub proxy_reverse_frontend_backend_tls_abort { + my $self = shift; + my $tmpdir = $self->{tmpdir}; + my $setup = test_setup($tmpdir, 'proxy'); + + 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 $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; + $proxy_config->{ProxyTLSVerifyServer} = 'off'; + + 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}, + SocketBindTight => 'on', + + IfModules => { + 'mod_proxy.c' => $proxy_config, + + 'mod_tls.c' => { + TLSEngine => 'on', + TLSLog => $setup->{log_file}, + TLSProtocol => 'SSLv3 TLSv1', + 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} + TLSProtocol SSLv3 TLSv1 + 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->_abort(); + unless ($res) { + die("ABOR failed unexpectedly: " . $client->last_message() . + "(" . IO::Socket::SSL::errstr() . ")"); + } + + my $resp_msg = $client->last_message(); + my $expected = '226 Abort successful'; + $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_tls_json_peruser { my $self = shift; my $tmpdir = $self->{tmpdir};