%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2018. 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_compat_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("ssh/src/ssh_transport.hrl"). % #ssh_msg_kexinit{} -include_lib("kernel/include/inet.hrl"). % #hostent{} -include_lib("kernel/include/file.hrl"). % #file_info{} -include("ssh_test_lib.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). -define(USER,"sshtester"). -define(PASSWD, "foobar"). -define(BAD_PASSWD, "NOT-"?PASSWD). -define(DOCKER_PFX, "ssh_compat_suite-ssh"). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- suite() -> [{timetrap,{seconds,60}}]. all() -> %% [check_docker_present] ++ [{group,G} || G <- ssh_image_versions()]. groups() -> [{otp_client, [], [login_otp_is_client, all_algorithms_sftp_exec_reneg_otp_is_client, send_recv_big_with_renegotiate_otp_is_client ]}, {otp_server, [], [login_otp_is_server, all_algorithms_sftp_exec_reneg_otp_is_server ]} | [{G, [], [{group,otp_client}, {group,otp_server}]} || G <- ssh_image_versions()] ]. ssh_image_versions() -> try %% Find all useful containers in such a way that undefined command, too low %% priviliges, no containers and containers found give meaningful result: L0 = ["REPOSITORY"++_|_] = string:tokens(os:cmd("docker images"), "\r\n"), [["REPOSITORY","TAG"|_]|L1] = [string:tokens(E, " ") || E<-L0], [list_to_atom(V) || [?DOCKER_PFX,V|_] <- L1] of Vs -> lists:sort(Vs) catch error:{badmatch,_} -> [] end. %%-------------------------------------------------------------------- init_per_suite(Config) -> ?CHECK_CRYPTO( case os:find_executable("docker") of false -> {skip, "No docker"}; _ -> ssh:start(), ct:log("Crypto info: ~p",[crypto:info_lib()]), Config end). end_per_suite(Config) -> %% Remove all containers that are not running: %%% os:cmd("docker rm $(docker ps -aq -f status=exited)"), %% Remove dangling images: %%% os:cmd("docker rmi $(docker images -f dangling=true -q)"), catch ssh:stop(), Config. init_per_group(otp_server, Config) -> case proplists:get_value(common_remote_client_algs, Config) of undefined -> SSHver = proplists:get_value(ssh_version, Config, ""), {skip,"No "++SSHver++ " client found in docker"}; _ -> Config end; init_per_group(otp_client, Config) -> Config; init_per_group(G, Config0) -> case lists:member(G, ssh_image_versions()) of true -> %% This group is for one of the images Vssh = atom_to_list(G), Cmnt = io_lib:format("+++ ~s +++",[Vssh]), ct:comment("~s",[Cmnt]), try start_docker(G) of {ok,ID} -> ct:log("==> ~p started",[G]), %% Find the algorithms that both client and server supports: {IP,Port} = ip_port([{id,ID}]), ct:log("Try contact ~p:~p",[IP,Port]), Config1 = [{id,ID}, {ssh_version,Vssh} | Config0], try common_algs(Config1, IP, Port) of {ok, ServerHello, RemoteServerCommon, ClientHello, RemoteClientCommon} -> case chk_hellos([ServerHello,ClientHello], Cmnt) of Cmnt -> ok; NewCmnt -> ct:comment("~s",[NewCmnt]) end, AuthMethods = %% This should be obtained by quering the peer, but that %% is a bit hard. It is possible with ssh_protocol_SUITE %% techniques, but it can wait. case Vssh of "dropbear" ++ _ -> [password, publickey]; _ -> [password, 'keyboard-interactive', publickey] end, [{common_remote_server_algs,RemoteServerCommon}, {common_remote_client_algs,RemoteClientCommon}, {common_authmethods,AuthMethods} |Config1]; Other -> ct:log("Error in init_per_group: ~p",[Other]), stop_docker(ID), {fail, "Can't contact docker sshd"} catch Class:Exc -> ST = erlang:get_stacktrace(), ct:log("common_algs: ~p:~p~n~p",[Class,Exc,ST]), stop_docker(ID), {fail, "Failed during setup"} end catch cant_start_docker -> {skip, "Can't start docker"}; C:E -> ST = erlang:get_stacktrace(), ct:log("No ~p~n~p:~p~n~p",[G,C,E,ST]), {skip, "Can't start docker"} end; false -> Config0 end. end_per_group(G, Config) -> case lists:member(G, ssh_image_versions()) of true -> catch stop_docker(proplists:get_value(id,Config)); false -> ok end. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- check_docker_present(_Config) -> ct:log("This testcase is just to show in Monitor that we have a test host with docker installed",[]), {fail, "Test is OK: just showing docker is available"}. %%-------------------------------------------------------------------- login_otp_is_client(Config) -> {IP,Port} = ip_port(Config), PublicKeyAlgs = [A || {public_key,A} <- proplists:get_value(common_remote_server_algs, Config)], CommonAuths = [{AuthMethod,Alg} || AuthMethod <- proplists:get_value(common_authmethods, Config), Alg <- case AuthMethod of publickey -> PublicKeyAlgs; _ -> [' '] end ], chk_all_algos(?FUNCTION_NAME, CommonAuths, Config, fun(AuthMethod,Alg) -> {Opts,Dir} = case AuthMethod of publickey -> {[], setup_remote_auth_keys_and_local_priv(Alg, Config)}; _ -> {[{password,?PASSWD}], new_dir(Config)} end, ssh:connect(IP, Port, [{auth_methods, atom_to_list(AuthMethod)}, {user,?USER}, {user_dir, Dir}, {silently_accept_hosts,true}, {user_interaction,false} | Opts ]) end). %%-------------------------------------------------------------------- login_otp_is_server(Config) -> PublicKeyAlgs = [A || {public_key,A} <- proplists:get_value(common_remote_client_algs, Config)], CommonAuths = [{AuthMethod,Alg} || AuthMethod <- proplists:get_value(common_authmethods, Config), Alg <- case AuthMethod of publickey -> PublicKeyAlgs; _ -> [' '] end ], SysDir = setup_local_hostdir(hd(PublicKeyAlgs), Config), chk_all_algos(?FUNCTION_NAME, CommonAuths, Config, fun(AuthMethod,Alg) -> {Opts,UsrDir} = case AuthMethod of publickey -> {[{user_passwords, [{?USER,?BAD_PASSWD}]}], setup_remote_priv_and_local_auth_keys(Alg, Config) }; _ -> {[{user_passwords, [{?USER,?PASSWD}]}], new_dir(Config) } end, {Server, Host, HostPort} = ssh_test_lib:daemon(0, [{auth_methods, atom_to_list(AuthMethod)}, {system_dir, SysDir}, {user_dir, UsrDir}, {failfun, fun ssh_test_lib:failfun/2} | Opts ]), R = exec_from_docker(Config, Host, HostPort, "'lists:concat([\"Answer=\",1+3]).\r\n'", [<<"Answer=4">>], ""), ssh:stop_daemon(Server), R end). %%-------------------------------------------------------------------- all_algorithms_sftp_exec_reneg_otp_is_client(Config) -> CommonAlgs = proplists:get_value(common_remote_server_algs, Config), {IP,Port} = ip_port(Config), chk_all_algos(?FUNCTION_NAME, CommonAlgs, Config, fun(Tag, Alg) -> ConnRes = ssh:connect(IP, Port, [{user,?USER}, {password,?PASSWD}, {auth_methods, "password"}, {user_dir, new_dir(Config)}, {preferred_algorithms, [{Tag,[Alg]}]}, {silently_accept_hosts,true}, {user_interaction,false} ]) , test_erl_client_reneg(ConnRes, % Seems that max 10 channels may be open in sshd [{exec,1}, {sftp,5}, {no_subsyst,1}, {setenv, 1}, {sftp_async,1} ]) end). %%-------------------------------------------------------------------- all_algorithms_sftp_exec_reneg_otp_is_server(Config) -> CommonAlgs = proplists:get_value(common_remote_client_algs, Config), UserDir = setup_remote_priv_and_local_auth_keys('ssh-rsa', Config), chk_all_algos(?FUNCTION_NAME, CommonAlgs, Config, fun(Tag,Alg) -> HostKeyAlg = case Tag of public_key -> Alg; _ -> 'ssh-rsa' end, SftpRootDir = new_dir(Config), %% ct:log("Rootdir = ~p",[SftpRootDir]), {Server, Host, HostPort} = ssh_test_lib:daemon(0, [{preferred_algorithms, [{Tag,[Alg]}]}, {system_dir, setup_local_hostdir(HostKeyAlg, Config)}, {user_dir, UserDir}, {user_passwords, [{?USER,?PASSWD}]}, {failfun, fun ssh_test_lib:failfun/2}, {subsystems, [ssh_sftpd:subsystem_spec([{cwd,SftpRootDir}, {root,SftpRootDir}]), {"echo_10",{ssh_echo_server,[10,[{dbg,true}]]}} ]} ]), R = do([fun() -> exec_from_docker(Config, Host, HostPort, "hi_there.\r\n", [<<"hi_there">>], "") end, fun() -> sftp_tests_erl_server(Config, Host, HostPort, SftpRootDir, UserDir) end ]), ssh:stop_daemon(Server), R end). %%-------------------------------------------------------------------- send_recv_big_with_renegotiate_otp_is_client(Config) -> %% Connect to the remote openssh server: {IP,Port} = ip_port(Config), {ok,C} = ssh:connect(IP, Port, [{user,?USER}, {password,?PASSWD}, {user_dir, setup_remote_auth_keys_and_local_priv('ssh-rsa', Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]), %% Open a channel and exec the Linux 'cat' command at the openssh side. %% This 'cat' will read stdin and write to stdout until an eof is read from stdin. {ok, Ch1} = ssh_connection:session_channel(C, infinity), success = ssh_connection:exec(C, Ch1, "cat", infinity), %% Build big binary HalfSizeBytes = 100*1000*1000, Data = << <> || X <- lists:seq(1, HalfSizeBytes div 4)>>, %% Send the data. Must spawn a process to avoid deadlock. The client will block %% until all is sent through the send window. But the server will stop receiveing %% when the servers send-window towards the client is full. %% Since the client can't receive before the server has received all but 655k from the client %% ssh_connection:send/4 is blocking... spawn_link( fun() -> ct:comment("Sending ~p Mbytes with renegotiation in the middle",[2*byte_size(Data)/1000000]), %% ct:log("sending first ~p bytes",[byte_size(Data)]), ok = ssh_connection:send(C, Ch1, Data, 10000), %% ct:log("Init renegotiation test",[]), Kex1 = renegotiate_test(init, C), %% ct:log("sending next ~p bytes",[byte_size(Data)]), ok = ssh_connection:send(C, Ch1, Data, 10000), %% ct:log("Finnish renegotiation test",[]), renegotiate_test(Kex1, C), %% ct:log("sending eof",[]), ok = ssh_connection:send_eof(C, Ch1) %%, ct:log("READY, sent ~p bytes",[2*byte_size(Data)]) end), {eof,ReceivedData} = loop_until(fun({eof,_}) -> true; (_ ) -> false end, fun(Acc) -> %%ct:log("Get more ~p",[ ExpectedSize-byte_size(Acc) ]), receive {ssh_cm, C, {eof,Ch}} when Ch==Ch1 -> %% ct:log("eof received",[]), {eof,Acc}; {ssh_cm, C, {data,Ch,0,B}} when Ch==Ch1, is_binary(B) -> %% ct:log("(1) Received ~p bytes (total ~p), missing ~p bytes", %% [byte_size(B), %% byte_size(B)+byte_size(Acc), %% 2*byte_size(Data)-(byte_size(B)+byte_size(Acc))]), ssh_connection:adjust_window(C, Ch1, byte_size(B)), <> end end, <<>>), ExpectedData = <>, case ReceivedData of ExpectedData -> %% ct:log("Correct data returned",[]), %% receive close messages loop_until(fun(Left) -> %% ct:log("Expect: ~p",[Left]), Left == [] end, fun([Next|Rest]) -> receive {ssh_cm,C,Next} -> Rest end end, [%% Already received: {eof, Ch1}, {exit_status,Ch1,0}, {closed,Ch1}] ), ok; _ when is_binary(ReceivedData) -> ct:fail("~p bytes echoed but ~p expected", [byte_size(ReceivedData), 2*byte_size(Data)]) end. %%-------------------------------------------------------------------- %% Utilities --------------------------------------------------------- %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- %% %% A practical meta function %% loop_until(CondFun, DoFun, Acc) -> case CondFun(Acc) of true -> Acc; false -> loop_until(CondFun, DoFun, DoFun(Acc)) end. %%-------------------------------------------------------------------- %% %% Exec the Command in the docker. Add the arguments ExtraSshArg in the %% ssh command. %% %% If Expects is returned, then return 'ok', else return {fail,Msg}. %% exec_from_docker(Config, HostIP, HostPort, Command, Expects, ExtraSshArg) when is_binary(hd(Expects)), is_list(Config) -> {DockerIP,DockerPort} = ip_port(Config), {ok,C} = ssh:connect(DockerIP, DockerPort, [{user,?USER}, {password,?PASSWD}, {user_dir, new_dir(Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]), R = exec_from_docker(C, HostIP, HostPort, Command, Expects, ExtraSshArg, Config), ssh:close(C), R. exec_from_docker(C, DestIP, DestPort, Command, Expects, ExtraSshArg, Config) when is_binary(hd(Expects)) -> ExecCommand = lists:concat( ["sshpass -p ",?PASSWD," " | case proplists:get_value(ssh_version,Config) of "dropbear" ++ _ -> ["dbclient -y -y -p ",DestPort," ",ExtraSshArg," ",iptoa(DestIP)," "]; _ -> %% OpenSSH or compatible ["/buildroot/ssh/bin/ssh -o 'CheckHostIP=no' -o 'StrictHostKeyChecking=no' ", ExtraSshArg," -p ",DestPort," ",iptoa(DestIP)," "] end]) ++ Command, case exec(C, ExecCommand) of {ok,{ExitStatus,Result}} = R when ExitStatus == 0 -> case binary:match(Result, Expects) of nomatch -> ct:log("Result of~n ~s~nis~n ~p",[ExecCommand,R]), {fail, "Bad answer"}; _ -> ok end; {ok,_} = R -> ct:log("Result of~n ~s~nis~n ~p",[ExecCommand,R]), {fail, "Exit status =/= 0"}; R -> ct:log("Result of~n ~s~nis~n ~p",[ExecCommand,R]), {fail, "Couldn't login to host"} end. exec(C, Cmd) -> %% ct:log("~s",[Cmd]), {ok,Ch} = ssh_connection:session_channel(C, 10000), success = ssh_connection:exec(C, Ch, Cmd, 10000), result_of_exec(C, Ch). result_of_exec(C, Ch) -> result_of_exec(C, Ch, undefined, <<>>). result_of_exec(C, Ch, ExitStatus, Acc) -> receive {ssh_cm,C,{closed,Ch}} -> %%ct:log("CHAN ~p got *closed*",[Ch]), {ok, {ExitStatus, Acc}}; {ssh_cm,C,{exit_status,Ch,ExStat}} when ExitStatus == undefined -> %%ct:log("CHAN ~p got *exit status ~p*",[Ch,ExStat]), result_of_exec(C, Ch, ExStat, Acc); {ssh_cm,C,{data,Ch,_,Data}=_X} when ExitStatus == undefined -> %%ct:log("CHAN ~p got ~p",[Ch,_X]), result_of_exec(C, Ch, ExitStatus, <>); _Other -> %%ct:log("OTHER: ~p",[_Other]), result_of_exec(C, Ch, ExitStatus, Acc) after 5000 -> ct:log("NO MORE, received so far:~n~s",[Acc]), {error, timeout} end. %%-------------------------------------------------------------------- %% %% Loop through all {Tag,Alg} pairs in CommonAlgs, call DoTestFun(Tag,Alg) which %% returns one of {ok,C}, ok, or Other. %% %% The chk_all_algos returns 'ok' or {fail,FaledAlgosList} %% chk_all_algos(FunctionName, CommonAlgs, Config, DoTestFun) when is_function(DoTestFun,2) -> ct:comment("~p algorithms",[length(CommonAlgs)]), %% Check each algorithm Failed = lists:foldl( fun({Tag,Alg}, FailedAlgos) -> %% ct:log("Try ~p",[Alg]), case DoTestFun(Tag,Alg) of {ok,C} -> ssh:close(C), FailedAlgos; ok -> FailedAlgos; Other -> ct:log("FAILED! ~p ~p: ~p",[Tag,Alg,Other]), [{Alg,Other}|FailedAlgos] end end, [], CommonAlgs), ct:pal("~s", [format_result_table_use_all_algos(FunctionName, Config, CommonAlgs, Failed)]), case Failed of [] -> ok; _ -> {fail, Failed} end. %%%---------------------------------------------------------------- %%% %%% Call all Funs as Fun() which returns 'ok', {ok,C} or Other. %%% do/1 returns 'ok' or the first encountered value that is not %%% successful. %%% do(Funs) -> do(Funs, 1). do([Fun|Funs], N) -> case Fun() of ok -> %% ct:log("Fun ~p ok",[N]), do(Funs, N-1); {ok,C} -> %% ct:log("Fun ~p {ok,C}",[N]), ssh:close(C), do(Funs, N-1); Other -> ct:log("Fun ~p FAILED:~n~p",[N, Other]), Other end; do([], _) -> %% ct:log("All Funs ok",[]), ok. %%-------------------------------------------------------------------- %% %% Functions to set up local and remote host's and user's keys and directories %% setup_local_hostdir(KeyAlg, Config) -> setup_local_hostdir(KeyAlg, new_dir(Config), Config). setup_local_hostdir(KeyAlg, HostDir, Config) -> {ok, {Priv,Publ}} = host_priv_pub_keys(Config, KeyAlg), %% Local private and public key DstFile = filename:join(HostDir, dst_filename(host,KeyAlg)), ok = file:write_file(DstFile, Priv), ok = file:write_file(DstFile++".pub", Publ), HostDir. setup_remote_auth_keys_and_local_priv(KeyAlg, Config) -> {IP,Port} = ip_port(Config), setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, new_dir(Config), Config). setup_remote_auth_keys_and_local_priv(KeyAlg, UserDir, Config) -> {IP,Port} = ip_port(Config), setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, UserDir, Config). setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, Config) -> setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, new_dir(Config), Config). setup_remote_auth_keys_and_local_priv(KeyAlg, IP, Port, UserDir, Config) -> {ok, {Priv,Publ}} = user_priv_pub_keys(Config, KeyAlg), %% Local private and public keys DstFile = filename:join(UserDir, dst_filename(user,KeyAlg)), ok = file:write_file(DstFile, Priv), ok = file:write_file(DstFile++".pub", Publ), %% Remote auth_methods with public key {ok,Ch,Cc} = ssh_sftp:start_channel(IP, Port, [{user, ?USER }, {password, ?PASSWD }, {auth_methods, "password"}, {silently_accept_hosts,true}, {user_interaction,false} ]), _ = ssh_sftp:make_dir(Ch, ".ssh"), ok = ssh_sftp:write_file(Ch, ".ssh/authorized_keys", Publ), ok = ssh_sftp:write_file_info(Ch, ".ssh/authorized_keys", #file_info{mode=8#700}), ok = ssh_sftp:write_file_info(Ch, ".ssh", #file_info{mode=8#700}), ok = ssh_sftp:stop_channel(Ch), ok = ssh:close(Cc), UserDir. setup_remote_priv_and_local_auth_keys(KeyAlg, Config) -> {IP,Port} = ip_port(Config), setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, new_dir(Config), Config). setup_remote_priv_and_local_auth_keys(KeyAlg, UserDir, Config) -> {IP,Port} = ip_port(Config), setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, UserDir, Config). setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, Config) -> setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, new_dir(Config), Config). setup_remote_priv_and_local_auth_keys(KeyAlg, IP, Port, UserDir, Config) -> {ok, {Priv,Publ}} = user_priv_pub_keys(Config, KeyAlg), %% Local auth_methods with public key AuthKeyFile = filename:join(UserDir, "authorized_keys"), ok = file:write_file(AuthKeyFile, Publ), %% Remote private and public key {ok,Ch,Cc} = ssh_sftp:start_channel(IP, Port, [{user, ?USER }, {password, ?PASSWD }, {auth_methods, "password"}, {silently_accept_hosts,true}, {user_interaction,false} ]), rm_id_in_remote_dir(Ch, ".ssh"), _ = ssh_sftp:make_dir(Ch, ".ssh"), DstFile = filename:join(".ssh", dst_filename(user,KeyAlg)), ok = ssh_sftp:write_file(Ch, DstFile, Priv), ok = ssh_sftp:write_file_info(Ch, DstFile, #file_info{mode=8#700}), ok = ssh_sftp:write_file(Ch, DstFile++".pub", Publ), ok = ssh_sftp:write_file_info(Ch, ".ssh", #file_info{mode=8#700}), ok = ssh_sftp:stop_channel(Ch), ok = ssh:close(Cc), UserDir. rm_id_in_remote_dir(Ch, Dir) -> case ssh_sftp:list_dir(Ch, Dir) of {error,_Error} -> ok; {ok,FileNames} -> lists:foreach(fun("id_"++_ = F) -> ok = ssh_sftp:delete(Ch, filename:join(Dir,F)); (_) -> leave end, FileNames) end. user_priv_pub_keys(Config, KeyAlg) -> priv_pub_keys("users_keys", user, Config, KeyAlg). host_priv_pub_keys(Config, KeyAlg) -> priv_pub_keys("host_keys", host, Config, KeyAlg). priv_pub_keys(KeySubDir, Type, Config, KeyAlg) -> KeyDir = filename:join(proplists:get_value(data_dir,Config), KeySubDir), {ok,Priv} = file:read_file(filename:join(KeyDir,src_filename(Type,KeyAlg))), {ok,Publ} = file:read_file(filename:join(KeyDir,src_filename(Type,KeyAlg)++".pub")), {ok, {Priv,Publ}}. %%%---------------- The default filenames src_filename(user, 'ssh-rsa' ) -> "id_rsa"; src_filename(user, 'rsa-sha2-256' ) -> "id_rsa"; src_filename(user, 'rsa-sha2-512' ) -> "id_rsa"; src_filename(user, 'ssh-dss' ) -> "id_dsa"; src_filename(user, 'ssh-ed25519' ) -> "id_ed25519"; src_filename(user, 'ssh-ed448' ) -> "id_ed448"; src_filename(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa256"; src_filename(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa384"; src_filename(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa521"; src_filename(host, 'ssh-rsa' ) -> "ssh_host_rsa_key"; src_filename(host, 'rsa-sha2-256' ) -> "ssh_host_rsa_key"; src_filename(host, 'rsa-sha2-512' ) -> "ssh_host_rsa_key"; src_filename(host, 'ssh-dss' ) -> "ssh_host_dsa_key"; src_filename(host, 'ssh-ed25519' ) -> "ssh_host_ed25519_key"; src_filename(host, 'ssh-ed448' ) -> "ssh_host_ed448_key"; src_filename(host, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key256"; src_filename(host, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key384"; src_filename(host, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key521". dst_filename(user, 'ssh-rsa' ) -> "id_rsa"; dst_filename(user, 'rsa-sha2-256' ) -> "id_rsa"; dst_filename(user, 'rsa-sha2-512' ) -> "id_rsa"; dst_filename(user, 'ssh-dss' ) -> "id_dsa"; dst_filename(user, 'ssh-ed25519' ) -> "id_ed25519"; dst_filename(user, 'ssh-ed448' ) -> "id_ed448"; dst_filename(user, 'ecdsa-sha2-nistp256') -> "id_ecdsa"; dst_filename(user, 'ecdsa-sha2-nistp384') -> "id_ecdsa"; dst_filename(user, 'ecdsa-sha2-nistp521') -> "id_ecdsa"; dst_filename(host, 'ssh-rsa' ) -> "ssh_host_rsa_key"; dst_filename(host, 'rsa-sha2-256' ) -> "ssh_host_rsa_key"; dst_filename(host, 'rsa-sha2-512' ) -> "ssh_host_rsa_key"; dst_filename(host, 'ssh-dss' ) -> "ssh_host_dsa_key"; dst_filename(host, 'ssh-ed25519' ) -> "ssh_host_ed25519_key"; dst_filename(host, 'ssh-ed448' ) -> "ssh_host_ed448_key"; dst_filename(host, 'ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key"; dst_filename(host, 'ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key"; dst_filename(host, 'ecdsa-sha2-nistp521') -> "ssh_host_ecdsa_key". %%-------------------------------------------------------------------- %% %% Format the result table for chk_all_algos/4 %% format_result_table_use_all_algos(FunctionName, Config, CommonAlgs, Failed) -> %% Write a nice table with the result AlgHead = 'Algorithm', AlgWidth = lists:max([length(atom_to_list(A)) || {_,A} <- CommonAlgs]), {ResultTable,_} = lists:mapfoldl( fun({T,A}, Tprev) -> Tag = case T of Tprev -> ""; _ -> io_lib:format('~s~n',[T]) end, {io_lib:format('~s ~*s ~s~n', [Tag, -AlgWidth, A, case proplists:get_value(A,Failed) of undefined -> "(ok)"; Err -> io_lib:format("<<<< FAIL <<<< ~p",[Err]) end]), T} end, undefined, CommonAlgs), Vssh = proplists:get_value(ssh_version,Config,""), io_lib:format("~nResults of ~p, Peer version: ~s~n~n" "Tag ~*s Result~n" "=====~*..=s=======~n~s" ,[FunctionName, Vssh, -AlgWidth, AlgHead, AlgWidth, "", ResultTable]). %%-------------------------------------------------------------------- %% %% Docker handling: start_docker/1 and stop_docker/1 %% start_docker(Ver) -> Cmnd = lists:concat(["docker run -itd --rm -p 1234 ",?DOCKER_PFX,":",Ver]), Id0 = os:cmd(Cmnd), ct:log("Ver = ~p, Cmnd ~p~n-> ~p",[Ver,Cmnd,Id0]), case is_docker_sha(Id0) of true -> Id = hd(string:tokens(Id0, "\n")), IP = ip(Id), Port = 1234, {ok, {Ver,{IP,Port},Id}}; false -> throw(cant_start_docker) end. stop_docker({_Ver,_,Id}) -> Cmnd = lists:concat(["docker kill ",Id]), os:cmd(Cmnd). is_docker_sha(L) -> lists:all(fun(C) when $a =< C,C =< $z -> true; (C) when $0 =< C,C =< $9 -> true; ($\n) -> true; (_) -> false end, L). %%-------------------------------------------------------------------- %% %% Misc docker info functions ip_port(Config) -> {_Ver,{IP,Port},_} = proplists:get_value(id,Config), {IP,Port}. port_mapped_to(Id) -> Cmnd = lists:concat(["docker ps --format \"{{.Ports}}\" --filter id=",Id]), [_, PortStr | _] = string:tokens(os:cmd(Cmnd), ":->/"), list_to_integer(PortStr). ip(Id) -> Cmnd = lists:concat(["docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ", Id]), IPstr0 = os:cmd(Cmnd), ct:log("Cmnd ~p~n-> ~p",[Cmnd,IPstr0]), IPstr = hd(string:tokens(IPstr0, "\n")), {ok,IP} = inet:parse_address(IPstr), IP. %%-------------------------------------------------------------------- %% %% Normalize the host returned from ssh_test_lib iptoa({0,0,0,0}) -> inet_parse:ntoa(host_ip()); iptoa(IP) -> inet_parse:ntoa(IP). host_ip() -> {ok,Name} = inet:gethostname(), {ok,#hostent{h_addr_list = [IP|_]}} = inet_res:gethostbyname(Name), IP. %%-------------------------------------------------------------------- %% %% Create a new fresh directory or clear an existing one %% new_dir(Config) -> PrivDir = proplists:get_value(priv_dir, Config), SubDirName = integer_to_list(erlang:system_time()), Dir = filename:join(PrivDir, SubDirName), case file:read_file_info(Dir) of {error,enoent} -> ok = file:make_dir(Dir), Dir; _ -> timer:sleep(25), new_dir(Config) end. clear_dir(Dir) -> delete_all_contents(Dir), {ok,[]} = file:list_dir(Dir), Dir. delete_all_contents(Dir) -> {ok,Fs} = file:list_dir(Dir), lists:map(fun(F0) -> F = filename:join(Dir, F0), case filelib:is_file(F) of true -> file:delete(F); false -> case filelib:is_dir(F) of true -> delete_all_contents(F), file:del_dir(F); false -> ct:log("Neither file nor dir: ~p",[F]) end end end, Fs). %%-------------------------------------------------------------------- %% %% Find the intersection of algoritms for otp ssh and the docker ssh. %% Returns {ok, ServerHello, Server, ClientHello, Client} where Server are the algorithms common %% with the docker server and analogous for Client. %% %% Client may be undefined if no usable client is found. %% %% Both Server and Client are lists of {Tag,AlgName}. %% common_algs(Config, IP, Port) -> case remote_server_algs(IP, Port) of {ok, {ServerHello, RemoteServerKexInit}} -> RemoteServerAlgs = kexint_msg2default_algorithms(RemoteServerKexInit), Server = find_common_algs(RemoteServerAlgs, use_algorithms(ServerHello)), ct:log("Remote server:~n~p~n~p",[ServerHello, RemoteServerAlgs]), case remote_client_algs(Config) of {ok,{ClientHello,RemoteClientKexInit}} -> RemoteClientAlgs = kexint_msg2default_algorithms(RemoteClientKexInit), Client = find_common_algs(RemoteClientAlgs, use_algorithms(ClientHello)), ct:log("Remote client:~n~p~n~p",[ClientHello, RemoteClientAlgs]), {ok, ServerHello, Server, ClientHello, Client}; {error,_} =TO -> ct:log("Remote client algs can't be found: ~p",[TO]), {ok, ServerHello, Server, undefined, undefined}; Other -> Other end; Other -> Other end. chk_hellos(Hs, Str) -> lists:foldl( fun(H, Acc) -> try binary:split(H, <<"-">>, [global]) of %% [<<"SSH">>,<<"2.0">>|_] -> %% Acc; [<<"SSH">>,OldVer = <<"1.",_/binary>>|_] -> io_lib:format("~s, Old SSH ver ~s",[Acc,OldVer]); _ -> Acc catch _:_ -> Acc end end, Str, Hs). find_common_algs(Remote, Local) -> [{T,V} || {T,Vs} <- ssh_test_lib:extract_algos( ssh_test_lib:intersection(Remote, Local)), V <- Vs]. use_algorithms(RemoteHelloBin) -> MyAlgos = ssh:chk_algos_opts( [{modify_algorithms, [{append, [{kex,['diffie-hellman-group1-sha1']} ]} ]} ]), ssh_transport:adjust_algs_for_peer_version(binary_to_list(RemoteHelloBin)++"\r\n", MyAlgos). kexint_msg2default_algorithms(#ssh_msg_kexinit{kex_algorithms = Kex, server_host_key_algorithms = PubKey, encryption_algorithms_client_to_server = CipherC2S, encryption_algorithms_server_to_client = CipherS2C, mac_algorithms_client_to_server = MacC2S, mac_algorithms_server_to_client = MacS2C, compression_algorithms_client_to_server = CompC2S, compression_algorithms_server_to_client = CompS2C }) -> [{kex, ssh_test_lib:to_atoms(Kex)}, {public_key, ssh_test_lib:to_atoms(PubKey)}, {cipher, [{client2server,ssh_test_lib:to_atoms(CipherC2S)}, {server2client,ssh_test_lib:to_atoms(CipherS2C)}]}, {mac, [{client2server,ssh_test_lib:to_atoms(MacC2S)}, {server2client,ssh_test_lib:to_atoms(MacS2C)}]}, {compression, [{client2server,ssh_test_lib:to_atoms(CompC2S)}, {server2client,ssh_test_lib:to_atoms(CompS2C)}]}]. %%-------------------------------------------------------------------- %% %% Find the algorithms supported by the remote server %% %% Connect with tcp to the server, send a hello and read the returned %% server hello and kexinit message. %% remote_server_algs(IP, Port) -> case try_gen_tcp_connect(IP, Port, 5) of {ok,S} -> ok = gen_tcp:send(S, "SSH-2.0-CheckAlgs\r\n"), receive_hello(S); {error,Error} -> {error,Error} end. try_gen_tcp_connect(IP, Port, N) when N>0 -> case gen_tcp:connect(IP, Port, [binary]) of {ok,S} -> {ok,S}; {error,_Error} when N>1 -> receive after 1000 -> ok end, try_gen_tcp_connect(IP, Port, N-1); {error,Error} -> {error,Error} end; try_gen_tcp_connect(_, _, _) -> {error, "No contact"}. %%-------------------------------------------------------------------- %% %% Find the algorithms supported by the remote client %% %% Set up a fake ssh server and make the remote client connect to it. Use %% hello message and the kexinit message. %% remote_client_algs(Config) -> Parent = self(), Ref = make_ref(), spawn( fun() -> {ok,Sl} = gen_tcp:listen(0, [binary]), {ok,{IP,Port}} = inet:sockname(Sl), Parent ! {addr,Ref,IP,Port}, {ok,S} = gen_tcp:accept(Sl), ok = gen_tcp:send(S, "SSH-2.0-CheckAlgs\r\n"), Parent ! {Ref,receive_hello(S)} end), receive {addr,Ref,IP,Port} -> spawn(fun() -> exec_from_docker(Config, IP, Port, "howdy.\r\n", [<<"howdy">>], "") end), receive {Ref, Result} -> Result after 5000 -> {error, {timeout,2}} end after 5000 -> {error, {timeout,1}} end. %%% Receive a few packets from the remote server or client and find what is supported: receive_hello(S) -> try receive_hello(S, <<>>) of Result -> Result catch Class:Error -> ST = erlang:get_stacktrace(), {error, {Class,Error,ST}} end. receive_hello(S, Ack) -> %% The Ack is to collect bytes until the full message is received receive {tcp, S, Bin0} when is_binary(Bin0) -> case binary:split(<>, [<<"\r\n">>,<<"\r">>,<<"\n">>]) of [Hello = <<"SSH-2.0-",_/binary>>, NextPacket] -> %% ct:log("Got 2.0 hello (~p), ~p bytes to next msg",[Hello,size(NextPacket)]), {ok, {Hello, receive_kexinit(S, NextPacket)}}; [Hello = <<"SSH-1.99-",_/binary>>, NextPacket] -> %% ct:log("Got 1.99 hello (~p), ~p bytes to next msg",[Hello,size(NextPacket)]), {ok, {Hello, receive_kexinit(S, NextPacket)}}; [Bin] when size(Bin) < 256 -> %% ct:log("Got part of hello (~p chars):~n~s~n~s",[size(Bin),Bin, %% [io_lib:format('~2.16.0b ',[C]) %% || C <- binary_to_list(Bin0) %% ] %% ]), receive_hello(S, Bin0); _ -> ct:log("Bad hello string (line ~p, ~p chars):~n~s~n~s",[?LINE,size(Bin0),Bin0, [io_lib:format('~2.16.0b ',[C]) || C <- binary_to_list(Bin0) ] ]), ct:fail("Bad hello string received") end; Other -> ct:log("Bad hello string (line ~p):~n~p",[?LINE,Other]), ct:fail("Bad hello string received") after 10000 -> ct:log("Timeout waiting for hello!~n~s",[Ack]), throw(timeout) end. receive_kexinit(_S, <>) when PacketLen < 5000, % heuristic max len to stop huge attempts if packet decodeing get out of sync size(PayloadAndPadding) >= (PacketLen-1) % Need more bytes? -> ct:log("Has all ~p packet bytes",[PacketLen]), PayloadLen = PacketLen - PaddingLen - 1, <> = PayloadAndPadding, ssh_message:decode(Payload); receive_kexinit(S, Ack) -> ct:log("Has ~p bytes, need more",[size(Ack)]), receive {tcp, S, Bin0} when is_binary(Bin0) -> receive_kexinit(S, <>); Other -> ct:log("Bad hello string (line ~p):~n~p",[?LINE,Other]), ct:fail("Bad hello string received") after 10000 -> ct:log("Timeout waiting for kexinit!~n~s",[Ack]), throw(timeout) end. %%%---------------------------------------------------------------- %%% Test of sftp from the OpenSSH client side %%% sftp_tests_erl_server(Config, ServerIP, ServerPort, ServerRootDir, UserDir) -> try Cmnds = prepare_local_directory(ServerRootDir), call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir), check_local_directory(ServerRootDir) catch Class:Error -> ST = erlang:get_stacktrace(), {error, {Class,Error,ST}} end. prepare_local_directory(ServerRootDir) -> file:write_file(filename:join(ServerRootDir,"tst1"), <<"Some test text">> ), ["get tst1", "put tst1 tst2", "put tst1 tst3", "rename tst1 ex_tst1", "rm tst3", "mkdir mydir", "cd mydir", "put tst1 file_1", "put tst1 unreadable_file", "chmod 222 unreadable_file", "exit"]. check_local_directory(ServerRootDir) -> TimesToTry = 3, % sleep 0.5, 1, 2 and then 4 secs (7.5s in total) check_local_directory(ServerRootDir, 500, TimesToTry-1). check_local_directory(ServerRootDir, SleepTime, N) -> case do_check_local_directory(ServerRootDir) of {error,Error} when N>0 -> %% Could be that the erlang side is faster and the docker's operations %% are not yet finalized. %% Sleep for a while and retry a few times: timer:sleep(SleepTime), check_local_directory(ServerRootDir, 2*SleepTime, N-1); Other -> Other end. do_check_local_directory(ServerRootDir) -> case lists:sort(ok(file:list_dir(ServerRootDir)) -- [".",".."]) of ["ex_tst1","mydir","tst2"] -> {ok,Expect} = file:read_file(filename:join(ServerRootDir,"ex_tst1")), case file:read_file(filename:join(ServerRootDir,"tst2")) of {ok,Expect} -> case lists:sort(ok(file:list_dir(filename:join(ServerRootDir,"mydir"))) -- [".",".."]) of ["file_1","unreadable_file"] -> case file:read_file(filename:join([ServerRootDir,"mydir","file_1"])) of {ok,Expect} -> case file:read_file(filename:join([ServerRootDir,"mydir","unreadable_file"])) of {error,_} -> ok; {ok,_} -> {error, {could_read_unreadable,"mydir/unreadable_file"}} end; {ok,Other} -> ct:log("file_1:~n~s~nExpected:~n~s",[Other,Expect]), {error, {bad_contents_in_file,"mydir/file_1"}} end; Other -> ct:log("Directory ~s~n~p",[filename:join(ServerRootDir,"mydir"),Other]), {error,{bad_dir_contents,"mydir"}} end; {ok,Other} -> ct:log("tst2:~n~s~nExpected:~n~s",[Other,Expect]), {error, {bad_contents_in_file,"tst2"}} end; ["tst1"] -> {error,{missing_file,"tst2"}}; Other -> ct:log("Directory ~s~n~p",[ServerRootDir,Other]), {error,{bad_dir_contents,"/"}} end. call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) -> {DockerIP,DockerPort} = ip_port(Config), {ok,C} = ssh:connect(DockerIP, DockerPort, [{user,?USER}, {password,?PASSWD}, {user_dir, UserDir}, {silently_accept_hosts,true}, {user_interaction,false} ]), %% Make commands for "expect" in the docker: PreExpectCmnds = ["spawn /buildroot/ssh/bin/sftp -oPort="++integer_to_list(ServerPort)++ " -oCheckHostIP=no -oStrictHostKeyChecking=no " ++ iptoa(ServerIP)++"\n" ], PostExpectCmnds= [], ExpectCmnds = PreExpectCmnds ++ ["expect \"sftp>\" {send \""++Cmnd++"\n\"}\n" || Cmnd <- Cmnds] ++ PostExpectCmnds, %% Make an commands file in the docker {ok,Ch} = ssh_sftp:start_channel(C, [{timeout,10000}]), ok = ssh_sftp:write_file(Ch, "commands", erlang:iolist_to_binary(ExpectCmnds)), ok = ssh_sftp:stop_channel(Ch), %% Call expect in the docker {ok, Ch1} = ssh_connection:session_channel(C, infinity), Kex1 = renegotiate_test(init, C), success = ssh_connection:exec(C, Ch1, "expect commands", infinity), renegotiate_test(Kex1, C), recv_log_msgs(C, Ch1), %% Done. ssh:close(C). recv_log_msgs(C, Ch) -> receive {ssh_cm,C,{closed,Ch}} -> %% ct:log("Channel closed ~p",[{closed,1}]), ok; {ssh_cm,C,{data,Ch,1,Msg}} -> ct:log("*** ERROR from docker:~n~s",[Msg]), recv_log_msgs(C, Ch); {ssh_cm,C,_Msg} -> %% ct:log("Got ~p",[_Msg]), recv_log_msgs(C, Ch) end. %%%---------------------------------------------------------------- %%%---------------------------------------------------------------- %%% %%% Tests from the Erlang client side %%% %%%---------------------------------------------------------------- %%%---------------------------------------------------------------- test_erl_client_reneg({ok,C}, Spec) -> %% Start the test processes on the connection C: Parent = self(), Pids = [spawn( fun() -> Parent ! {self(), TestType, Id, one_test_erl_client(TestType,Id,C)} end ) || {TestType,N} <- Spec, Id <- lists:seq(1,N)], Kex1 = renegotiate_test(init, C), %% Collect the results: case lists:filter( fun(R) -> R=/=ok end, [receive {Pid,_TestType,_Id,ok} -> %% ct:log("Test ~p:~p passed!", [_TestType,_Id]), ok; {Pid,TestType,Id,OtherResult} -> ct:log("~p:~p ~p ~p~n~p",[?MODULE,?LINE,TestType,Id,OtherResult]), {error,TestType,Id} end || Pid <- Pids]) of [] -> renegotiate_test(Kex1, C), {ok,C}; Other -> renegotiate_test(Kex1, C), Other end; test_erl_client_reneg(Error, _) -> Error. one_test_erl_client(exec, Id, C) -> {ok, Ch} = ssh_connection:session_channel(C, infinity), success = ssh_connection:exec(C, Ch, "echo Hi there", 5000), case loop_until(fun({eof,_}) -> true; (_ ) -> false end, fun(Acc) -> receive {ssh_cm, C, {eof,Ch}} -> {eof,Acc}; {ssh_cm, C, {data,Ch,0,B}} when is_binary(B) -> <> end end, <<>>) of {eof,<<"Hi there\n">>} -> ok; Other -> ct:pal("exec Got other ~p", [Other]), {error, {exec,Id,bad_msg,Other,undefined}} end; one_test_erl_client(no_subsyst, Id, C) -> {ok, Ch} = ssh_connection:session_channel(C, infinity), case ssh_connection:subsystem(C, Ch, "foo", infinity) of failure -> ok; Other -> ct:pal("no_subsyst Got other ~p", [Other]), {error, {no_subsyst,Id,bad_ret,Other,undefined}} end; one_test_erl_client(setenv, Id, C) -> {ok, Ch} = ssh_connection:session_channel(C, infinity), Var = "ENV_TEST", Value = lists:concat(["env_test_",Id,"_",erlang:system_time()]), Env = case ssh_connection:setenv(C, Ch, Var, Value, infinity) of success -> binary_to_list(Value++"\n"); failure -> <<"\n">> end, success = ssh_connection:exec(C, Ch, "echo $"++Var, 5000), case loop_until(fun({eof,_}) -> true; (_ ) -> false end, fun(Acc) -> receive {ssh_cm, C, {eof,Ch}} -> {eof,Acc}; {ssh_cm, C, {data,Ch,0,B}} when is_binary(B) -> <> end end, <<>>) of {eof,Env} -> ok; Other -> ct:pal("setenv Got other ~p", [Other]), {error, {setenv,Id,bad_msg,Other,undefined}} end; one_test_erl_client(SFTP, Id, C) when SFTP==sftp ; SFTP==sftp_async -> try {ok,Ch} = ssh_sftp:start_channel(C, [{timeout,10000}]), %% A new fresh name of a new file tree: RootDir = lists:concat(["r_",Id,"_",erlang:system_time()]), %% Check that it does not exist: false = lists:member(RootDir, ok(ssh_sftp:list_dir(Ch, "."))), %% Create it: ok = ssh_sftp:make_dir(Ch, RootDir), {ok, #file_info{type=directory, access=read_write}} = ssh_sftp:read_file_info(Ch, RootDir), R = do_sftp_tests_erl_client(SFTP, C, Ch, Id, RootDir), catch ssh_sftp:stop_channel(Ch), R catch Class:Error -> ST = erlang:get_stacktrace(), {error, {SFTP,Id,Class,Error,ST}} end. do_sftp_tests_erl_client(sftp_async, _C, Ch, _Id, RootDir) -> FileName1 = "boring_name", F1 = filename:join(RootDir, FileName1), %% Open a new handle and start writing: {ok,Handle1} = ssh_sftp:open(Ch, F1, [write,binary]), {async,Aref1} = ssh_sftp:awrite(Ch, Handle1, <<0:250000/unsigned-unit:8>>), wait_for_async_result(Aref1); do_sftp_tests_erl_client(sftp, _C, Ch, _Id, RootDir) -> FileName0 = "f0", F0 = filename:join(RootDir, FileName0), %% Create and write a file: ok = ssh_sftp:write_file(Ch, F0 = filename:join(RootDir, FileName0), Data0 = mkbin(1234,240)), {ok,Data0} = ssh_sftp:read_file(Ch, F0), {ok, #file_info{type=regular, access=read_write, size=1234}} = ssh_sftp:read_file_info(Ch, F0), %% Re-write: {ok,Handle0} = ssh_sftp:open(Ch, F0, [write,read,binary]), ok = ssh_sftp:pwrite(Ch, Handle0, 16, Data0_1=mkbin(10,255)), <> = Data0, FileContents = <>, <<_:1/binary, Part:25/binary, _/binary>> = FileContents, {ok, Part} = ssh_sftp:pread(Ch, Handle0, 1, 25), %% Check: {ok, FileContents} = ssh_sftp:pread(Ch, Handle0, 0, 1234), ok = ssh_sftp:close(Ch, Handle0), %% Check in another way: {ok, FileContents} = ssh_sftp:read_file(Ch, F0), %% Remove write access rights and check that it can't be written: ok = ssh_sftp:write_file_info(Ch, F0, #file_info{mode=8#400}), %read}), {ok, #file_info{type=regular, access=read}} = ssh_sftp:read_file_info(Ch, F0), {error,permission_denied} = ssh_sftp:write_file(Ch, F0, mkbin(10,14)), %% Test deletion of file and dir: [FileName0] = ok(ssh_sftp:list_dir(Ch, RootDir)) -- [".", ".."], ok = ssh_sftp:delete(Ch, F0), [] = ok(ssh_sftp:list_dir(Ch, RootDir)) -- [".", ".."], ok = ssh_sftp:del_dir(Ch, RootDir), false = lists:member(RootDir, ok(ssh_sftp:list_dir(Ch, "."))), ok. wait_for_async_result(Aref) -> receive {async_reply, Aref, Result} -> Result after 60000 -> timeout end. mkbin(Size, Byte) -> list_to_binary(lists:duplicate(Size,Byte)). ok({ok,X}) -> X. %%%---------------------------------------------------------------- renegotiate_test(init, ConnectionRef) -> Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), ssh_connection_handler:renegotiate(ConnectionRef), %%ct:log("Renegotiate test initiated!",[]), Kex1; renegotiate_test(Kex1, ConnectionRef) -> case ssh_test_lib:get_kex_init(ConnectionRef) of Kex1 -> ct:log("Renegotiate test failed, Kex1 == Kex2!",[]), error(renegotiate_failed); _ -> %% ct:log("Renegotiate test passed!",[]), ok end.