aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssh/test/ssh_basic_SUITE.erl
blob: 406c0c071e0bfe82c8e54fe6bde522f67f1e5022 (plain) (tree)
1
2
3
4


                   
                                                        


















                                                                         
                                           
                                        
                                        





                                                           
                               
                                                                      
                                                                      
                                                                      
 

                                  
 
         
              
                
                      
                      

                           
                             
                          
                            

                                
                          
                  
                         
                              

                                                      

                                     

                                 
                          





                                 

                             
 
           

                                  
                                       
                                       
                                            
                                                                         



                                                                          



                                                                      


                                           
      
 
 
                
                                    
                                                     
                                                                        
 
 

                                                                      
                        









                                                                      

                                          









                                             









                                                                     





                                                                   























                                                                                          

                            
 

                                         







                                        







                                        




                                        

                           



                                                                      
 












                                                                                 
                                                                      

                                              


                                        
                                                                      




                                                                      



                                                                             



                                                                 













                                                                      
    





                                                          
 


















                                                                      
 
                                                                      

                                                     

                                    
                                                                 

                                        
                                                                     
                                                                 


                                                                                     
                                                              
                                                                      







                                                                               
                           












                                                                               
                           




                                                                      

                                                  

                                               
                                                                 
                                         

                                                                                         
                                                                                            



                                                                                     
                                                              
                                                                      







                                                                              
                          




                                                                      

                                 


                                                                 
 



                                                                                     
                                                                        
                                                              

                                                                    



                                                                   
                                                                                 


                                                                      

                                 
                
                                          


                                                                     
                                                                 
                                                                                   

                                                                   
                                                                
 


                                                                        

                                                             


                                                                    
                            




                                                                      
















































































































































                                                                             

                                           

                                     
                                                                 
                                        
   
                                                                                           
                                                                                       
                  

                                        
                                                        
           
                         
                                         
                        
                                                                


                               
                                                                      


































                                                                                          


                                                                              
                                                      
                                          

                                        
                                                                      
                                                                  

                                                                                      
                                                                         

                                                                                    


                                                                      

                                                                  
                                                      

                                                                                           
                           
                                       
                                                                  
                                                                 







                                                                        


                                                                            
                     




                                                               
    
                                                                 





                                                                      

                                                                  
                                                          

                                                                                           

                                          
                                                                  
                                                                 







                                                                                     

                             


                                                                            




                                                               
                     




                                                               


                                                                      









































                                                                      























































































                                                                                            




































































                                                                                           

                                                           
                                           
                                          
                                         
    
                                                                                         
                                                                                     
 
                                                       

                                                 
                   
                                                              

                                                                    




                                                                             
                                                          
                                                
                      
                         
                                                                      
 

                                                                    
















                                                                                     
                                                                      
 

                                                                                  




                                                                 
                                                                     

                                                                                     
                    

                                                               
                                                             
                       
                         
 
                                                                      

                                          


















                                                                                     
                      
                                                              
                                                 



                                                                 
                                                                      
                                                                 



                                                                                               



                                                                        

                                                                                                




                                                                     


                                                                  


                                                                








                                                    
 

                                              
 




                                                                     

                                                                      
 
          
                                                        
                                     
























                                                                                     













                                                                                           
    

                                 
 
                                                                      



















                                                                                          























                                                                                        
                                                        






                                                                         
                                              
                                


                                               

                                                                         



                                                           




                                                                                          








                                               





                                                          
 
                                                                      

























































                                                                                           


















































                                                                                           
                                  

                                                         




                                                 

                          



                                                                      

                                                                            




                                                 

                          



                                                                      

                                                                           



                                               
                                           

                                              

                          




                                                                      
                                                                      





                                                                      
                                                                      




                                                                      
                                                                      








                                                                      














































                                                                                                         
                                                




                                                        

                                 






































                                                                                            
 







                                                                     
                                                                                                          




                                                                                     

                                                                                                   
                                            


                                  
                                                                      
 


































                                                                              





                                                                             

                                                                 


                                                                     

                                                                                   
                                                                             

                                                                        
                                                                              
                                                             
      







                                                               
              
                                  



                                                               

















                                                                                                  







                                                                      
                                                                      
                                                                      
  

                                                                    
                               
       
                                   



                     







                                                        

















































                                                       






















                                                                                                       
 



                           
                                                                       
                                                                
                                                                                  












                                                                                   














                                                                            
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%

-module(ssh_basic_SUITE).

-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/file.hrl").

%% Note: This directive should only be used in test suites.
-compile(export_all).

-define(NEWLINE, <<"\r\n">>).

-define(REKEY_DATA_TMO, 65000).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

suite() ->
    [{ct_hooks,[ts_install_cth]}].

all() -> 
    [app_test,
     appup_test,
     {group, dsa_key},
     {group, rsa_key},
     {group, dsa_pass_key},
     {group, rsa_pass_key},
     {group, internal_error},
     {group, renegotiate},
     daemon_already_started,
     server_password_option,
     server_userpassword_option,
     {group, dir_options},
     double_close,
     ssh_connect_timeout,
     ssh_connect_arg4_timeout,
     packet_size_zero,
     ssh_daemon_minimal_remote_max_packet_size_option,
     ssh_msg_debug_fun_option_client,
     ssh_msg_debug_fun_option_server,
     disconnectfun_option_server,
     disconnectfun_option_client,
     preferred_algorithms,
     id_string_no_opt_client,
     id_string_own_string_client,
     id_string_random_client,
     id_string_no_opt_server,
     id_string_own_string_server,
     id_string_random_server,
     {group, hardening_tests}
    ].

groups() ->
    [{dsa_key, [], basic_tests()},
     {rsa_key, [], basic_tests()},
     {dsa_pass_key, [], [pass_phrase]},
     {rsa_pass_key, [], [pass_phrase]},
     {internal_error, [], [internal_error]},
     {renegotiate, [], [rekey, rekey_limit, renegotiate1, renegotiate2]},
     {hardening_tests, [], [ssh_connect_nonegtimeout_connected_parallel,
			    ssh_connect_nonegtimeout_connected_sequential,
			    ssh_connect_negtimeout_parallel,
			    ssh_connect_negtimeout_sequential,
			    max_sessions_ssh_connect_parallel,
			    max_sessions_ssh_connect_sequential,
			    max_sessions_sftp_start_channel_parallel,
			    max_sessions_sftp_start_channel_sequential
			   ]},
     {dir_options, [], [user_dir_option,
			system_dir_option]}
    ].


basic_tests() ->
    [send, close, peername_sockname,
     exec, exec_compressed, shell, cli, known_hosts, 
     idle_time, openssh_zlib_basic_test, misc_ssh_options, inet_option].


