aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssh/test/ssh_compat_SUITE.erl
blob: 8e82527c6e02fb192e6bb596407f32ebf51a7555 (plain) (tree)
1
2
3
4


                   
                                                        





























                                                                           

                                   






                                                                      
                              

        

                                             

           







                                                                                     


      
                       






























                                                                                   
                     


           







                                                                  
 




                                                 
               



                                                      

                                  
                                                 

                                                                                
















                                                                                                 

                                                              






                                                                                     





















                                                                            
                   

        






                                                              



                                                                      


                                                                                                         

                                                                      




























                                                                                                    
                       
    

                                                                      







































                                                                                                    

                                                                      

                                                                        
                                
                                                     
                                  















                                                                                                         



                                                                      

                                                                        
                                                                       
                                                     




                                                             

                                                                  




                                                                                                         






                                                                                                    
                                                     









                                                                                                             




                                                                      





















































































                                                                                                           

                                                                      
 


















                                                                        




                                                                                                      
                                             



                                                       
                                                                                     
                 






                                                                                                           


                                                                                           






                                                                                                  






                                                                            
                     

                                                                    
            




                                                                    
               
                          

                                                       
                          

 

                                           
 
                                         






                                                                          
                                               


                                                                      
                                                                           


                                           
                                                  

                 
                                                          



                            








                                                                                           




                                                     
                                            







                                                                      
                                                   

                               
                                                                                                






                          


































                                                                              





























                                                                                     
                                                                          






























                                                                                       
                                                                          



                                                                                
                                    









                                                                        











                                                                                 









                                                                                        
                                         



                                                      

                                                          






                                                                

                                                                    







                                                                     

                                                          






                                                                

                                                                    




                                                                  




                                                                              











                                                                           


                                                                                     




                                                      
                                                         

                                          

                                      

                                               



                                                                      

























                                                                               



                                                                      

















                                                                                                               
















                                                                       



































                                                                            










                                                                                                

                                        




                                                                                  
                                              
                                                         

                                                                                          





                                                                                     







                         

















                                                                       




































                                                                                                  






                                                                      



                                                          
                             

















                                               






                                                                         









                                                            
                                             











                                                            

                                        
               

                                


        













                                                                                      






                                                                                             
                                                                                                   


                                                                  
                                                                                                    


                                                                  




                                                                                                      










































                                                                                                      


                                                                   
 









                                                                              
 
 















                                                        
 
                                       















                                                                                  

































                                                                                                                    
 



































                                                                                            
 


























































































































































































































                                                                                                   
%%
%% %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:32>> || 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)),
                                   <<Acc/binary, B/binary>>
                           end
                   end,
                   <<>>),

    ExpectedData = <<Data/binary, Data/binary>>,
    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, <<Acc/binary, Data/binary>>);

        _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(<<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: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.

%%%----------------------------------------------------------------
%%% 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) ->
                                    <<Acc/binary, B/binary>>
                            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) ->
                                    <<Acc/binary, B/binary>>
                            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)),

    <<B1:16/binary, _:10/binary, B2:(1234-26)/binary>> = Data0,
    FileContents = <<B1:16/binary, Data0_1:10/binary, B2:(1234-26)/binary>>,

    <<_: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.