diff options
author | Alexander Clouter <alex@digriz.org.uk> | 2020-05-28 16:16:11 +0100 |
---|---|---|
committer | Alexander Clouter <alex@digriz.org.uk> | 2020-10-28 11:14:46 +0000 |
commit | 1b57b2df284106ea50537acf2ac4bff2f12f7e27 (patch) | |
tree | a2f6649722926b6a190a86a5bf1e2c252fb6be31 /lib/ftp | |
parent | 0e7f9415c92d96d45060844556c414514791e715 (diff) | |
download | erlang-1b57b2df284106ea50537acf2ac4bff2f12f7e27.tar.gz |
ftp: data socket failures should not stop the gen_server
{verbose,true} now shows error messages from the control socket
Diffstat (limited to 'lib/ftp')
-rw-r--r-- | lib/ftp/src/ftp.erl | 62 | ||||
-rw-r--r-- | lib/ftp/test/ftp_SUITE.erl | 181 | ||||
-rw-r--r-- | lib/ftp/test/ftp_SUITE_data/vsftpd.conf | 2 |
3 files changed, 155 insertions, 90 deletions
diff --git a/lib/ftp/src/ftp.erl b/lib/ftp/src/ftp.erl index f3860f503d..d842a5f8fe 100644 --- a/lib/ftp/src/ftp.erl +++ b/lib/ftp/src/ftp.erl @@ -1731,13 +1731,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {dir, Dir}} = State0) -> State = activate_data_connection(State1), {noreply, State#state{caller = {handle_dir_result, Dir}}}; {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; handle_ctrl_result({pos_compl, _}, #state{caller = {handle_dir_result, Dir, @@ -1840,13 +1834,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = recv_bin} = State0) -> State = activate_data_connection(State1), {noreply, State}; {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; handle_ctrl_result({pos_compl, _}, #state{caller = {recv_bin, Data}, @@ -1866,21 +1854,14 @@ handle_ctrl_result({Status, _}, #state{caller = {recv_bin, _}} = State) -> {error, epath}); %%-------------------------------------------------------------------------- %% File handling - start_chunk_transfer -handle_ctrl_result({pos_prel, _}, #state{client = From, - caller = start_chunk_transfer} +handle_ctrl_result({pos_prel, _}, #state{caller = start_chunk_transfer} = State0) -> case accept_data_connection(State0) of {ok, State1} -> State = start_chunk(State1), {noreply, State}; {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; %%-------------------------------------------------------------------------- @@ -1915,13 +1896,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) -> State = activate_data_connection(State1), {noreply, State}; {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; handle_ctrl_result({Status, _}, #state{caller = {recv_file, Fd}} = State) -> @@ -1937,13 +1912,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_file, Fd}} {ok, State1} -> send_file(State1, Fd); {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} @@ -1952,13 +1921,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} {ok, State} -> send_bin(State, Bin); {error, _Reason} = Error -> - case State0#state.client of - undefined -> - {stop, Error, State0}; - From -> - gen_server:reply(From, Error), - {stop, normal, State0#state{client = undefined}} - end + ctrl_result_response(error, State0, Error) end; %%-------------------------------------------------------------------------- @@ -1978,13 +1941,22 @@ ctrl_result_response(enofile, #state{client = From} = State, _) -> gen_server:reply(From, {error, enofile}), {noreply, State#state{client = undefined, caller = undefined}}; +ctrl_result_response(error, State0, {error, _Reason} = Error) -> + case State0#state.client of + undefined -> + {stop, Error, State0}; + From -> + gen_server:reply(From, Error), + State = activate_ctrl_connection(State0), + {noreply, State} + end; + ctrl_result_response(Status, #state{client = From} = State, _) when (Status =:= etnospc) orelse (Status =:= epnospc) orelse (Status =:= efnamena) orelse (Status =:= econn) -> gen_server:reply(From, {error, Status}), -%% {stop, normal, {error, Status}, State#state{client = undefined}}; {stop, normal, State#state{client = undefined}}; ctrl_result_response(_, #state{client = From} = State, ErrorMsg) -> diff --git a/lib/ftp/test/ftp_SUITE.erl b/lib/ftp/test/ftp_SUITE.erl index a83332d389..80444e4ee1 100644 --- a/lib/ftp/test/ftp_SUITE.erl +++ b/lib/ftp/test/ftp_SUITE.erl @@ -57,6 +57,7 @@ all() -> app, appup, error_ehost, + error_datafail, clean_shutdown ]. @@ -144,28 +145,62 @@ ftp_sup_tests() -> ConfFile = filename:join(DataDir, "vsftpd.conf"), PrivDir = proplists:get_value(priv_dir,__CONF__), AnonRoot = PrivDir, - Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"), - " -oftpd_banner=erlang_otp_testing", - " -oanon_root=\"",AnonRoot,"\"", - " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"", - " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\"" - ], - Result = os:cmd(Cmd), - ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p", - [case file:read_file(ConfFile) of - {ok,X} -> X; - _ -> "" - end, - Cmd, Result - ]), - case Result of - [] -> {ok,'dont care'}; - [Msg] -> {error,Msg} + Cmd0 = AbsName, + Args0 = [filename:join(DataDir,"vsftpd.conf"), + "-oftpd_banner=erlang_otp_testing", + "-oanon_root=\"" ++ AnonRoot ++ "\"", + "-orsa_cert_file=\"" ++ filename:join(DataDir,"server-cert.pem") ++ "\"", + "-orsa_private_key_file=\"" ++ filename:join(DataDir,"server-key.pem") ++ "\"" + ], + Args = lists:append(Args0, case proplists:get_value(ftpd_ssl_reuse,__CONF__) of + true -> ["-orequire_ssl_reuse=YES"]; + _ -> [] + end), + % eof on stdin does not kill vsftpd + Cmd = "script -qefc '" ++ "stty -echo intr ^D && exec " ++ string:join([Cmd0|Args], " ") ++ "' /dev/null", + Parent = self(), + Helper = spawn(fun() -> + case os:cmd("ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'") of + [] -> + case open_port({spawn,Cmd},[exit_status]) of + Port when is_port(Port) -> + timer:sleep(500), % give it a chance to actually open the listening socket + Parent ! {ok,Port}, + receive {From,close} -> + true = erlang:port_command(Port, [4]), + receive {Port,{exit_status,Status}} -> + ct:log("vsftpd exit with status ~b", [Status - 128]) + after 500 -> + ct:log("vsftpd requires violence", []), + os:cmd("kill -9 `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`") + end, + From ! ok + end; + _Else -> + Parent ! {error,open_port} + end; + OSPids -> + Parent ! {error,{existing,OSPids}} + end + end), + receive + {ok,Port} -> + ct:log("Config file:~n~s~n~nServer start command:~n ~s~nResult:~n ~p", + [case file:read_file(ConfFile) of + {ok,X} -> X; + _ -> "" + end, + Cmd, erlang:port_info(Port) + ]), + {ok, {Helper, Port}}; + {error,_} = Error -> + ct:fail("open_port: ~p", [Error]), + Error end end, - fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep") + fun(_StartResult = {_Helper, Port}) -> erlang:port_info(Port) end, - fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`") + fun(_StartResult = {Helper, _Port}) -> Helper ! {self(), close}, receive ok -> ok end end, fun(__CONF__) -> AnonRoot = proplists:get_value(priv_dir,__CONF__), @@ -180,32 +215,29 @@ ftp_sup_tests() -> init_per_suite(Config) -> + % remove anything defunct from previoused crashed runs + os:cmd("kill -9 `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`"), + case find_executable(Config) of false -> {skip, "No ftp server found"}; {ok,Data} -> TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"), file:make_dir(TstDir), - %% make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)), ftp_test_lib:make_cert_files(proplists:get_value(data_dir,Config)), - start_ftpd([{test_dir,TstDir}, - {ftpd_data,Data} - | Config]) + [{test_dir,TstDir},{ftpd_data,Data} | Config] end. -end_per_suite(Config) -> - ps_ftpd(Config), - stop_ftpd(Config), - ps_ftpd(Config), +end_per_suite(_Config) -> ok. %%-------------------------------------------------------------------- -init_per_group(Group, Config) when Group == ftps_active, +init_per_group(Group, Config) when Group == ftps_active; Group == ftps_passive -> catch crypto:stop(), try crypto:start() of ok -> - Config + start_ftpd(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -213,25 +245,46 @@ init_per_group(Group, Config) when Group == ftps_active, init_per_group(ftp_sup, Config) -> try ftp:start() of ok -> - Config + start_ftpd(Config) catch _:_ -> {skip, "Ftp did not start"} end; init_per_group(_Group, Config) -> - Config. + start_ftpd(Config). end_per_group(ftp_sup, Config) -> ftp:stop(), + stop_ftpd(Config), Config; end_per_group(_Group, Config) -> + stop_ftpd(Config), Config. %%-------------------------------------------------------------------- init_per_testcase(T, Config0) when T =:= app; T =:= appup -> Config0; init_per_testcase(Case, Config0) -> + case Case of + error_datafail -> + catch crypto:stop(), + try crypto:start() of + ok -> + Config = start_ftpd([{ftpd_ssl_reuse,true}|Config0]), + init_per_testcase2(Case, Config) + catch + _:_ -> + {skip, "Crypto did not start"} + end; + clean_shutdown -> + Config = start_ftpd(Config0), + init_per_testcase2(Case, Config); + _ -> + init_per_testcase2(Case, Config0) + end. + +init_per_testcase2(Case, Config0) -> Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config0)), %% Workaround for interoperability issues with vsftpd =< 3.0.2: @@ -247,7 +300,11 @@ init_per_testcase(Case, Config0) -> (_) -> false end}]), Suites = ssl:append_cipher_suites(RSASuites, Default), %% vsftpd =< 3.0.3 gets upset with anything later than tlsv1.2 - TLS = [{tls,[{reuse_sessions,true},{versions,['tlsv1.2']},{ciphers, Suites}]}], + TLS = [{tls, [ + {versions,['tlsv1.2']},{ciphers,Suites}, + % not safe for ftp ctrl channels as reuse is for data channels + {reuse_sessions,not proplists:get_value(ftpd_ssl_reuse,Config0,false)} + ]}], ACTIVE = [{mode,active}], PASSIVE = [{mode,passive}], CaseOpts = case Case of @@ -272,18 +329,27 @@ init_per_testcase(Case, Config0) -> error_ehost -> Config; clean_shutdown -> Config; _ -> - Pid = proplists:get_value(ftp,Config), + ConfigN = if + Case == error_datafail -> + ftp__open(Config, TLS++PASSIVE++ExtraOpts); + true -> + Config + end, + Pid = proplists:get_value(ftp,ConfigN), ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ), - ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)), - Config + ok = ftp:cd(Pid, proplists:get_value(priv_dir,ConfigN)), + ConfigN end. -end_per_testcase(T, _Config) when T =:= app; T =:= appup -> ok; +end_per_testcase(T, _Config) when T =:= app; T =:= appup -> ok; end_per_testcase(user, _Config) -> ok; end_per_testcase(bad_user, _Config) -> ok; end_per_testcase(error_elogin, _Config) -> ok; end_per_testcase(error_ehost, _Config) -> ok; -end_per_testcase(clean_shutdown, _Config) -> ok; +end_per_testcase(T, Config) when T =:= error_datafail; T =:= clean_shutdown -> + T == error_datafail andalso ftp__close(Config), + stop_ftpd(Config), + ok; end_per_testcase(_Case, Config) -> case proplists:get_value(tc_status,Config) of ok -> ok; @@ -963,6 +1029,39 @@ error_ehost(_Config) -> {error, ehost} = ftp:open("nohost.nodomain"), ok. +%%%---------------------------------------------------------------- +error_datafail() -> + [{doc, "Test that failure to open data channel captures " + "error emitted on ctrl chanenel"}]. + +error_datafail(Config) -> + Self = self(), + Pid = proplists:get_value(ftp, Config), + % ftp:latest_ctrl_response/1 returns {error,eclosed} + % and erlang:group_leader/2 does not work under ct + dbg:start(), + dbg:tracer(process, {fun + ({trace,P,call,{ftp,verbose,[M,_,'receive']}}, ok) when P == Pid -> Self ! M, ok; + (_, ok) -> ok + end, ok}), + dbg:tpl(ftp, verbose, []), + dbg:p(Pid, [call]), + {error,_} = ftp:ls(Pid), + dbg:stop_clear(), + Recv = fun(Recv) -> + receive + Msg when is_list(Msg) -> + case string:find(Msg, "session reuse required") of + nomatch -> Recv(Recv); + _ -> ok + end + after 2000 -> + {fail, "missing error stating 'session reuse required'"} + end + end, + Result = Recv(Recv), + Result. + %%-------------------------------------------------------------------- %% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- @@ -1083,6 +1182,7 @@ start_ftpd(Config0) -> Class:Exception -> ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p", [AbsName,Host,Port,Class,Exception]), + stop_ftpd(Config), {skip, [AbsName," started but unusuable"]} end; {error,Msg} -> @@ -1093,14 +1193,9 @@ stop_ftpd(Config) -> {_Name,_StartCmd,_ChkUp,StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), StopCommand(proplists:get_value(ftpd_start_result,Config)). -ps_ftpd(Config) -> - {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), - ct:log( ChkUp(proplists:get_value(ftpd_start_result,Config)) ). - - ftpd_running(Config) -> {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config), - ChkUp(proplists:get_value(ftpd_start_result,Config)). + undefined =/= ChkUp(proplists:get_value(ftpd_start_result,Config)). ftp__open(Config, Options) -> Host = proplists:get_value(ftpd_host,Config), diff --git a/lib/ftp/test/ftp_SUITE_data/vsftpd.conf b/lib/ftp/test/ftp_SUITE_data/vsftpd.conf index a91bf4d2f4..d615ad4d7b 100644 --- a/lib/ftp/test/ftp_SUITE_data/vsftpd.conf +++ b/lib/ftp/test/ftp_SUITE_data/vsftpd.conf @@ -14,8 +14,6 @@ ssl_enable=YES ssl_ciphers=HIGH:!aNULL:!MD5 allow_anon_ssl=YES -background=YES - # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=735357 # https://technologytales.com/2013/09/21/turning-off-seccomp-sandbox-in-vsftpd/ seccomp_sandbox=NO |