%%--------------------------------------------------------------------
init_per_suite(Config) ->
    catch crypto:stop(),
    case catch crypto:start() of
	ok ->
	    Config;
	_Else ->
	    {skip, "Crypto could not be started!"}
    end.
end_per_suite(_Config) ->
    ssh:stop(),
    crypto:stop().
%%--------------------------------------------------------------------
init_per_group(hardening_tests, Config) ->
    init_per_group(dsa_key, Config);
init_per_group(dsa_key, Config) ->
    DataDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:setup_dsa(DataDir, PrivDir),
    Config;
init_per_group(rsa_key, Config) ->
    DataDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:setup_rsa(DataDir, PrivDir),
    Config;
init_per_group(rsa_pass_key, Config) ->
    DataDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:setup_rsa_pass_pharse(DataDir, PrivDir, "Password"),
    [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
init_per_group(dsa_pass_key, Config) ->
    DataDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:setup_dsa_pass_pharse(DataDir, PrivDir, "Password"),
    [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
init_per_group(internal_error, Config) ->
    DataDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:setup_dsa(DataDir, PrivDir),
    file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
    Config;
init_per_group(dir_options, Config) ->
    PrivDir = ?config(priv_dir, Config),
    %% Make unreadable dir:
    Dir_unreadable = filename:join(PrivDir, "unread"),
    ok = file:make_dir(Dir_unreadable),
    {ok,F1} = file:read_file_info(Dir_unreadable),
    ok = file:write_file_info(Dir_unreadable, 
			      F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}),
    %% Make readable file:
    File_readable = filename:join(PrivDir, "file"),
    ok = file:write_file(File_readable, <<>>),
    %% Check:
    case {file:read_file_info(Dir_unreadable), 
	  file:read_file_info(File_readable)} of
	{{ok, #file_info{type=directory, access=Md}},
	 {ok, #file_info{type=regular, access=Mf}}} when Md=/=read, Md=/=read_write ->
	    %% Save:
	    [{unreadable_dir, Dir_unreadable},
	     {readable_file, File_readable} 
	     | Config];
	X ->
	    ct:log("#file_info : ~p",[X]),
	    {skip, "File or dir mode settings failed"}
    end;
init_per_group(_, Config) ->
    Config.

end_per_group(hardening_tests, Config) ->
    end_per_group(dsa_key, Config);
end_per_group(dsa_key, Config) ->
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:clean_dsa(PrivDir),
    Config;
end_per_group(rsa_key, Config) ->
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:clean_rsa(PrivDir),
    Config;
end_per_group(dsa_pass_key, Config) ->
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:clean_dsa(PrivDir),
    Config;
end_per_group(rsa_pass_key, Config) ->
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:clean_rsa(PrivDir),
    Config;
end_per_group(internal_error, Config) ->
    PrivDir = ?config(priv_dir, Config),
    ssh_test_lib:clean_dsa(PrivDir),
    Config;

end_per_group(_, Config) ->
    Config.
%%--------------------------------------------------------------------
init_per_testcase(_TestCase, Config) ->
    ssh:start(),
    Config.

end_per_testcase(TestCase, Config) when TestCase == server_password_option;
					TestCase == server_userpassword_option ->
    UserDir = filename:join(?config(priv_dir, Config), nopubkey),
    ssh_test_lib:del_dirs(UserDir),
    end_per_testcase(Config);
end_per_testcase(_TestCase, Config) ->
    end_per_testcase(Config).
end_per_testcase(_Config) ->    
    ssh:stop(),
    ok.

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
app_test() ->
    [{doc, "App lication consistency test."}].
app_test(Config) when is_list(Config) ->
    ?t:app_test(ssh),
    ok.
%%--------------------------------------------------------------------
appup_test() ->
    [{doc, "Appup file consistency test."}].
appup_test(Config) when is_list(Config) ->
    ok = ?t:appup_test(ssh).
%%--------------------------------------------------------------------
misc_ssh_options() ->
    [{doc, "Test that we can set some misc options not tested elsewhere, "
      "some options not yet present are not decided if we should support or "
      "if they need thier own test case."}].
misc_ssh_options(Config) when is_list(Config) ->  
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    CMiscOpt0 = [{connect_timeout, 1000}, {user_dir, UserDir}],
    CMiscOpt1 = [{connect_timeout, infinity}, {user_dir, UserDir}],
    SMiscOpt0 =  [{user_dir, UserDir}, {system_dir, SystemDir}],
    SMiscOpt1 =  [{user_dir, UserDir}, {system_dir, SystemDir}],

    basic_test([{client_opts, CMiscOpt0}, {server_opts, SMiscOpt0}]),
    basic_test([{client_opts, CMiscOpt1}, {server_opts, SMiscOpt1}]).

%%--------------------------------------------------------------------
inet_option() ->
    [{doc, "Test configuring IPv4"}].
inet_option(Config) when is_list(Config) ->   
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    ClientOpts =  [{silently_accept_hosts, true},
		   {user_dir, UserDir},
		   {user_interaction, false}],
    ServerOpts = [{system_dir, SystemDir},
		  {user_dir, UserDir},
		  {failfun, fun ssh_test_lib:failfun/2}], 

    basic_test([{client_opts, [{inet, inet} | ClientOpts]}, 
		{server_opts, [{inet, inet} | ServerOpts]}]).

%%--------------------------------------------------------------------
inet6_option() ->
    [{doc, "Test configuring IPv6"}].
inet6_option(Config) when is_list(Config) ->   
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    ClientOpts =  [{silently_accept_hosts, true},
		   {user_dir, UserDir},
		   {user_interaction, false}],
    ServerOpts = [{system_dir, SystemDir},
		  {user_dir, UserDir},
		  {failfun, fun ssh_test_lib:failfun/2}], 

    basic_test([{client_opts, [{inet, inet6} | ClientOpts]}, 
		{server_opts, [{inet, inet6} | ServerOpts]}]).

%%--------------------------------------------------------------------
exec() ->
    [{doc, "Test api function ssh_connection:exec"}].
exec(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
    success = ssh_connection:exec(ConnectionRef, ChannelId0,
				  "1+1.", infinity),
    Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"2\n">>}},
    case ssh_test_lib:receive_exec_result(Data0) of
	expected ->
	    ok;
	Other0 ->
	    ct:fail(Other0)
    end,
    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),

    %% Test that it is possible to start a new channel and
    %% run an other exec on the same connection.
    {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
    success = ssh_connection:exec(ConnectionRef, ChannelId1,
				  "2+2.", infinity),
    Data1 = {ssh_cm, ConnectionRef, {data, ChannelId1, 0, <<"4\n">>}},
    case ssh_test_lib:receive_exec_result(Data1) of
	expected ->
	    ok;
	Other1 ->
	    ct:fail(Other1)
    end,
    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
exec_compressed() ->
    [{doc, "Test that compression option works"}].
exec_compressed(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config), 

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
					     {preferred_algorithms,[{compression, [zlib]}]},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
    success = ssh_connection:exec(ConnectionRef, ChannelId,
				  "1+1.", infinity),
    Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}},
    case ssh_test_lib:receive_exec_result(Data) of
	expected ->
	    ok;
	Other ->
	    ct:fail(Other)
    end,
    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
idle_time() ->
    [{doc, "Idle timeout test"}].
idle_time(Config) ->
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {idle_time, 2000}]),
    {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
    ssh_connection:close(ConnectionRef, Id),
    receive
    after 10000 ->
	    {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000)
    end,
    ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
rekey() ->
    [{doc, "Idle timeout test"}].
rekey(Config) ->
    SystemDir = ?config(data_dir, Config),
    UserDir = ?config(priv_dir, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
    					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2},
    					     {user_passwords,
    					      [{"simon", "says"}]},
					     {rekey_limit, 0}]),

    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user, "simon"},
					  {password, "says"},
					  {user_interaction, false},
					  {rekey_limit, 0}]),
    receive
    after ?REKEY_DATA_TMO ->
	    %%By this time rekeying would have been done
	    ssh:close(ConnectionRef),
	    ssh:stop_daemon(Pid)
    end.
