%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2017. 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(PWD, "foobar"). -define(DOCKER_PFX, "ssh_compat_suite-ssh"). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- suite() -> [%%{ct_hooks,[ts_install_cth]}, {timetrap,{seconds,40}}]. all() -> [{group,G} || G <- vers()]. groups() -> [{G, [], tests()} || G <- vers()]. tests() -> [login_with_password_otp_is_client, login_with_password_otp_is_server, login_with_keyboard_interactive_otp_is_client, login_with_keyboard_interactive_otp_is_server, login_with_all_public_keys_otp_is_client, login_with_all_public_keys_otp_is_server, all_algorithms_otp_is_client, all_algorithms_otp_is_server ]. vers() -> 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)"), Config. init_per_group(G, Config) -> case lists:member(G, vers()) of true -> try start_docker(G) of {ok,ID} -> ct:log("==> ~p",[G]), [Vssh|VsslRest] = string:tokens(atom_to_list(G), "-"), Vssl = lists:flatten(lists:join($-,VsslRest)), ct:comment("+++ ~s + ~s +++",[Vssh,Vssl]), %% Find the algorithms that both client and server supports: {IP,Port} = ip_port([{id,ID}]), try common_algs([{id,ID}|Config], IP, Port) of {ok, RemoteServerCommon, RemoteClientCommon} -> [{ssh_version,Vssh},{ssl_version,Vssl}, {id,ID}, {common_server_algs,RemoteServerCommon}, {common_client_algs,RemoteClientCommon} |Config]; 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 -> Config end. end_per_group(_, Config) -> catch stop_docker(proplists:get_value(id,Config)), Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- login_with_password_otp_is_client(Config) -> {IP,Port} = ip_port(Config), {ok,C} = ssh:connect(IP, Port, [{auth_methods,"password"}, {user,?USER}, {password,?PWD}, {user_dir, new_dir(Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]), ssh:close(C). %%-------------------------------------------------------------------- login_with_password_otp_is_server(Config) -> {Server, Host, HostPort} = ssh_test_lib:daemon(0, [{auth_methods,"password"}, {system_dir, setup_local_hostdir('ssh-rsa',Config)}, {user_dir, new_dir(Config)}, {user_passwords, [{?USER,?PWD}]}, {failfun, fun ssh_test_lib:failfun/2} ]), R = exec_from_docker(Config, Host, HostPort, "'lists:concat([\"Answer=\",1+2]).\r\n'", [<<"Answer=3">>], ""), ssh:stop_daemon(Server), R. %%-------------------------------------------------------------------- login_with_keyboard_interactive_otp_is_client(Config) -> {DockerIP,DockerPort} = ip_port(Config), {ok,C} = ssh:connect(DockerIP, DockerPort, [{auth_methods,"keyboard-interactive"}, {user,?USER}, {password,?PWD}, {user_dir, new_dir(Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]), ssh:close(C). %%-------------------------------------------------------------------- login_with_keyboard_interactive_otp_is_server(Config) -> {Server, Host, HostPort} = ssh_test_lib:daemon(0, [{auth_methods,"keyboard-interactive"}, {system_dir, setup_local_hostdir('ssh-rsa',Config)}, {user_dir, new_dir(Config)}, {user_passwords, [{?USER,?PWD}]}, {failfun, fun ssh_test_lib:failfun/2} ]), R = exec_from_docker(Config, Host, HostPort, "'lists:concat([\"Answer=\",1+3]).\r\n'", [<<"Answer=4">>], ""), ssh:stop_daemon(Server), R. %%-------------------------------------------------------------------- login_with_all_public_keys_otp_is_client(Config) -> CommonAlgs = [{public_key_from_host,A} || {public_key,A} <- proplists:get_value(common_server_algs, Config)], {DockerIP,DockerPort} = ip_port(Config), chk_all_algos(CommonAlgs, Config, fun(_Tag,Alg) -> ssh:connect(DockerIP, DockerPort, [{auth_methods, "publickey"}, {user, ?USER}, {user_dir, setup_remote_auth_keys_and_local_priv(Alg, Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]) end). %%-------------------------------------------------------------------- login_with_all_public_keys_otp_is_server(Config) -> CommonAlgs = [{public_key_to_host,A} || {public_key,A} <- proplists:get_value(common_client_algs, Config)], UserDir = new_dir(Config), {Server, Host, HostPort} = ssh_test_lib:daemon(0, [{auth_methods, "publickey"}, {system_dir, setup_local_hostdir('ssh-rsa',Config)}, {user_dir, UserDir}, {user_passwords, [{?USER,?PWD}]}, {failfun, fun ssh_test_lib:failfun/2} ]), R = chk_all_algos(CommonAlgs, Config, fun(_Tag,Alg) -> setup_remote_priv_and_local_auth_keys(Alg, clear_dir(UserDir), Config), exec_from_docker(Config, Host, HostPort, "'lists:concat([\"Answer=\",1+4]).\r\n'", [<<"Answer=5">>], "") end), ssh:stop_daemon(Server), R. %%-------------------------------------------------------------------- all_algorithms_otp_is_client(Config) -> CommonAlgs = proplists:get_value(common_server_algs, Config), {IP,Port} = ip_port(Config), chk_all_algos(CommonAlgs, Config, fun(Tag, Alg) -> ssh:connect(IP, Port, [{user,?USER}, {password,?PWD}, {auth_methods, "password"}, {user_dir, new_dir(Config)}, {preferred_algorithms, [{Tag,[Alg]}]}, {silently_accept_hosts,true}, {user_interaction,false} ]) end). %%-------------------------------------------------------------------- all_algorithms_otp_is_server(Config) -> CommonAlgs = proplists:get_value(common_client_algs, Config), UserDir = setup_remote_priv_and_local_auth_keys('ssh-rsa', Config), chk_all_algos(CommonAlgs, Config, fun(Tag,Alg) -> HostKeyAlg = case Tag of public_key -> Alg; _ -> 'ssh-rsa' end, {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,?PWD}]}, {failfun, fun ssh_test_lib:failfun/2} ]), R = exec_from_docker(Config, Host, HostPort, "hi_there.\r\n", [<<"hi_there">>], ""), ssh:stop_daemon(Server), R end). %%-------------------------------------------------------------------- %% Utilities --------------------------------------------------------- %%-------------------------------------------------------------------- exec_from_docker(WhatEver, {0,0,0,0}, HostPort, Command, Expects, ExtraSshArg) -> exec_from_docker(WhatEver, host_ip(), HostPort, Command, Expects, ExtraSshArg); 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,?PWD}, {user_dir, new_dir(Config)}, {silently_accept_hosts,true}, {user_interaction,false} ]), R = exec_from_docker(C, HostIP, HostPort, Command, Expects, ExtraSshArg), ssh:close(C), R; exec_from_docker(C, HostIP, HostPort, Command, Expects, ExtraSshArg) when is_binary(hd(Expects)) -> SSH_from_docker = lists:concat(["sshpass -p ",?PWD," ", "/buildroot/ssh/bin/ssh -p ",HostPort," -o 'CheckHostIP=no' -o 'StrictHostKeyChecking=no' ", ExtraSshArg," ", inet_parse:ntoa(HostIP)," " ]), ExecCommand = SSH_from_docker ++ Command, R = exec(C, ExecCommand), case R of {ok,{ExitStatus,Result}} 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,_} -> ct:log("Result of~n ~s~nis~n ~p",[ExecCommand,R]), {fail, "Exit status =/= 0"}; _ -> 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), exec_result(C, Ch). exec_result(C, Ch) -> exec_result(C, Ch, undefined, <<>>). exec_result(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]), exec_result(C, Ch, ExStat, Acc); {ssh_cm,C,{data,Ch,_,Data}=_X} when ExitStatus == undefined -> %%ct:log("CHAN ~p got ~p",[Ch,_X]), exec_result(C, Ch, ExitStatus, <>); _Other -> %%ct:log("OTHER: ~p",[_Other]), exec_result(C, Ch, ExitStatus, Acc) after 5000 -> %%ct:log("NO MORE, received so far:~n~s",[Acc]), {error, timeout} end. chk_all_algos(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|FailedAlgos] end end, [], CommonAlgs), ct:pal("~s", [format_result_table_use_all_algos(Config, CommonAlgs, Failed)]), case Failed of [] -> ok; _ -> {fail, Failed} end. 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, ?PWD }, {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, ?PWD }, {auth_methods, "password"}, {silently_accept_hosts,true}, {user_interaction,false} ]), _ = 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. 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}}. 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, '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, '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, '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, '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_result_table_use_all_algos(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 lists:member(A,Failed) of true -> "<<<< FAIL <<<<"; false-> "(ok)" end]), T} end, undefined, CommonAlgs), Vssh = proplists:get_value(ssh_version,Config,""), Vssl = proplists:get_value(ssl_version,Config,""), io_lib:format("~nResults, Peer versions: ~s and ~s~n" "Tag ~*s Result~n" "=====~*..=s=======~n~s" ,[Vssh,Vssl, -AlgWidth,AlgHead, AlgWidth, "", ResultTable]). 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). 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. 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). common_algs(Config, IP, Port) -> case remote_server_algs(IP, Port) of {ok, {RemoteHelloBin, RemoteServerKexInit}} -> case remote_client_algs(Config) of {ok,{_Hello,RemoteClientKexInit}} -> RemoteServerAlgs = kexint_msg2default_algorithms(RemoteServerKexInit), Server = find_common_algs(RemoteServerAlgs, use_algorithms(RemoteHelloBin)), RemoteClientAlgs = kexint_msg2default_algorithms(RemoteClientKexInit), Client = find_common_algs(RemoteClientAlgs, use_algorithms(RemoteHelloBin)), ct:log("Docker server algorithms:~n ~p~n~nDocker client algorithms:~n ~p", [RemoteServerAlgs,RemoteClientAlgs]), {ok, Server, Client}; Other -> Other end; Other -> Other end. 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)}]}]. 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"}. 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 15000 -> {error, timeout2} end after 15000 -> {error, timeout1} 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:comment("Old SSH ~s",["1.99"]), 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. host_ip() -> {ok,Name} = inet:gethostname(), {ok,#hostent{h_addr_list = [IP|_]}} = inet_res:gethostbyname(Name), IP.