diff options
Diffstat (limited to 'lib/ssh/test/ssh_compat_SUITE.erl')
| -rw-r--r-- | lib/ssh/test/ssh_compat_SUITE.erl | 814 | 
1 files changed, 814 insertions, 0 deletions
diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl new file mode 100644 index 0000000000..74ab5aca3a --- /dev/null +++ b/lib/ssh/test/ssh_compat_SUITE.erl @@ -0,0 +1,814 @@ +%% +%% %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, <<Acc/binary, Data/binary>>); + +        _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(<<Ack/binary, Bin0/binary>>, [<<"\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, <<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) +  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, +    <<Payload:PayloadLen/binary, _Padding:PaddingLen/binary>> = 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, <<Ack/binary, Bin0/binary>>); +        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. + +  | 