%%--------------------------------------------------------------------
rekey_limit() ->
    [{doc, "Test rekeying by data volume"}].
rekey_limit(Config) ->
    SystemDir = ?config(data_dir, Config),
    UserDir = ?config(priv_dir, Config),
    DataFile = filename:join(UserDir, "rekey.data"),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
    					     {user_dir, UserDir},
    					     {user_passwords,
    					      [{"simon", "says"}]}]),
    {ok, SftpPid, ConnectionRef} =
    	ssh_sftp:start_channel(Host, Port, [{system_dir, SystemDir},
    					    {user_dir, UserDir},
    					    {user, "simon"},
    					    {password, "says"},
    					    {rekey_limit, 2500},
    					    {user_interaction, false},
    					    {silently_accept_hosts, true}]),

    Kex1 = get_kex_init(ConnectionRef),

    ct:sleep(?REKEY_DATA_TMO),
    Kex1 = get_kex_init(ConnectionRef),

    Data = lists:duplicate(9000,1),
    ok = ssh_sftp:write_file(SftpPid, DataFile, Data),

    ct:sleep(?REKEY_DATA_TMO),
    Kex2 = get_kex_init(ConnectionRef),

    false = (Kex2 == Kex1),

    ct:sleep(?REKEY_DATA_TMO),
    Kex2 = get_kex_init(ConnectionRef),

    ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),

    ct:sleep(?REKEY_DATA_TMO),
    Kex2 = get_kex_init(ConnectionRef),

    false = (Kex2 == Kex1),

    ct:sleep(?REKEY_DATA_TMO),
    Kex2 = get_kex_init(ConnectionRef),


    ssh_sftp:stop_channel(SftpPid),
    ssh:close(ConnectionRef),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
renegotiate1() ->
    [{doc, "Test rekeying with simulataneous send request"}].
renegotiate1(Config) ->
    SystemDir = ?config(data_dir, Config),
    UserDir = ?config(priv_dir, Config),
    DataFile = filename:join(UserDir, "renegotiate1.data"),

    {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir},
					       {user_dir, UserDir},
					       {user_passwords,
						[{"simon", "says"}]}]),
    RPort = ssh_test_lib:inet_port(),

    {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),

    {ok, SftpPid, ConnectionRef} =
	ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {user, "simon"},
					     {password, "says"},
					     {user_interaction, false},
					     {silently_accept_hosts, true}]),

    Kex1 = get_kex_init(ConnectionRef),

    {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),

    ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),

    ssh_relay:hold(RelayPid, rx, 20, 1000),
    ssh_connection_handler:renegotiate(ConnectionRef),
    spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),

    ct:sleep(2000),

    Kex2 = get_kex_init(ConnectionRef),

    false = (Kex2 == Kex1),
    
    ssh_relay:stop(RelayPid),
    ssh_sftp:stop_channel(SftpPid),
    ssh:close(ConnectionRef),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
renegotiate2() ->
    [{doc, "Test rekeying with inflight messages from peer"}].
renegotiate2(Config) ->
    SystemDir = ?config(data_dir, Config),
    UserDir = ?config(priv_dir, Config),
    DataFile = filename:join(UserDir, "renegotiate1.data"),

    {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir},
					       {user_dir, UserDir},
					       {user_passwords,
						[{"simon", "says"}]}]),
    RPort = ssh_test_lib:inet_port(),

    {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),

    {ok, SftpPid, ConnectionRef} =
	ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {user, "simon"},
					     {password, "says"},
					     {user_interaction, false},
					     {silently_accept_hosts, true}]),

    Kex1 = get_kex_init(ConnectionRef),

    {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),

    ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),

    ssh_relay:hold(RelayPid, rx, 20, infinity),
    spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
    %% need a small pause here to ensure ssh_sftp:write is executed
    ct:sleep(10),
    ssh_connection_handler:renegotiate(ConnectionRef),
    ssh_relay:release(RelayPid, rx),

    ct:sleep(2000),

    Kex2 = get_kex_init(ConnectionRef),

    false = (Kex2 == Kex1),

    ssh_relay:stop(RelayPid),
    ssh_sftp:stop_channel(SftpPid),
    ssh:close(ConnectionRef),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
shell() ->
    [{doc, "Test that ssh:shell/2 works"}].
shell(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
   
    {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
					       {failfun, fun ssh_test_lib:failfun/2}]),
    ct:sleep(500),

    IO = ssh_test_lib:start_io_server(),
    Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
    receive
	{'EXIT', _, _} ->
	    ct:fail(no_ssh_connection);  
	ErlShellStart ->
	    ct:pal("Erlang shell start: ~p~n", [ErlShellStart]),
	    do_shell(IO, Shell)
    end.
    
%%--------------------------------------------------------------------
cli() ->
    [{doc, ""}].
cli(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
   
    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
					       {password, "morot"},
					       {ssh_cli, {ssh_test_cli, [cli]}}, 
					       {subsystems, []},
					       {failfun, fun ssh_test_lib:failfun/2}]),
    ct:sleep(500),
    
    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
						      {user, "foo"},
						      {password, "morot"},
						      {user_interaction, false},
						      {user_dir, UserDir}]),
    
    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
    ssh_connection:shell(ConnectionRef, ChannelId),
    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"q">>),
    receive 
	{ssh_cm, ConnectionRef,
	 {data,0,0, <<"\r\nYou are accessing a dummy, type \"q\" to exit\r\n\n">>}} ->
	    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"q">>)
    end,
    
    receive 
     	{ssh_cm, ConnectionRef,{closed, ChannelId}} ->
     	    ok
    end.

