aboutsummaryrefslogblamecommitdiffstats
path: root/lib/ssh/test/ssh_sup_SUITE.erl
blob: b145066c36d1b859c0020bb77d546c602bb172e3 (plain) (tree)
1
2
3
4
5


                   
                                                        
  










                                                                           







                                           
                             



                                                           


                          

                                



                                                                      

                                 
                               
 
         
                                                                    


                              










                                     







                                                                                                  





                                            
                                                      









                                                                                    
                                                     

















                                                                           

                                                         
 
                                                                           


                                                     

                                                             
 

                                                         


                                                                                                   

                                                         

                                                     


                                                                                                    

                                                        

                                                     
                    
                                                        
                                                     
                    
                                                         
 
                                                                           


                                                     


                                                      



                                                                                  
 

                                                                            


                                                    

                                                   


                                           
                                                         
 
                                                                           


                                                                                



                                                      
 






                                                                                            


                                                    

                                                   


                                           
                                                         
 
                                                                           













                                                                           

                                                 

                           












                                                                            

                                                    

                             
                                                 
 
                                                        



                                                                   

                                 

                                                   












                                                                                                 

                                                                    
                                                    




                                                                    
                                                               



                                                                    
                                                              

                                                              
 
                                                                           








                                                                                           
                                              
























                                                                                    
                                                               





                                                                                         
                                                      
















































                                                                                    







































                                                                                    



























                                                                                                   
 
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%

-module(ssh_sup_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("ssh/src/ssh.hrl").
-include("ssh_test_lib.hrl").

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

-define(USER, "Alladin").
-define(PASSWD, "Sesame").

-define(WAIT_FOR_SHUTDOWN, 500).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

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

all() -> 
    [default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile,
     killed_acceptor_restarts,
     shell_channel_tree
    ].

groups() -> 
    [].

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.

init_per_suite(Config) ->
    ?CHECK_CRYPTO(
       begin
	   Port = ssh_test_lib:inet_port(node()),
	   PrivDir = proplists:get_value(priv_dir, Config),
	   UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
	   file:make_dir(UserDir),
	   [{userdir, UserDir},{port, Port}, {host, "localhost"}, {host_ip, any} | Config]
       end).

end_per_suite(_) ->
    ok.

init_per_testcase(sshc_subtree, Config) ->  
    ssh:start(),
    SystemDir = proplists:get_value(data_dir, Config),
    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
					      {failfun, fun ssh_test_lib:failfun/2},
					      {user_passwords,
					       [{?USER, ?PASSWD}]}]),
    [{server, {Pid, Host, Port}} | Config];
init_per_testcase(Case, Config) ->
    end_per_testcase(Case, Config),
    ssh:start(),
    Config.
end_per_testcase(sshc_subtree, Config) ->
    {Pid,_,_} = proplists:get_value(server, Config), 
    ssh:stop_daemon(Pid),
    ssh:stop();
end_per_testcase(_, _Config) ->
    ssh:stop().

%%-------------------------------------------------------------------------
%% Test cases 
%%-------------------------------------------------------------------------
default_tree() ->
    [{doc, "Makes sure the correct processes are started and linked," 
     "in the default case."}].
default_tree(Config) when is_list(Config) ->
    TopSupChildren = supervisor:which_children(ssh_sup),
    2 = length(TopSupChildren),
    {value, {sshc_sup, _, supervisor,[sshc_sup]}} =
	lists:keysearch(sshc_sup, 1, TopSupChildren),
    {value, {sshd_sup, _,supervisor,[sshd_sup]}} = 
	lists:keysearch(sshd_sup, 1, TopSupChildren),
    ?wait_match([], supervisor:which_children(sshc_sup)),
    ?wait_match([], supervisor:which_children(sshd_sup)).

%%-------------------------------------------------------------------------
sshc_subtree() ->
    [{doc, "Make sure the sshc subtree is correct"}].
