diff options
Diffstat (limited to 'lib/ssh')
-rw-r--r-- | lib/ssh/doc/src/notes.xml | 66 | ||||
-rw-r--r-- | lib/ssh/src/ssh.appup.src | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_channel.erl | 9 | ||||
-rw-r--r-- | lib/ssh/src/ssh_cli.erl | 11 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 51 | ||||
-rw-r--r-- | lib/ssh/src/ssh_dbg.erl | 7 | ||||
-rw-r--r-- | lib/ssh/test/Makefile | 3 | ||||
-rw-r--r-- | lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl | 92 | ||||
-rw-r--r-- | lib/ssh/test/ssh_basic_SUITE.erl | 66 | ||||
-rw-r--r-- | lib/ssh/test/ssh_eqc_event_handler.erl | 43 | ||||
-rw-r--r-- | lib/ssh/test/ssh_options_SUITE.erl | 25 | ||||
-rw-r--r-- | lib/ssh/test/ssh_property_test_SUITE.erl | 7 | ||||
-rw-r--r-- | lib/ssh/test/ssh_protocol_SUITE.erl | 31 | ||||
-rw-r--r-- | lib/ssh/test/ssh_renegotiate_SUITE.erl | 40 | ||||
-rw-r--r-- | lib/ssh/test/ssh_test_lib.erl | 97 | ||||
-rw-r--r-- | lib/ssh/test/ssh_to_openssh_SUITE.erl | 134 | ||||
-rw-r--r-- | lib/ssh/vsn.mk | 2 |
17 files changed, 563 insertions, 125 deletions
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index b990c18e9a..f5a67bc00e 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,72 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Re-negotiation problems with OpenSSH client solved.</p> + <p> + Own Id: OTP-13972</p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.3.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + If a client illegaly sends an info-line and then + immediatly closes the TCP-connection, a badmatch + exception was raised.</p> + <p> + Own Id: OTP-13966</p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.3.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Intermittent ssh ERROR REPORT mentioning + nonblocking_sender</p> + <p> + Own Id: OTP-13953 Aux Id: seq13199 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle all possible exit values that should be + interpreted as {error, closed}. Failing to do so could + lead to unexpected crashes for users of the ssh + application.</p> + <p> + Own Id: OTP-13932 Aux Id: seq13189 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.3.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index e38cecf226..4cda8fee95 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -20,9 +20,13 @@ {"%VSN%", [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ], [ + {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []} + ]}, {<<".*">>, [{restart_application, ssh}]} ] }. diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index a8e6ebde16..85b31f3669 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -93,11 +93,16 @@ call(ChannelPid, Msg, TimeOute) -> catch exit:{noproc, _} -> {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{shutdown, _} -> + {error, closed}; + exit:{{shutdown, _}, _} -> + {error, closed}; exit:{timeout, _} -> {error, timeout} end. - cast(ChannelPid, Msg) -> gen_server:cast(ChannelPid, Msg). @@ -256,7 +261,7 @@ handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager, adjust_window(Msg), {noreply, State#state{channel_state = ChannelState}, Timeout}; {stop, ChannelId, ChannelState} -> - ssh_connection:close(ConnectionManager, ChannelId), + catch ssh_connection:close(ConnectionManager, ChannelId), {stop, normal, State#state{close_sent = true, channel_state = ChannelState}} end; diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl index 74cd2e081a..8af0ecc5f9 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -208,8 +208,15 @@ handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty, write_chars(ConnectionHandler, ChannelId, Chars), {ok, State#state{buf = NewBuf}}; -handle_msg({'EXIT', Group, _Reason}, #state{group = Group, - channel = ChannelId} = State) -> +handle_msg({'EXIT', Group, Reason}, #state{group = Group, + cm = ConnectionHandler, + channel = ChannelId} = State) -> + Status = case Reason of + normal -> 0; + _ -> -1 + end, + ssh_connection:exit_status(ConnectionHandler, ChannelId, Status), + ssh_connection:send_eof(ConnectionHandler, ChannelId), {stop, ChannelId, State}; handle_msg(_, State) -> diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index facf6b561a..dd414894d4 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -339,7 +339,6 @@ renegotiate_data(ConnectionHandler) -> ssh_params :: #ssh{} | undefined, socket :: inet:socket(), - sender :: pid() | undefined, decrypted_data_buffer = <<>> :: binary(), encrypted_data_buffer = <<>> :: binary(), undecrypted_packet_length :: undefined | non_neg_integer(), @@ -368,10 +367,9 @@ init_connection_handler(Role, Socket, Opts) -> {Protocol, Callback, CloseTag} = proplists:get_value(transport, Opts, ?DefaultTransport), S0#data{ssh_params = init_ssh_record(Role, Socket, Opts), - sender = spawn_link(fun() -> nonblocking_sender(Socket, Callback) end), - transport_protocol = Protocol, - transport_cb = Callback, - transport_close_tag = CloseTag + transport_protocol = Protocol, + transport_cb = Callback, + transport_close_tag = CloseTag } of S -> @@ -547,6 +545,7 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) -> case Role of client -> %% The server may send info lines to the client before the version_exchange + %% RFC4253/4.2 inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; server -> @@ -672,8 +671,9 @@ handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) -> {next_state, {service_request,Role}, D#data{ssh_params=Ssh}}; %% Subsequent key exchange rounds (renegotiation): -handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) -> - {next_state, {connected,Role}, D}; +handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) -> + {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params), + {next_state, {connected,Role}, D#data{ssh_params=Ssh}}; %%% ######## {service_request, client|server} @@ -1447,15 +1447,18 @@ start_the_connection_child(UserPid, Role, Socket, Options) -> %% Stopping -type finalize_termination_result() :: ok . -finalize_termination(_StateName, D) -> - case D#data.connection_state of +finalize_termination(_StateName, #data{transport_cb = Transport, + connection_state = Connection, + socket = Socket}) -> + case Connection of #connection{system_supervisor = SysSup, sub_system_supervisor = SubSysSup} when is_pid(SubSysSup) -> ssh_system_sup:stop_subsystem(SysSup, SubSysSup); _ -> do_nothing end, - close_transport(D). + (catch Transport:close(Socket)), + ok. %%-------------------------------------------------------------------- %% "Invert" the Role @@ -1510,34 +1513,10 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) -> send_bytes(Bytes, State), State#data{ssh_params=Ssh}. -send_bytes(Bytes, #data{sender = Sender}) -> - Sender ! {send,Bytes}, - ok. - -close_transport(D) -> - D#data.sender ! close, +send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) -> + _ = Transport:send(Socket, Bytes), ok. - -nonblocking_sender(Socket, Callback) -> - receive - {send, Bytes} -> - case Callback:send(Socket, Bytes) of - ok -> - nonblocking_sender(Socket, Callback); - E = {error,_} -> - exit({shutdown,E}) - end; - - close -> - case Callback:close(Socket) of - ok -> - ok; - E = {error,_} -> - exit({shutdown,E}) - end - end. - handle_version({2, 0} = NumVsn, StrVsn, Ssh0) -> Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0), {ok, Ssh}; diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index bd6bc0335b..ce5596e0f9 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -113,7 +113,12 @@ setup_tracer(Write, MangleArg) -> ok. %%%---------------------------------------------------------------- -shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN',size(B),element(1,split_binary(B,20)),'***'}; +shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN', + size(B), + element(1,split_binary(B,20)), + '...', + element(2,split_binary(B,size(B)-20)) + }; shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L); shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T))); shrink_bin(X) -> X. diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile index 6ce6d6f537..3fca78237c 100644 --- a/lib/ssh/test/Makefile +++ b/lib/ssh/test/Makefile @@ -52,7 +52,8 @@ MODULES= \ ssh_echo_server \ ssh_peername_sockname_server \ ssh_test_cli \ - ssh_relay + ssh_relay \ + ssh_eqc_event_handler HRL_FILES_NEEDED_IN_TEST= \ $(ERL_TOP)/lib/ssh/test/ssh_test_lib.hrl \ diff --git a/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl b/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl new file mode 100644 index 0000000000..c07140dc43 --- /dev/null +++ b/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl @@ -0,0 +1,92 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ssh_eqc_client_info_timing). + +-compile(export_all). + +-proptest(eqc). +-proptest([triq,proper]). + +-ifndef(EQC). +-ifndef(PROPER). +-ifndef(TRIQ). +-define(EQC,true). +%%-define(PROPER,true). +%%-define(TRIQ,true). +-endif. +-endif. +-endif. + +-ifdef(EQC). +-include_lib("eqc/include/eqc.hrl"). +-define(MOD_eqc,eqc). + +-else. +-ifdef(PROPER). +-include_lib("proper/include/proper.hrl"). +-define(MOD_eqc,proper). + +-else. +-ifdef(TRIQ). +-define(MOD_eqc,triq). +-include_lib("triq/include/triq.hrl"). + +-endif. +-endif. +-endif. + + +%%% Properties: + +prop_seq(_Config) -> + {ok,Pid} = ssh_eqc_event_handler:add_report_handler(), + {_, _, Port} = init_daemon(), + numtests(1000, + ?FORALL(Delay, choose(0,100),%% Micro seconds + try + send_bad_sequence(Port, Delay, Pid), + not any_relevant_error_report(Pid) + catch + C:E -> io:format('~p:~p~n',[C,E]), + false + end + )). + +send_bad_sequence(Port, Delay, Pid) -> + {ok,S} = gen_tcp:connect("localhost",Port,[]), + gen_tcp:send(S,"Illegal info-string\r\n"), + ssh_test_lib:sleep_microsec(Delay), + gen_tcp:close(S). + +any_relevant_error_report(Pid) -> + {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid), + lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) -> + lists:member({reason,{badmatch,{error,closed}}}, L); + (_) -> + false + end, Reports). + +%%%================================================================ +init_daemon() -> + ok = begin ssh:stop(), ssh:start() end, + ssh_test_lib:daemon([]). + diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index d52d453007..0a0ab5cdf7 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -67,7 +67,8 @@ shell_unicode_string/1, ssh_info_print/1, key_callback/1, - key_callback_options/1 + key_callback_options/1, + shell_exit_status/1 ]). %%% Common test callbacks @@ -106,7 +107,8 @@ all() -> multi_daemon_opt_fd, packet_size_zero, ssh_info_print, - {group, login_bad_pwd_no_retry} + {group, login_bad_pwd_no_retry}, + shell_exit_status ]. groups() -> @@ -313,9 +315,9 @@ init_per_testcase(TC, Config) when TC==shell_no_unicode ; {user_passwords, [{"foo", "bar"}]}]), ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir, - [{silently_accept_hosts, true}, - {user,"foo"},{password,"bar"}]), + Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}, + {silently_accept_hosts, true}, + {user,"foo"},{password,"bar"}]), ct:log("IO=~p, Shell=~p, self()=~p",[IO,Shell,self()]), ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p", [file:native_name_encoding(),io:getopts()]), @@ -341,14 +343,15 @@ end_per_testcase(TC, Config) when TC==shell_no_unicode ; TC==shell_unicode_string -> case proplists:get_value(sftpd, Config) of {Pid, _, _} -> - ssh:stop_daemon(Pid), - ssh:stop(); + catch ssh:stop_daemon(Pid); _ -> - ssh:stop() - end; + ok + end, + end_per_testcase(Config); end_per_testcase(_TestCase, Config) -> end_per_testcase(Config). -end_per_testcase(_Config) -> + +end_per_testcase(_Config) -> ssh:stop(), ok. @@ -522,7 +525,7 @@ shell(Config) when is_list(Config) -> ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir), + Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]), receive {'EXIT', _, _} -> ct:fail(no_ssh_connection); @@ -560,10 +563,10 @@ exec_key_differs(Config, UserPKAlgs) -> ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir, - [{preferred_algorithms,[{public_key,['ssh-rsa']}]}, - {pref_public_key_algs,UserPKAlgs} - ]), + Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}, + {preferred_algorithms,[{public_key,['ssh-rsa']}]}, + {pref_public_key_algs,UserPKAlgs} + ]), receive @@ -594,9 +597,9 @@ exec_key_differs_fail(Config) when is_list(Config) -> ct:sleep(500), IO = ssh_test_lib:start_io_server(), - ssh_test_lib:start_shell(Port, IO, UserDir, - [{preferred_algorithms,[{public_key,['ssh-rsa']}]}, - {pref_public_key_algs,['ssh-dss']}]), + ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}, + {preferred_algorithms,[{public_key,['ssh-rsa']}]}, + {pref_public_key_algs,['ssh-dss']}]), receive {'EXIT', _, _} -> ok; @@ -1167,6 +1170,33 @@ login_bad_pwd_no_retry(Config, AuthMethods) -> end end. + +%%---------------------------------------------------------------------------- +%%% Test that when shell REPL exit with reason normal client receives status 0 +shell_exit_status(Config) when is_list(Config) -> + process_flag(trap_exit, true), + SystemDir = proplists:get_value(data_dir, Config), + UserDir = proplists:get_value(priv_dir, Config), + + ShellFun = fun (_User) -> spawn(fun() -> ok end) end, + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {user_passwords, [{"vego", "morot"}]}, + {shell, ShellFun}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user, "vego"}, + {password, "morot"}, + {user_interaction, false}]), + + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + ok = ssh_connection:shell(ConnectionRef, ChannelId), + ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), + ssh:stop_daemon(Pid). + + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- diff --git a/lib/ssh/test/ssh_eqc_event_handler.erl b/lib/ssh/test/ssh_eqc_event_handler.erl new file mode 100644 index 0000000000..233965012a --- /dev/null +++ b/lib/ssh/test/ssh_eqc_event_handler.erl @@ -0,0 +1,43 @@ +-module(ssh_eqc_event_handler). + +-compile(export_all). + +-behaviour(gen_event). + +add_report_handler() -> + error_logger:add_report_handler(?MODULE, [self(),Ref=make_ref()]), + receive + {event_handler_started,HandlerPid,Ref} -> + {ok,HandlerPid} + end. + +get_reports(Pid) -> + Pid ! {get_reports,self(),Ref=make_ref()}, + receive + {reports,Reports,Ref} -> + {ok,Reports} + end. + +%%%================================================================ + +-record(state, { + reports = [] + }). + +%% error_logger:add_report_handler(ssh_eqc_event_handler, [self()]). + +init([CallerPid,Ref]) -> + CallerPid ! {event_handler_started,self(),Ref}, + {ok, #state{}}. + +handle_event(Event, State) -> + {ok, State#state{reports = [Event|State#state.reports]}}. + +handle_info({get_reports,From,Ref}, State) -> + From ! {reports, lists:reverse(State#state.reports), Ref}, + {ok, State#state{reports=[]}}. + +handle_call(_Request, State) -> {ok,reply,State}. +terminate(_Arg, _State) -> stop. + +code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 61883c0647..4cc12cbcbe 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -540,10 +540,18 @@ connectfun_disconnectfun_server(Config) -> {disconnect,Ref,R} -> ct:log("Disconnect result: ~p",[R]), ssh:stop_daemon(Pid) - after 2000 -> + after 5000 -> + receive + X -> ct:log("received ~p",[X]) + after 0 -> ok + end, {fail, "No disconnectfun action"} end - after 2000 -> + after 5000 -> + receive + X -> ct:log("received ~p",[X]) + after 0 -> ok + end, {fail, "No connectfun action"} end. @@ -649,7 +657,7 @@ disconnectfun_option_server(Config) -> ct:log("Server detected disconnect: ~p",[Reason]), ssh:stop_daemon(Pid), ok - after 3000 -> + after 5000 -> receive X -> ct:log("received ~p",[X]) after 0 -> ok @@ -974,7 +982,14 @@ ssh_connect_negtimeout(Config, Parallel) -> ct:sleep(round(Factor * NegTimeOut)), case inet:sockname(Socket) of - {ok,_} -> ct:fail("Socket not closed"); + {ok,_} -> + %% Give it another chance... + ct:log("Sleep more...",[]), + ct:sleep(round(Factor * NegTimeOut)), + case inet:sockname(Socket) of + {ok,_} -> ct:fail("Socket not closed"); + {error,_} -> ok + end; {error,_} -> ok end. @@ -1003,7 +1018,7 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) -> ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir), + Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]), receive Error = {'EXIT', _, _} -> ct:log("~p",[Error]), diff --git a/lib/ssh/test/ssh_property_test_SUITE.erl b/lib/ssh/test/ssh_property_test_SUITE.erl index c8aabcedb7..7ba2732a88 100644 --- a/lib/ssh/test/ssh_property_test_SUITE.erl +++ b/lib/ssh/test/ssh_property_test_SUITE.erl @@ -38,6 +38,7 @@ -include_lib("common_test/include/ct.hrl"). all() -> [{group, messages}, + client_sends_info_timing, {group, client_server} ]. @@ -106,3 +107,9 @@ client_server_parallel_multi(Config) -> ssh_eqc_client_server:prop_parallel_multi(Config), Config ). + +client_sends_info_timing(Config) -> + ct_property_test:quickcheck( + ssh_eqc_client_info_timing:prop_seq(Config), + Config + ). diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl index 4fac1f718a..93d0bc2eb0 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -48,6 +48,7 @@ suite() -> all() -> [{group,tool_tests}, + client_info_line, {group,kex}, {group,service_requests}, {group,authentication}, @@ -575,6 +576,36 @@ client_handles_keyboard_interactive_0_pwds(Config) -> ). + +%%%-------------------------------------------------------------------- +client_info_line(_Config) -> + %% A client must not send an info-line. If it does, the server should handle + %% handle this gracefully + {ok,Pid} = ssh_eqc_event_handler:add_report_handler(), + {_, _, Port} = ssh_test_lib:daemon([]), + + %% Fake client: + {ok,S} = gen_tcp:connect("localhost",Port,[]), + gen_tcp:send(S,"An illegal info-string\r\n"), + gen_tcp:close(S), + + %% wait for server to react: + timer:sleep(1000), + + %% check if a badmatch was received: + {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid), + case lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) -> + lists:member({reason,{badmatch,{error,closed}}}, L); + (_) -> + false + end, Reports) of + true -> + ct:fail("Bad error report on info_line from client"); + false -> + ok + end. + + %%%================================================================ %%%==== Internal functions ======================================== %%%================================================================ diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl index b10ec3707f..74bbc291b2 100644 --- a/lib/ssh/test/ssh_renegotiate_SUITE.erl +++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl @@ -92,11 +92,11 @@ rekey(Config) -> ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, 0}]), - Kex1 = get_kex_init(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), receive after ?REKEY_DATA_TMO -> %%By this time rekeying would have been done - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), false = (Kex2 == Kex1), ssh:close(ConnectionRef), ssh:stop_daemon(Pid) @@ -120,31 +120,31 @@ rekey_limit(Config) -> {max_random_length_padding,0}]), {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - Kex1 = get_kex_init(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), timer:sleep(?REKEY_DATA_TMO), - Kex1 = get_kex_init(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), Data = lists:duplicate(159000,1), ok = ssh_sftp:write_file(SftpPid, DataFile, Data), timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), false = (Kex2 == Kex1), timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"), timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), false = (Kex2 == Kex1), timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), ssh_sftp:stop_channel(SftpPid), ssh:close(ConnectionRef), @@ -169,7 +169,7 @@ renegotiate1(Config) -> ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - Kex1 = get_kex_init(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), @@ -181,7 +181,7 @@ renegotiate1(Config) -> timer:sleep(2000), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), false = (Kex2 == Kex1), @@ -208,7 +208,7 @@ renegotiate2(Config) -> ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]), {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), - Kex1 = get_kex_init(ConnectionRef), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), @@ -223,7 +223,7 @@ renegotiate2(Config) -> timer:sleep(2000), - Kex2 = get_kex_init(ConnectionRef), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), false = (Kex2 == Kex1), @@ -235,19 +235,3 @@ renegotiate2(Config) -> %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- -%% get_kex_init - helper function to get key_exchange_init_msg -get_kex_init(Conn) -> - %% First, validate the key exchange is complete (StateName == connected) - {{connected,_},S} = sys:get_state(Conn), - %% Next, walk through the elements of the #state record looking - %% for the #ssh_msg_kexinit record. This method is robust against - %% changes to either record. The KEXINIT message contains a cookie - %% unique to each invocation of the key exchange procedure (RFC4253) - SL = tuple_to_list(S), - case lists:keyfind(ssh_msg_kexinit, 1, SL) of - false -> - throw(not_found); - KexInit -> - KexInit - end. - diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index 6233680dce..f93237f3e7 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -127,24 +127,19 @@ std_simple_exec(Host, Port, Config, Opts) -> ssh:close(ConnectionRef). -start_shell(Port, IOServer, UserDir) -> - start_shell(Port, IOServer, UserDir, []). - -start_shell(Port, IOServer, UserDir, Options) -> - spawn_link(?MODULE, init_shell, [Port, IOServer, [{user_dir, UserDir}|Options]]). - start_shell(Port, IOServer) -> - spawn_link(?MODULE, init_shell, [Port, IOServer, []]). + start_shell(Port, IOServer, []). -init_shell(Port, IOServer, UserDir) -> - Host = hostname(), - Options = [{user_interaction, false}, {silently_accept_hosts, - true}] ++ UserDir, - group_leader(IOServer, self()), - loop_shell(Host, Port, Options). +start_shell(Port, IOServer, ExtraOptions) -> + spawn_link( + fun() -> + Host = hostname(), + Options = [{user_interaction, false}, + {silently_accept_hosts,true} | ExtraOptions], + group_leader(IOServer, self()), + ssh:shell(Host, Port, Options) + end). -loop_shell(Host, Port, Options) -> - ssh:shell(Host, Port, Options). start_io_server() -> spawn_link(?MODULE, init_io_server, [self()]). @@ -208,6 +203,16 @@ reply(TestCase, Result) -> rcv_expected(Expect, SshPort, Timeout) -> receive + {SshPort, Recvd} when is_function(Expect) -> + case Expect(Recvd) of + true -> + ct:log("Got expected ~p from ~p",[Recvd,SshPort]), + catch port_close(SshPort), + rcv_lingering(50); + false -> + ct:log("Got UNEXPECTED ~p~n",[Recvd]), + rcv_expected(Expect, SshPort, Timeout) + end; {SshPort, Expect} -> ct:log("Got expected ~p from ~p",[Expect,SshPort]), catch port_close(SshPort), @@ -767,3 +772,65 @@ open_port(Arg1, ExtraOpts) -> use_stdio, overlapped_io, hide %only affects windows | ExtraOpts]). + +%%%---------------------------------------------------------------- +%%% Sleeping + +%%% Milli sec +sleep_millisec(Nms) -> receive after Nms -> ok end. + +%%% Micro sec +sleep_microsec(Nus) -> + busy_wait(Nus, erlang:system_time(microsecond)). + +busy_wait(Nus, T0) -> + T = erlang:system_time(microsecond) - T0, + Tleft = Nus - T, + if + Tleft > 2000 -> + sleep_millisec((Tleft-1500) div 1000), % μs -> ms + busy_wait(Nus,T0); + Tleft > 1 -> + busy_wait(Nus, T0); + true -> + T + end. + +%%%---------------------------------------------------------------- +%% get_kex_init - helper function to get key_exchange_init_msg + +get_kex_init(Conn) -> + Ref = make_ref(), + {ok,TRef} = timer:send_after(15000, {reneg_timeout,Ref}), + get_kex_init(Conn, Ref, TRef). + +get_kex_init(Conn, Ref, TRef) -> + %% First, validate the key exchange is complete (StateName == connected) + case sys:get_state(Conn) of + {{connected,_}, S} -> + timer:cancel(TRef), + %% Next, walk through the elements of the #state record looking + %% for the #ssh_msg_kexinit record. This method is robust against + %% changes to either record. The KEXINIT message contains a cookie + %% unique to each invocation of the key exchange procedure (RFC4253) + SL = tuple_to_list(S), + case lists:keyfind(ssh_msg_kexinit, 1, SL) of + false -> + throw(not_found); + KexInit -> + KexInit + end; + + {OtherState, S} -> + ct:log("Not in 'connected' state: ~p",[OtherState]), + receive + {reneg_timeout,Ref} -> + ct:log("S = ~p", [S]), + ct:fail(reneg_timeout) + after 0 -> + timer:sleep(100), % If renegotiation is complete we do not + % want to exit on the reneg_timeout + get_kex_init(Conn, Ref, TRef) + end + end. + diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl index a914938c41..2c7fe7898f 100644 --- a/lib/ssh/test/ssh_to_openssh_SUITE.erl +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -29,6 +29,7 @@ -define(TIMEOUT, 50000). -define(SSH_DEFAULT_PORT, 22). +-define(REKEY_DATA_TMO, 65000). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -55,10 +56,12 @@ groups() -> erlang_client_openssh_server_publickey_rsa, erlang_client_openssh_server_password, erlang_client_openssh_server_kexs, - erlang_client_openssh_server_nonexistent_subsystem + erlang_client_openssh_server_nonexistent_subsystem, + erlang_client_openssh_server_renegotiate ]}, {erlang_server, [], [erlang_server_openssh_client_public_key_dsa, - erlang_server_openssh_client_public_key_rsa + erlang_server_openssh_client_public_key_rsa, + erlang_server_openssh_client_renegotiate ]} ]. @@ -104,6 +107,11 @@ init_per_testcase(erlang_server_openssh_client_public_key_rsa, Config) -> chk_key(sshc, 'ssh-rsa', ".ssh/id_rsa", Config); init_per_testcase(erlang_client_openssh_server_publickey_dsa, Config) -> chk_key(sshd, 'ssh-dss', ".ssh/id_dsa", Config); +init_per_testcase(erlang_server_openssh_client_renegotiate, Config) -> + case os:type() of + {unix,_} -> ssh:start(), Config; + Type -> {skip, io_lib:format("Unsupported test on ~p",[Type])} + end; init_per_testcase(_TestCase, Config) -> ssh:start(), Config. @@ -145,7 +153,7 @@ erlang_shell_client_openssh_server(Config) when is_list(Config) -> IO = ssh_test_lib:start_io_server(), Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO), IO ! {input, self(), "echo Hej\n"}, - receive_hej(), + receive_data("Hej"), IO ! {input, self(), "exit\n"}, receive_logout(), receive_normal_exit(Shell). @@ -386,6 +394,103 @@ erlang_server_openssh_client_public_key_X(Config, PubKeyAlg) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +%% Test that the Erlang/OTP server can renegotiate with openSSH +erlang_server_openssh_client_renegotiate(Config) -> + PubKeyAlg = ssh_rsa, + SystemDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + KnownHosts = filename:join(PrivDir, "known_hosts"), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {public_key_alg, PubKeyAlg}, + {failfun, fun ssh_test_lib:failfun/2}]), + +%% catch ssh_dbg:messages(fun(String,_D) -> ct:log(String) end), + ct:sleep(500), + + RenegLimitK = 3, + DataFile = filename:join(PrivDir, "renegotiate_openssh_client.data"), + Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a), + ok = file:write_file(DataFile, Data), + + Cmd = "ssh -p " ++ integer_to_list(Port) ++ + " -o UserKnownHostsFile=" ++ KnownHosts ++ + " -o RekeyLimit=" ++ integer_to_list(RenegLimitK) ++"K" ++ + " " ++ Host ++ " < " ++ DataFile, + OpenSsh = ssh_test_lib:open_port({spawn, Cmd}), + + Expect = fun({data,R}) -> + try + NonAlphaChars = [C || C<-lists:seq(1,255), + not lists:member(C,lists:seq($a,$z)), + not lists:member(C,lists:seq($A,$Z)) + ], + Lines = string:tokens(binary_to_list(R), NonAlphaChars), + lists:any(fun(L) -> length(L)>1 andalso lists:prefix(L, Data) end, + Lines) + catch + _:_ -> false + end; + (_) -> + false + end, + + ssh_test_lib:rcv_expected(Expect, OpenSsh, ?TIMEOUT), + %% Unfortunatly we can't check that there has been a renegotiation, just trust OpenSSH. + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +erlang_client_openssh_server_renegotiate(_Config) -> + process_flag(trap_exit, true), + + IO = ssh_test_lib:start_io_server(), + Ref = make_ref(), + Parent = self(), + + catch ssh_dbg:messages(fun(X,_) -> ct:log(X) end), + Shell = + spawn_link( + fun() -> + Host = ssh_test_lib:hostname(), + Options = [{user_interaction, false}, + {silently_accept_hosts,true}], + group_leader(IO, self()), + {ok, ConnRef} = ssh:connect(Host, ?SSH_DEFAULT_PORT, Options), + case ssh_connection:session_channel(ConnRef, infinity) of + {ok,ChannelId} -> + success = ssh_connection:ptty_alloc(ConnRef, ChannelId, []), + Args = [{channel_cb, ssh_shell}, + {init_args,[ConnRef, ChannelId]}, + {cm, ConnRef}, {channel_id, ChannelId}], + {ok, State} = ssh_channel:init([Args]), + Parent ! {ok, Ref, ConnRef}, + ssh_channel:enter_loop(State); + Error -> + Parent ! {error, Ref, Error} + end, + receive + nothing -> ok + end + end), + + receive + {error, Ref, Error} -> + ct:fail("Error=~p",[Error]); + {ok, Ref, ConnectionRef} -> + IO ! {input, self(), "echo Hej1\n"}, + receive_data("Hej1"), + Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), + ssh_connection_handler:renegotiate(ConnectionRef), + IO ! {input, self(), "echo Hej2\n"}, + receive_data("Hej2"), + Kex2 = ssh_test_lib:get_kex_init(ConnectionRef), + IO ! {input, self(), "exit\n"}, + receive_logout(), + receive_normal_exit(Shell), + true = (Kex1 =/= Kex2) + end. + +%%-------------------------------------------------------------------- erlang_client_openssh_server_password() -> [{doc, "Test client password option"}]. erlang_client_openssh_server_password(Config) when is_list(Config) -> @@ -440,27 +545,24 @@ erlang_client_openssh_server_nonexistent_subsystem(Config) when is_list(Config) %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- -receive_hej() -> +receive_data(Data) -> receive - <<"Hej", _binary>> = Hej -> - ct:log("Expected result: ~p~n", [Hej]); - <<"Hej\n", _binary>> = Hej -> - ct:log("Expected result: ~p~n", [Hej]); - <<"Hej\r\n", _/binary>> = Hej -> - ct:log("Expected result: ~p~n", [Hej]); - Info -> - Lines = binary:split(Info, [<<"\r\n">>], [global]), - case lists:member(<<"Hej">>, Lines) of + Info when is_binary(Info) -> + Lines = string:tokens(binary_to_list(Info), "\r\n "), + case lists:member(Data, Lines) of true -> ct:log("Expected result found in lines: ~p~n", [Lines]), ok; false -> ct:log("Extra info: ~p~n", [Info]), - receive_hej() - end + receive_data(Data) + end; + Other -> + ct:log("Unexpected: ~p",[Other]), + receive_data(Data) after 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) - end. + end. receive_logout() -> receive diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 212b99c695..c023429056 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.3.2 +SSH_VSN = 4.3.6 APP_VSN = "ssh-$(SSH_VSN)" |