%%--------------------------------------------------------------------
daemon_already_started() ->
    [{doc, "Test that get correct error message if you try to start a daemon",
      "on an adress that already runs a daemon see also seq10667"}].
daemon_already_started(Config) when is_list(Config) ->
    SystemDir = ?config(data_dir, Config),
    UserDir = ?config(priv_dir, Config),

    {Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					      {user_dir, UserDir},
					      {failfun, fun ssh_test_lib:failfun/2}]),
    {error, eaddrinuse} = ssh_test_lib:daemon(Port, [{system_dir, SystemDir},
						     {user_dir, UserDir},
						     {failfun,
						      fun ssh_test_lib:failfun/2}]),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
server_password_option() ->
    [{doc, "validate to server that uses the 'password' option"}].
server_password_option(Config) when is_list(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),
    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, UserDir},
					     {password, "morot"}]),

    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "foo"},
					  {password, "morot"},
					  {user_interaction, false},
					  {user_dir, UserDir}]),

    Reason = "Unable to connect using the available authentication methods",
    
    {error, Reason} =
	ssh:connect(Host, Port, [{silently_accept_hosts, true},
				 {user, "vego"},
				 {password, "foo"},
				 {user_interaction, false},
				 {user_dir, UserDir}]),
    
    ct:pal("Test of wrong password: Error msg: ~p ~n", [Reason]),

    ssh:close(ConnectionRef),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------

server_userpassword_option() ->
    [{doc, "validate to server that uses the 'password' option"}].
server_userpassword_option(Config) when is_list(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),	  
    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, PrivDir},
					     {user_passwords, [{"vego", "morot"}]}]),

    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "vego"},
					  {password, "morot"},
					  {user_interaction, false},
					  {user_dir, UserDir}]),
    ssh:close(ConnectionRef),

    Reason = "Unable to connect using the available authentication methods",

    {error, Reason} =
	ssh:connect(Host, Port, [{silently_accept_hosts, true},
				 {user, "foo"},
				 {password, "morot"},
				 {user_interaction, false},
				 {user_dir, UserDir}]),
    {error, Reason} =
	ssh:connect(Host, Port, [{silently_accept_hosts, true},
				 {user, "vego"},
				 {password, "foo"},
				 {user_interaction, false},
				 {user_dir, UserDir}]),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
system_dir_option(Config) ->
    DirUnread = proplists:get_value(unreadable_dir,Config),
    FileRead = proplists:get_value(readable_file,Config),

    case ssh_test_lib:daemon([{system_dir, DirUnread}]) of
	{error,{eoptions,{{system_dir,DirUnread},eacces}}} ->
	    ok;
	{Pid1,_Host1,Port1} when is_pid(Pid1),is_integer(Port1) ->
	    ssh:stop_daemon(Pid1),
	    ct:fail("Didn't detect that dir is unreadable", [])
	end,
    
    case ssh_test_lib:daemon([{system_dir, FileRead}]) of
	{error,{eoptions,{{system_dir,FileRead},enotdir}}} ->
	    ok;
	{Pid2,_Host2,Port2} when is_pid(Pid2),is_integer(Port2) ->
	    ssh:stop_daemon(Pid2),
	    ct:fail("Didn't detect that option is a plain file", [])
    end.


user_dir_option(Config) ->
    DirUnread = proplists:get_value(unreadable_dir,Config),
    FileRead = proplists:get_value(readable_file,Config),
    %% Any port will do (beware, implementation knowledge!):
    Port = 65535,

    case ssh:connect("localhost", Port, [{user_dir, DirUnread}]) of
	{error,{eoptions,{{user_dir,DirUnread},eacces}}} ->
	    ok;
	{error,econnrefused} ->
	    ct:fail("Didn't detect that dir is unreadable", [])
    end,

    case ssh:connect("localhost", Port, [{user_dir, FileRead}]) of
	{error,{eoptions,{{user_dir,FileRead},enotdir}}} ->
	    ok;
	{error,econnrefused} ->
	    ct:fail("Didn't detect that option is a plain file", [])
    end.

%%--------------------------------------------------------------------
ssh_msg_debug_fun_option_client() ->
    [{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}].
ssh_msg_debug_fun_option_client(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, UserDir},
					     {password, "morot"},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    Parent = self(),
    DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end,
		
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "foo"},
					  {password, "morot"},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {ssh_msg_debug_fun,DbgFun}]),
    %% Beware, implementation knowledge:
    gen_fsm:send_all_state_event(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}),
    receive
	{msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} ->
	    ct:log("Got expected dbg msg ~p",[X]),
	    ssh:stop_daemon(Pid);
	{msg_dbg,X={_,false,<<"Hello">>,<<>>}} ->
	    ct:log("Got dbg msg but bad ConnectionRef (~p expected) ~p",[ConnectionRef,X]),
	    ssh:stop_daemon(Pid),
	    {fail, "Bad ConnectionRef received"};
	{msg_dbg,X} ->
	    ct:log("Got bad dbg msg ~p",[X]),
	    ssh:stop_daemon(Pid),
	    {fail,"Bad msg received"}
    after 1000 ->
	    ssh:stop_daemon(Pid),
	    {fail,timeout}
    end.

%%--------------------------------------------------------------------
ssh_msg_debug_fun_option_server() ->
    [{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}].
ssh_msg_debug_fun_option_server(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),

    Parent = self(),
    DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end,
    ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end,

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, UserDir},
					     {password, "morot"},
					     {failfun, fun ssh_test_lib:failfun/2},
					     {connectfun, ConnFun},
					     {ssh_msg_debug_fun, DbgFun}]),
    _ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "foo"},
					  {password, "morot"},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    receive
	{connection_pid,Server} ->
	    %% Beware, implementation knowledge:
	    gen_fsm:send_all_state_event(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}),
	    receive
		{msg_dbg,X={_,false,<<"Hello">>,<<>>}} ->
		    ct:log("Got expected dbg msg ~p",[X]),
		    ssh:stop_daemon(Pid);
		{msg_dbg,X} ->
		    ct:log("Got bad dbg msg ~p",[X]),
		    ssh:stop_daemon(Pid),
		    {fail,"Bad msg received"}
	    after 3000 ->
		    ssh:stop_daemon(Pid),
		    {fail,timeout2}
	    end
    after 3000 ->
	    ssh:stop_daemon(Pid),
	    {fail,timeout1}
    end.