sshc_subtree(Config) when is_list(Config) ->
    {_Pid, Host, Port} = proplists:get_value(server, Config),
    UserDir = proplists:get_value(userdir, Config),

    ?wait_match([], supervisor:which_children(sshc_sup)),

    {ok, Pid1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_interaction, false},
					  {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),

    ?wait_match([{_, _,worker,[ssh_connection_handler]}],
		supervisor:which_children(sshc_sup)),

    {ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
					  {user_interaction, false},
					  {user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]),
    ?wait_match([{_,_,worker,[ssh_connection_handler]}, 
		 {_,_,worker,[ssh_connection_handler]}],
		supervisor:which_children(sshc_sup)),

    ssh:close(Pid1),
    ?wait_match([{_,_,worker,[ssh_connection_handler]}],
		supervisor:which_children(sshc_sup)),
    ssh:close(Pid2),
    ?wait_match([], supervisor:which_children(sshc_sup)).

%%-------------------------------------------------------------------------
sshd_subtree() ->
    [{doc, "Make sure the sshd subtree is correct"}].
sshd_subtree(Config) when is_list(Config) ->
    HostIP = proplists:get_value(host_ip, Config),
    Port = proplists:get_value(port, Config),
    SystemDir = proplists:get_value(data_dir, Config),
    {ok,Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
                                            {failfun, fun ssh_test_lib:failfun/2},
                                            {user_passwords,
                                             [{?USER, ?PASSWD}]}]),

    ct:log("Expect HostIP=~p, Port=~p, Daemon=~p",[HostIP,Port,Daemon]),
    ?wait_match([{{server,ssh_system_sup, ListenIP, Port, ?DEFAULT_PROFILE},
		  Daemon, supervisor,
		  [ssh_system_sup]}],
		supervisor:which_children(sshd_sup),
		[ListenIP,Daemon]),
    true = ssh_test_lib:match_ip(HostIP, ListenIP),
    check_sshd_system_tree(Daemon, Config),
    ssh:stop_daemon(HostIP, Port),
    ct:sleep(?WAIT_FOR_SHUTDOWN),
    ?wait_match([], supervisor:which_children(sshd_sup)).

%%-------------------------------------------------------------------------
sshd_subtree_profile() ->
    [{doc, "Make sure the sshd subtree using profile option is correct"}].	
sshd_subtree_profile(Config) when is_list(Config) ->
    HostIP = proplists:get_value(host_ip, Config),
    Port = proplists:get_value(port, Config),
    Profile = proplists:get_value(profile, Config), 
    SystemDir = proplists:get_value(data_dir, Config),

    {ok, Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
                                             {failfun, fun ssh_test_lib:failfun/2},
                                             {user_passwords,
                                              [{?USER, ?PASSWD}]},
                                             {profile, Profile}]),
    ct:log("Expect HostIP=~p, Port=~p, Profile=~p, Daemon=~p",[HostIP,Port,Profile,Daemon]),
    ?wait_match([{{server,ssh_system_sup, ListenIP,Port,Profile},
		  Daemon, supervisor,
		  [ssh_system_sup]}],
		supervisor:which_children(sshd_sup),
		[ListenIP,Daemon]),
    true = ssh_test_lib:match_ip(HostIP, ListenIP),
    check_sshd_system_tree(Daemon, Config),
    ssh:stop_daemon(HostIP, Port, Profile),
    ct:sleep(?WAIT_FOR_SHUTDOWN),
    ?wait_match([], supervisor:which_children(sshd_sup)).