%%--------------------------------------------------------------------
disconnectfun_option_server(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),

    Parent = self(),
    DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end,

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, UserDir},
					     {password, "morot"},
					     {failfun, fun ssh_test_lib:failfun/2},
					     {disconnectfun, DisConnFun}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "foo"},
					  {password, "morot"},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    ssh:close(ConnectionRef),
    receive
	{disconnect,Reason} ->
	    ct:log("Server detected disconnect: ~p",[Reason]),
	    ssh:stop_daemon(Pid),
	    ok
    after 3000 ->
	    receive
		X -> ct:log("received ~p",[X])
	    after 0 -> ok
	    end,
	    {fail,"Timeout waiting for disconnect"}
    end.

%%--------------------------------------------------------------------
disconnectfun_option_client(Config) ->
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = ?config(data_dir, Config),

    Parent = self(),
    DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end,

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
					     {user_dir, UserDir},
					     {password, "morot"},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    _ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user, "foo"},
					  {password, "morot"},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {disconnectfun, DisConnFun}]),
    ssh:stop_daemon(Pid),
    receive
	{disconnect,Reason} ->
	    ct:log("Client detected disconnect: ~p",[Reason]),
	    ok
    after 3000 ->
	    receive
		X -> ct:log("received ~p",[X])
	    after 0 -> ok
	    end,
	    {fail,"Timeout waiting for disconnect"}
    end.

%%--------------------------------------------------------------------
known_hosts() ->
    [{doc, "check that known_hosts is updated correctly"}].
known_hosts(Config) when is_list(Config) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config), 
    
    {Pid, Host, Port} = ssh_test_lib:daemon([{user_dir, PrivDir},{system_dir, SystemDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),

    KnownHosts = filename:join(PrivDir, "known_hosts"),
    file:delete(KnownHosts),
    {error, enoent} = file:read_file(KnownHosts),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
					  {user_interaction, false},
					  silently_accept_hosts]),
    {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
    ok = ssh:close(ConnectionRef),
    {ok, Binary} = file:read_file(KnownHosts),
    Lines = string:tokens(binary_to_list(Binary), "\n"),
    [Line] = Lines,
    [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
    [Host, _Ip] = string:tokens(HostAndIp, ","),
    "ssh-" ++ _ = Alg,
    ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------

pass_phrase() ->
    [{doc, "Test that we can use keyes protected by pass phrases"}].
pass_phrase(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    PhraseArg = ?config(pass_phrase, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  PhraseArg,
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------

internal_error() ->
    [{doc,"Test that client does not hang if disconnects due to internal error"}].
internal_error(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    {error, Error} =
	ssh:connect(Host, Port, [{silently_accept_hosts, true},
				 {user_dir, UserDir},
				 {user_interaction, false}]),
    check_error(Error),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------
send() ->
    [{doc, "Test ssh_connection:send/3"}].
send(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
    ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
    ssh:stop_daemon(Pid).


%%--------------------------------------------------------------------
peername_sockname() ->
    [{doc, "Test ssh:connection_info([peername, sockname])"}].
peername_sockname(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),

    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {subsystems, [{"peername_sockname",
							    {ssh_peername_sockname_server, []}}
							  ]}
					    ]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
    success = ssh_connection:subsystem(ConnectionRef, ChannelId, "peername_sockname", infinity),
    [{peer, {_Name, {HostPeerClient,PortPeerClient} = ClientPeer}}] =
	ssh:connection_info(ConnectionRef, [peer]),
    [{sockname, {HostSockClient,PortSockClient} = ClientSock}] =
	ssh:connection_info(ConnectionRef, [sockname]),
    ct:pal("Client: ~p ~p", [ClientPeer, ClientSock]),
    receive
	{ssh_cm, ConnectionRef, {data, ChannelId, _, Response}} ->
	    {PeerNameSrv,SockNameSrv} = binary_to_term(Response),
	    {HostPeerSrv,PortPeerSrv} = PeerNameSrv,
	    {HostSockSrv,PortSockSrv} = SockNameSrv,
	    ct:pal("Server: ~p ~p", [PeerNameSrv, SockNameSrv]),
	    host_equal(HostPeerSrv, HostSockClient),
	    PortPeerSrv = PortSockClient,
	    host_equal(HostSockSrv, HostPeerClient),
	    PortSockSrv = PortPeerClient,
	    host_equal(HostSockSrv, Host),
	    PortSockSrv = Port
    after 10000 ->
	    throw(timeout)
    end.

host_equal(H1, H2) ->
    not ordsets:is_disjoint(ips(H1), ips(H2)).

ips(IP) when is_tuple(IP) -> ordsets:from_list([IP]);
ips(Name) when is_list(Name) ->
    {ok,#hostent{h_addr_list=IPs4}} = inet:gethostbyname(Name,inet),
    {ok,#hostent{h_addr_list=IPs6}} = inet:gethostbyname(Name,inet6),
    ordsets:from_list(IPs4++IPs6).

%%--------------------------------------------------------------------

close() ->
    [{doc, "Client receives close when server closes"}].
close(Config) when is_list(Config) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    
    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    Client =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false}]),
    {ok, ChannelId} = ssh_connection:session_channel(Client, infinity),
    
    ssh:stop_daemon(Server),
    receive 
	{ssh_cm, Client,{closed, ChannelId}} ->  
	    ok
    after 5000 ->
	    ct:fail(timeout)
    end.

%%--------------------------------------------------------------------
double_close() ->
    [{doc, "Simulate that we try to close an already closed connection"}].
double_close(Config) when is_list(Config) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config), 
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),

    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {user_passwords, [{"vego", "morot"}]},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    {ok, CM} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
					   {user_dir, UserDir},
					    {user, "vego"},
					    {password, "morot"},
					    {user_interaction, false}]),
    
    exit(CM, {shutdown, normal}),
    ok = ssh:close(CM).

%%--------------------------------------------------------------------
ssh_connect_timeout() ->
    [{doc, "Test connect_timeout option in ssh:connect/4"}].
ssh_connect_timeout(_Config) ->
    ConnTimeout = 2000,
    {error,{faked_transport,connect,TimeoutToTransport}} = 
	ssh:connect("localhost", 12345, 
		    [{transport,{tcp,?MODULE,tcp_closed}},
		     {connect_timeout,ConnTimeout}],
		    1000),
    case TimeoutToTransport of
	ConnTimeout -> ok;
	Other -> 
	    ct:log("connect_timeout is ~p but transport received ~p",[ConnTimeout,Other]),
	    {fail,"ssh:connect/4 wrong connect_timeout received in transport"}
    end.
    
%% Help for the test above
connect(_Host, _Port, _Opts, Timeout) ->
    {error, {faked_transport,connect,Timeout}}.


%%--------------------------------------------------------------------
ssh_connect_arg4_timeout() ->
    [{doc, "Test fourth argument in ssh:connect/4"}].
ssh_connect_arg4_timeout(_Config) ->
    Timeout = 1000,
    Parent = self(),
    %% start the server
    Server = spawn(fun() ->
			   {ok,Sl} = gen_tcp:listen(0,[]),
			   {ok,{_,Port}} = inet:sockname(Sl),
			   Parent ! {port,self(),Port},
			   Rsa = gen_tcp:accept(Sl),
			   ct:log("Server gen_tcp:accept got ~p",[Rsa]),
			   receive after 2*Timeout -> ok end %% let client timeout first
		   end),

    %% Get listening port
    Port = receive
	       {port,Server,ServerPort} -> ServerPort
	   end,

    %% try to connect with a timeout, but "supervise" it
    Client = spawn(fun() ->
			   T0 = erlang:monotonic_time(),
			   Rc = ssh:connect("localhost",Port,[],Timeout),
			   ct:log("Client ssh:connect got ~p",[Rc]),
			   Parent ! {done,self(),Rc,T0}
		   end),

    %% Wait for client reaction on the connection try:
    receive
	{done, Client, {error,timeout}, T0} ->
	    Msp = ms_passed(T0),
	    exit(Server,hasta_la_vista___baby),
	    Low = 0.9*Timeout,
	    High =  1.1*Timeout,
	    ct:log("Timeout limits: ~.4f - ~.4f ms, timeout "
                   "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]),
	    if
		Low<Msp, Msp<High -> ok;
		true -> {fail, "timeout not within limits"}
	    end;

	{done, Client, {error,Other}, _T0} ->
	    ct:log("Error message \"~p\" from the client is unexpected.",[{error,Other}]),
	    {fail, "Unexpected error message"};

	{done, Client, {ok,_Ref}, _T0} ->
	    {fail,"ssh-connected ???"}
    after
	5000 ->
	    exit(Server,hasta_la_vista___baby),
	    exit(Client,hasta_la_vista___baby),
	    {fail, "Didn't timeout"}
    end.

%% Help function, elapsed milliseconds since T0
ms_passed(T0) ->
    %% OTP 18
    erlang:convert_time_unit(erlang:monotonic_time() - T0,
			     native,
			     micro_seconds) / 1000.

%%--------------------------------------------------------------------
packet_size_zero(Config) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config), 
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),

    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
						{user_dir, UserDir},
						{user_passwords, [{"vego", "morot"}]}]),
    Conn =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {user, "vego"},
					  {password, "morot"}]),

    {ok,Chan} = ssh_connection:session_channel(Conn, 1000, _MaxPacketSize=0, 60000),
    ok = ssh_connection:shell(Conn, Chan),

    ssh:close(Conn),
    ssh:stop_daemon(Server),

    receive
	{ssh_cm,Conn,{data,Chan,_Type,_Msg1}} = M ->
	    ct:pal("Got ~p",[M]),
	    ct:fail(doesnt_obey_max_packet_size_0)
    after 5000 ->
	    ok
    end.    
    
%%--------------------------------------------------------------------
ssh_daemon_minimal_remote_max_packet_size_option(Config) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config), 
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    
    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
						{user_dir, UserDir},
						{user_passwords, [{"vego", "morot"}]},
						{failfun, fun ssh_test_lib:failfun/2},
						{minimal_remote_max_packet_size, 14}]),
    Conn =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {user, "vego"},
					  {password, "morot"}]),

    %% Try the limits of the minimal_remote_max_packet_size:
    {ok, _ChannelId} = ssh_connection:session_channel(Conn, 100, 14, infinity),
    {open_error,_,"Maximum packet size below 14 not supported",_} = 
	ssh_connection:session_channel(Conn, 100, 13, infinity),

    ssh:close(Conn),
    ssh:stop_daemon(Server).
    
%%--------------------------------------------------------------------
%% This test try every algorithm by connecting to an Erlang server
preferred_algorithms(Config) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),

    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
						{user_dir, UserDir},
						{user_passwords, [{"vego", "morot"}]},
						{failfun, fun ssh_test_lib:failfun/2}]),
    Available = ssh:default_algorithms(),
    Tests = [[{Tag,[Alg]}] || {Tag, SubAlgs} <- Available,
			  is_atom(hd(SubAlgs)),
			  Alg <- SubAlgs]
	++  [[{Tag,[{T1,[A1]},{T2,[A2]}]}] || {Tag, [{T1,As1},{T2,As2}]} <- Available,
					      A1 <- As1,
					      A2 <- As2],
    ct:log("TESTS: ~p",[Tests]),
    [connect_exec_channel(Host,Port,PrefAlgs)  || PrefAlgs <- Tests],
    ssh:stop_daemon(Server).


connect_exec_channel(_Host, Port, Algs) ->
    ct:log("Try ~p",[Algs]),
    ConnectionRef = ssh_test_lib:connect(Port, [{silently_accept_hosts, true},
						{user_interaction, false},
						{user, "vego"},
						{password, "morot"},
						{preferred_algorithms,Algs}
					       ]),
    chan_exec(ConnectionRef, "2*21.", <<"42\n">>),
    ssh:close(ConnectionRef).

chan_exec(ConnectionRef, Cmnd, Expected) ->
    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
    success = ssh_connection:exec(ConnectionRef, ChannelId0,Cmnd, infinity),
    Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Expected}},
    case ssh_test_lib:receive_exec_result(Data0) of
	expected ->
	    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0);
	{unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}}
	 = ExitStatus0} ->
	    ct:pal("0: Collected data ~p", [ExitStatus0]),
	    ssh_test_lib:receive_exec_result(Data0,
					     ConnectionRef, ChannelId0);
	Other0 ->
	    ct:fail(Other0)
    end.

%%--------------------------------------------------------------------
id_string_no_opt_client(Config) ->
    {Server, _Host, Port} = fake_daemon(Config),
    {error,_} = ssh:connect("localhost", Port, [], 1000),
    receive
	{id,Server,"SSH-2.0-Erlang/"++Vsn} ->
	    true = expected_ssh_vsn(Vsn);
	{id,Server,Other} ->
	    ct:fail("Unexpected id: ~s.",[Other])
    after 5000 ->
	    {fail,timeout}
    end.

%%--------------------------------------------------------------------
id_string_own_string_client(Config) ->
    {Server, _Host, Port} = fake_daemon(Config),
    {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}], 1000),
    receive
	{id,Server,"SSH-2.0-Pelle\r\n"} ->
	    ok;
	{id,Server,Other} ->
	    ct:fail("Unexpected id: ~s.",[Other])
    after 5000 ->
	    {fail,timeout}
    end.