%%-------------------------------------------------------------------------
killed_acceptor_restarts(Config) ->
    Profile = proplists:get_value(profile, Config), 
    SystemDir = proplists:get_value(data_dir, Config),
    UserDir = proplists:get_value(userdir, Config),
    {ok, DaemonPid} = ssh:daemon(0, [{system_dir, SystemDir},
                                     {failfun, fun ssh_test_lib:failfun/2},
                                     {user_passwords, [{?USER, ?PASSWD}]},
                                     {profile, Profile}]),

    {ok, DaemonPid2} = ssh:daemon(0, [{system_dir, SystemDir},
                                     {failfun, fun ssh_test_lib:failfun/2},
                                     {user_passwords, [{?USER, ?PASSWD}]},
                                     {profile, Profile}]),

    Port  = ssh_test_lib:daemon_port(DaemonPid),
    Port2 = ssh_test_lib:daemon_port(DaemonPid2),
    true = (Port /= Port2),

    {ok,[{AccPid,ListenAddr,Port}]} = acceptor_pid(DaemonPid),
    {ok,[{AccPid2,ListenAddr,Port2}]} = acceptor_pid(DaemonPid2),

    true = (AccPid /= AccPid2),

    %% Connect first client and check it is alive:
    {ok,C1} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
                                              {user_interaction, false},
                                              {user, ?USER},
                                              {password, ?PASSWD},
                                              {user_dir, UserDir}]),
    [{client_version,_}] = ssh:connection_info(C1,[client_version]),

    ct:log("~s",[lists:flatten(ssh_info:string())]),

    %% Make acceptor restart:
    exit(AccPid, kill),
    ?wait_match(undefined, process_info(AccPid)),

    %% Check it is a new acceptor and wait if it is not:
    ?wait_match({ok,[{AccPid1,ListenAddr,Port}]}, AccPid1=/=AccPid,
                acceptor_pid(DaemonPid),
                AccPid1,
                500, 30),

    true = (AccPid1 =/= AccPid2),

    %% Connect second client and check it is alive:
    C2 =
        case ssh:connect("localhost", Port, [{silently_accept_hosts, true},
                                             {user_interaction, false},
                                             {user, ?USER},
                                             {password, ?PASSWD},
                                             {user_dir, UserDir}]) of
            {ok,_C2} ->
                _C2;
            _Other ->
                ct:log("new connect failed: ~p~n~n~s",[_Other,lists:flatten(ssh_info:string())]),
                ct:fail("Re-connect failed!", [])
        end,

    [{client_version,_}] = ssh:connection_info(C2,[client_version]),
    
    ct:log("~s",[lists:flatten(ssh_info:string())]),

    %% Check first client is still alive:
    [{client_version,_}] = ssh:connection_info(C1,[client_version]),
    
    ok = ssh:stop_daemon(DaemonPid2),
    ?wait_match(undefined, process_info(DaemonPid2), 1000, 30),
    [{client_version,_}] = ssh:connection_info(C1,[client_version]),
    [{client_version,_}] = ssh:connection_info(C2,[client_version]),
    
    ok = ssh:stop_daemon(DaemonPid),
    ?wait_match(undefined, process_info(DaemonPid), 1000, 30),
    {error,closed} = ssh:connection_info(C1,[client_version]),
    {error,closed} = ssh:connection_info(C2,[client_version]).

%%-------------------------------------------------------------------------
shell_channel_tree(Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
    file:make_dir(UserDir),
    SysDir = proplists:get_value(data_dir, Config),
    TimeoutShell =
        fun() ->
                io:format("TimeoutShell started!~n",[]),
                timer:sleep(5000),
                ct:log("~p TIMEOUT!",[self()])
        end,
    {Daemon, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
                                                {user_dir, UserDir},
                                                {password, "morot"},
                                                {shell, fun(_User) ->
                                                                spawn(TimeoutShell)
                                                        end
                                                }
                                            ]),
    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
						      {user, "foo"},
						      {password, "morot"},
						      {user_interaction, true},
						      {user_dir, UserDir}]),

    [ChannelSup|_] = Sups0 = chk_empty_con_daemon(Daemon),
    
    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
    ok = ssh_connection:shell(ConnectionRef,ChannelId0),

    ?wait_match([{_, GroupPid,worker,[ssh_channel]}],
		supervisor:which_children(ChannelSup),
               [GroupPid]),
    {links,GroupLinks} = erlang:process_info(GroupPid, links),
    [ShellPid] = GroupLinks--[ChannelSup],
    ct:log("GroupPid = ~p, ShellPid = ~p",[GroupPid,ShellPid]),

    receive
        {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"TimeoutShell started!\r\n">>}} ->
            receive
                %%---- wait for the subsystem to terminate
                {ssh_cm,ConnectionRef,{closed,ChannelId0}} ->
                    ct:log("Subsystem terminated",[]),
                    case {chk_empty_con_daemon(Daemon),
                          process_info(GroupPid),
                          process_info(ShellPid)} of
                        {Sups0, undefined, undefined} ->
                            %% SUCCESS
                            ssh:stop_daemon(Daemon);
                        {Sups0, _, undefined}  ->
                            ssh:stop_daemon(Daemon),
                            ct:fail("Group proc lives!");
                        {Sups0, undefined, _}  ->
                            ssh:stop_daemon(Daemon),
                            ct:fail("Shell proc lives!");
                        _ ->
                            ssh:stop_daemon(Daemon),
                            ct:fail("Sup tree changed!")
                    end
            after 10000 ->
                    ssh:close(ConnectionRef),
                    ssh:stop_daemon(Daemon),
                    ct:fail("CLI Timeout")
            end
    after 10000 ->
            ssh:close(ConnectionRef),
            ssh:stop_daemon(Daemon),
            ct:fail("CLI Timeout")
    end.