%%--------------------------------------------------------------------
id_string_random_client(Config) ->
    {Server, _Host, Port} = fake_daemon(Config),
    {error,_} = ssh:connect("localhost", Port, [{id_string,random}], 1000),
    receive
	{id,Server,Id="SSH-2.0-Erlang"++_} ->
	    ct:fail("Unexpected id: ~s.",[Id]);
	{id,Server,Rnd="SSH-2.0-"++_} ->
	    ct:log("Got correct ~s",[Rnd]);
	{id,Server,Id} ->
	    ct:fail("Unexpected id: ~s.",[Id])
    after 5000 ->
	    {fail,timeout}
    end.

%%--------------------------------------------------------------------
id_string_no_opt_server(Config) ->
    {_Server, Host, Port} = std_daemon(Config, []),
    {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
    {ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000),
    true = expected_ssh_vsn(Vsn).

%%--------------------------------------------------------------------
id_string_own_string_server(Config) ->
    {_Server, Host, Port} = std_daemon(Config, [{id_string,"Olle"}]),
    {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
    {ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000).

%%--------------------------------------------------------------------
id_string_random_server(Config) ->
    {_Server, Host, Port} = std_daemon(Config, [{id_string,random}]),
    {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
    {ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000),
    case Rnd of
	"Erlang"++_ -> ct:log("Id=~p",[Rnd]),
		       {fail,got_default_id};
	"Olle\r\n" -> {fail,got_previous_tests_value};
	_ -> ct:log("Got ~s.",[Rnd])
    end.

%%--------------------------------------------------------------------
ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true).
ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false).
    
ssh_connect_negtimeout(Config, Parallel) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    NegTimeOut = 2000,				% ms
    ct:log("Parallel: ~p",[Parallel]),

    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
					      {parallel_login, Parallel},
					      {negotiation_timeout, NegTimeOut},
					      {failfun, fun ssh_test_lib:failfun/2}]),
    
    {ok,Socket} = gen_tcp:connect(Host, Port, []),
    ct:pal("And now sleeping 1.2*NegTimeOut (~p ms)...", [round(1.2 * NegTimeOut)]),
    receive after round(1.2 * NegTimeOut) -> ok end,
    
    case inet:sockname(Socket) of
	{ok,_} -> ct:fail("Socket not closed");
	{error,_} -> ok
    end.

%%--------------------------------------------------------------------
ssh_connect_nonegtimeout_connected_parallel() ->
    [{doc, "Test that ssh connection does not timeout if the connection is established (parallel)"}].
ssh_connect_nonegtimeout_connected_parallel(Config) ->
    ssh_connect_nonegtimeout_connected(Config, true).

ssh_connect_nonegtimeout_connected_sequential() ->
    [{doc, "Test that ssh connection does not timeout if the connection is established (non-parallel)"}].
ssh_connect_nonegtimeout_connected_sequential(Config) ->
    ssh_connect_nonegtimeout_connected(Config, false).


ssh_connect_nonegtimeout_connected(Config, Parallel) ->
    process_flag(trap_exit, true),
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    NegTimeOut = 20000,				% ms
    ct:log("Parallel: ~p",[Parallel]),
   
    {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
					       {parallel_login, Parallel},
					       {negotiation_timeout, NegTimeOut},
					       {failfun, fun ssh_test_lib:failfun/2}]),
    ct:pal("~p Listen ~p:~p",[_Pid,_Host,Port]),
    ct:sleep(500),

    IO = ssh_test_lib:start_io_server(),
    Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
    receive
	Error = {'EXIT', _, _} ->
	    ct:pal("~p",[Error]),
	    ct:fail(no_ssh_connection);  
	ErlShellStart ->
	    ct:pal("---Erlang shell start: ~p~n", [ErlShellStart]),
	    one_shell_op(IO, NegTimeOut),
	    one_shell_op(IO, NegTimeOut),
	    ct:pal("And now sleeping 1.2*NegTimeOut (~p ms)...", [round(1.2 * NegTimeOut)]),
	    receive after round(1.2 * NegTimeOut) -> ok end,
	    one_shell_op(IO, NegTimeOut)
    end,
    exit(Shell, kill).


one_shell_op(IO, TimeOut) ->
    ct:pal("One shell op: Waiting for prompter"),
    receive
	ErlPrompt0 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt0])
	after TimeOut -> ct:fail("Timeout waiting for promter")
    end,

    IO ! {input, self(), "2*3*7.\r\n"},
    receive
	Echo0 -> ct:log("Echo: ~p ~n", [Echo0])
	after TimeOut -> ct:fail("Timeout waiting for echo")
    end,

    receive
	?NEWLINE -> ct:log("NEWLINE received", [])
    after TimeOut -> 
	    receive Any1 -> ct:log("Bad NEWLINE: ~p",[Any1])
	    after 0 -> ct:fail("Timeout waiting for NEWLINE")
	    end
    end,

    receive
	Result0 -> ct:log("Result: ~p~n", [Result0])
	after TimeOut ->  ct:fail("Timeout waiting for result")
    end.

%%--------------------------------------------------------------------

openssh_zlib_basic_test() ->
    [{doc, "Test basic connection with openssh_zlib"}].
openssh_zlib_basic_test(Config) ->
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),

    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					     {user_dir, UserDir},
					     {preferred_algorithms,[{compression, ['[email protected]']}]},
					     {failfun, fun ssh_test_lib:failfun/2}]),
    ConnectionRef =
	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_dir, UserDir},
					  {user_interaction, false},
					  {preferred_algorithms,[{compression, ['[email protected]',
										none]}]}
					 ]),
    ok = ssh:close(ConnectionRef),
    ssh:stop_daemon(Pid).

%%--------------------------------------------------------------------

max_sessions_ssh_connect_parallel(Config) -> 
    max_sessions(Config, true, connect_fun(ssh__connect,Config)).
max_sessions_ssh_connect_sequential(Config) -> 
    max_sessions(Config, false, connect_fun(ssh__connect,Config)).

max_sessions_sftp_start_channel_parallel(Config) -> 
    max_sessions(Config, true, connect_fun(ssh_sftp__start_channel, Config)).
max_sessions_sftp_start_channel_sequential(Config) -> 
    max_sessions(Config, false, connect_fun(ssh_sftp__start_channel, Config)).


%%%---- helpers:
connect_fun(ssh__connect, Config) ->
    fun(Host,Port) ->
	    ssh_test_lib:connect(Host, Port, 
				 [{silently_accept_hosts, true},
				  {user_dir, ?config(priv_dir,Config)},
				  {user_interaction, false},
				  {user, "carni"},
				  {password, "meat"}
				 ])
	    %% ssh_test_lib returns R when ssh:connect returns {ok,R}
    end;
connect_fun(ssh_sftp__start_channel, _Config) ->
    fun(Host,Port) ->
	    {ok,_Pid,ConnRef} =
		ssh_sftp:start_channel(Host, Port, 
				       [{silently_accept_hosts, true},
					{user, "carni"},
					{password, "meat"}
				       ]),
	    ConnRef
    end.


max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) ->
    Connect = fun(Host,Port) ->
		      R = Connect0(Host,Port),
		      ct:pal("Connect(~p,~p) -> ~p",[Host,Port,R]),
		      R
	      end,
    SystemDir = filename:join(?config(priv_dir, Config), system),
    UserDir = ?config(priv_dir, Config),
    MaxSessions = 5,
    {Pid, Host, Port} = ssh_test_lib:daemon([
					     {system_dir, SystemDir},
					     {user_dir, UserDir},
					     {user_passwords, [{"carni", "meat"}]},
					     {parallel_login, ParallelLogin},
					     {max_sessions, MaxSessions}
					    ]),
    ct:pal("~p Listen ~p:~p for max ~p sessions",[Pid,Host,Port,MaxSessions]),
    try [Connect(Host,Port) || _ <- lists:seq(1,MaxSessions)]
    of
	Connections ->
	    %% Step 1 ok: could set up max_sessions connections
	    ct:log("Connections up: ~p",[Connections]),
	    [_|_] = Connections,

	    %% Now try one more than alowed:
	    ct:pal("Info Report might come here...",[]),
	    try Connect(Host,Port)
	    of
		_ConnectionRef1 ->
		    ssh:stop_daemon(Pid),
		    {fail,"Too many connections accepted"}
	    catch
		error:{badmatch,{error,"Connection closed"}} ->
		    %% Step 2 ok: could not set up max_sessions+1 connections
		    %% This is expected
		    %% Now stop one connection and try to open one more
		    ok = ssh:close(hd(Connections)),
		    try Connect(Host,Port)
		    of
			_ConnectionRef1 ->
			    %% Step 3 ok: could set up one more connection after killing one
			    %% Thats good.
			    ssh:stop_daemon(Pid),
			    ok
		    catch
			error:{badmatch,{error,"Connection closed"}} ->
			    %% Bad indeed. Could not set up one more connection even after killing
			    %% one existing. Very bad.
			    ssh:stop_daemon(Pid),
			    {fail,"Does not decrease # active sessions"}
		    end
	    end
    catch
	error:{badmatch,{error,"Connection closed"}} ->
	    ssh:stop_daemon(Pid),
	    {fail,"Too few connections accepted"}
    end.

%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
  
%% Due to timing the error message may or may not be delivered to
%% the "tcp-application" before the socket closed message is recived
check_error("Invalid state") ->
    ok;
check_error("Connection closed") ->
    ok;
check_error(Error) ->
    ct:fail(Error).

basic_test(Config) ->
    ClientOpts = ?config(client_opts, Config),
    ServerOpts = ?config(server_opts, Config),
    
    {Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts),
    {ok, CM} = ssh:connect(Host, Port, ClientOpts),
    ok = ssh:close(CM),
    ssh:stop_daemon(Pid).

do_shell(IO, Shell) ->
    receive
	ErlPrompt0 ->
	    ct:pal("Erlang prompt: ~p~n", [ErlPrompt0])
    end,
    IO ! {input, self(), "1+1.\r\n"},
     receive
	Echo0 ->
	     ct:pal("Echo: ~p ~n", [Echo0])
    end,
    receive
	?NEWLINE ->
	    ok
    end,
    receive
	Result0 = <<"2">> ->
	    ct:pal("Result: ~p~n", [Result0])
    end,
    receive
	?NEWLINE ->
	    ok
    end,
    receive
	ErlPrompt1 ->
	    ct:pal("Erlang prompt: ~p~n", [ErlPrompt1])
    end,
    exit(Shell, kill).
    %%Does not seem to work in the testserver!
    %% 	IO ! {input, self(), "q().\r\n"},
    %% receive
    %%  	?NEWLINE ->
    %%  	    ok
    %% end,
    %% receive
    %%  	Echo1 ->
    %% 	    ct:pal("Echo: ~p ~n", [Echo1])
    %% end,
    %% receive
    %% 	?NEWLINE ->
    %%  	    ok
    %% end,
    %% receive
    %%  	Result1 ->
    %%  	    ct:pal("Result: ~p~n", [Result1])
    %%      end,
    %% receive
    %% 	{'EXIT', Shell, killed} ->
    %% 	    ok
    %% end.


std_daemon(Config, ExtraOpts) ->
    SystemDir = ?config(data_dir, Config),
    PrivDir = ?config(priv_dir, Config), 
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    {_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
						   {user_dir, UserDir},
						   {failfun, fun ssh_test_lib:failfun/2} | ExtraOpts]).

expected_ssh_vsn(Str) ->
    try
	{ok,L} = application:get_all_key(ssh),
	proplists:get_value(vsn,L,"")++"\r\n"
    of
	Str -> true;
	"\r\n" -> true;
	_ -> false
    catch
	_:_ -> true %% ssh not started so we dont't know
    end.
	    

fake_daemon(_Config) ->
    Parent = self(),
    %% start the server
    Server = spawn(fun() ->
			   {ok,Sl} = gen_tcp:listen(0,[{packet,line}]),
			   {ok,{Host,Port}} = inet:sockname(Sl),
			   ct:log("fake_daemon listening on ~p:~p~n",[Host,Port]),
			   Parent ! {sockname,self(),Host,Port},
			   Rsa = gen_tcp:accept(Sl),
			   ct:log("Server gen_tcp:accept got ~p",[Rsa]),
			   {ok,S} = Rsa,
			   receive
			       {tcp, S, Id} -> Parent ! {id,self(),Id}
			   end
		   end),
    %% Get listening host and port
    receive
	{sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort}
    end.

%% get_kex_init - helper function to get key_exchange_init_msg
get_kex_init(Conn) ->
    %% First, validate the key exchange is complete (StateName == connected)
    {connected,S} = sys:get_state(Conn),
    %% Next, walk through the elements of the #state record looking
    %% for the #ssh_msg_kexinit record. This method is robust against
    %% changes to either record. The KEXINIT message contains a cookie
    %% unique to each invocation of the key exchange procedure (RFC4253)
    SL = tuple_to_list(S),
    case lists:keyfind(ssh_msg_kexinit, 1, SL) of
	false ->
	    throw(not_found);
	KexInit ->
	    KexInit
    end.