chk_empty_con_daemon(Daemon) ->
    ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
		 {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
		supervisor:which_children(Daemon),
                [SubSysSup,AccSup]),
    ?wait_match([{{server,ssh_connection_sup, _,_},
		  ConnectionSup, supervisor,
		  [ssh_connection_sup]},
		 {{server,ssh_channel_sup,_ ,_},
		  ChannelSup,supervisor,
		  [ssh_channel_sup]}],
		supervisor:which_children(SubSysSup),
		[ConnectionSup,ChannelSup]),
    ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
		supervisor:which_children(AccSup)),
    ?wait_match([{_, _, worker,[ssh_connection_handler]}],
		supervisor:which_children(ConnectionSup)),
    ?wait_match([], supervisor:which_children(ChannelSup)),
    [ChannelSup, ConnectionSup, SubSysSup, AccSup].

%%-------------------------------------------------------------------------
%% Help functions
%%-------------------------------------------------------------------------
check_sshd_system_tree(Daemon, Config) -> 
    Host = proplists:get_value(host, Config),
    Port = proplists:get_value(port, Config),
    UserDir = proplists:get_value(userdir, Config),
    {ok, Client} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
                                            {user_interaction, false},
                                            {user, ?USER},
                                            {password, ?PASSWD},
                                            {user_dir, UserDir}]),
    
    ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
		 {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
		supervisor:which_children(Daemon),
                [SubSysSup,AccSup]),
    
    ?wait_match([{{server,ssh_connection_sup, _,_},
		  ConnectionSup, supervisor,
		  [ssh_connection_sup]},
		 {{server,ssh_channel_sup,_ ,_},
		  ChannelSup,supervisor,
		  [ssh_channel_sup]}],
		supervisor:which_children(SubSysSup),
		[ConnectionSup,ChannelSup]),
    
    ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
		supervisor:which_children(AccSup)),
    
    ?wait_match([{_, _, worker,[ssh_connection_handler]}],
		supervisor:which_children(ConnectionSup)),
    
    ?wait_match([], supervisor:which_children(ChannelSup)),
    
    ssh_sftp:start_channel(Client),

    ?wait_match([{_, _,worker,[ssh_channel]}],
		supervisor:which_children(ChannelSup)),
    ssh:close(Client).

acceptor_pid(DaemonPid) ->
    Parent = self(),
    Pid = spawn(fun() ->
                        Parent ! {self(), supsearch,
                                  [{AccPid,ListenAddr,Port}

                                   || {{server,ssh_system_sup,ListenAddr,Port,NS},
                                       DPid,supervisor,
                                       [ssh_system_sup]} <- supervisor:which_children(sshd_sup),
                                      DPid == DaemonPid,

                                      {{ssh_acceptor_sup,L1,P1,NS1},
                                       AccSupPid,supervisor,
                                       [ssh_acceptor_sup]} <- supervisor:which_children(DaemonPid),
                                      L1 == ListenAddr,
                                      P1 == Port,
                                      NS1 == NS1,

                                      {{ssh_acceptor_sup,L2,P2,NS2},
                                       AccPid,worker,
                                       [ssh_acceptor]} <- supervisor:which_children(AccSupPid),
                                      L2 == ListenAddr,
                                      P2 == Port,
                                      NS2 == NS]}
                end),
    receive {Pid, supsearch, L} -> {ok,L}
    after 2000 -> timeout
    end.