1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2015-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21 22-module(ssh_sup_SUITE). 23-include_lib("common_test/include/ct.hrl"). 24-include_lib("ssh/src/ssh.hrl"). 25-include("ssh_test_lib.hrl"). 26 27%% Note: This directive should only be used in test suites. 28-compile(export_all). 29 30-define(USER, "Alladin"). 31-define(PASSWD, "Sesame"). 32 33-define(WAIT_FOR_SHUTDOWN, 500). 34 35%%-------------------------------------------------------------------- 36%% Common Test interface functions ----------------------------------- 37%%-------------------------------------------------------------------- 38 39suite() -> 40 [{ct_hooks,[ts_install_cth]}, 41 {timetrap,{seconds,100}}]. 42 43all() -> 44 [default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile, 45 killed_acceptor_restarts, 46 shell_channel_tree 47 ]. 48 49groups() -> 50 []. 51 52init_per_group(_GroupName, Config) -> 53 Config. 54 55end_per_group(_GroupName, Config) -> 56 Config. 57 58init_per_suite(Config) -> 59 ?CHECK_CRYPTO( 60 begin 61 Port = ssh_test_lib:inet_port(node()), 62 PrivDir = proplists:get_value(priv_dir, Config), 63 UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth 64 file:make_dir(UserDir), 65 [{userdir, UserDir},{port, Port}, {host, "localhost"}, {host_ip, any} | Config] 66 end). 67 68end_per_suite(_) -> 69 ok. 70 71init_per_testcase(sshc_subtree, Config) -> 72 ssh:start(), 73 SystemDir = proplists:get_value(data_dir, Config), 74 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, 75 {failfun, fun ssh_test_lib:failfun/2}, 76 {user_passwords, 77 [{?USER, ?PASSWD}]}]), 78 [{server, {Pid, Host, Port}} | Config]; 79init_per_testcase(Case, Config) -> 80 end_per_testcase(Case, Config), 81 ssh:start(), 82 Config. 83end_per_testcase(sshc_subtree, Config) -> 84 {Pid,_,_} = proplists:get_value(server, Config), 85 ssh:stop_daemon(Pid), 86 ssh:stop(); 87end_per_testcase(_, _Config) -> 88 ssh:stop(). 89 90%%------------------------------------------------------------------------- 91%% Test cases 92%%------------------------------------------------------------------------- 93default_tree() -> 94 [{doc, "Makes sure the correct processes are started and linked," 95 "in the default case."}]. 96default_tree(Config) when is_list(Config) -> 97 TopSupChildren = supervisor:which_children(ssh_sup), 98 2 = length(TopSupChildren), 99 {value, {sshc_sup, _, supervisor,[sshc_sup]}} = 100 lists:keysearch(sshc_sup, 1, TopSupChildren), 101 {value, {sshd_sup, _,supervisor,[sshd_sup]}} = 102 lists:keysearch(sshd_sup, 1, TopSupChildren), 103 ?wait_match([], supervisor:which_children(sshc_sup)), 104 ?wait_match([], supervisor:which_children(sshd_sup)). 105 106%%------------------------------------------------------------------------- 107sshc_subtree() -> 108 [{doc, "Make sure the sshc subtree is correct"}]. 109sshc_subtree(Config) when is_list(Config) -> 110 {_Pid, Host, Port} = proplists:get_value(server, Config), 111 UserDir = proplists:get_value(userdir, Config), 112 113 ?wait_match([], supervisor:which_children(sshc_sup)), 114 115 {ok, Pid1} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 116 {user_interaction, false}, 117 {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]), 118 119 ?wait_match([{_, _,worker,[ssh_connection_handler]}], 120 supervisor:which_children(sshc_sup)), 121 122 {ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 123 {user_interaction, false}, 124 {user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]), 125 ?wait_match([{_,_,worker,[ssh_connection_handler]}, 126 {_,_,worker,[ssh_connection_handler]}], 127 supervisor:which_children(sshc_sup)), 128 129 ssh:close(Pid1), 130 ?wait_match([{_,_,worker,[ssh_connection_handler]}], 131 supervisor:which_children(sshc_sup)), 132 ssh:close(Pid2), 133 ?wait_match([], supervisor:which_children(sshc_sup)). 134 135%%------------------------------------------------------------------------- 136sshd_subtree() -> 137 [{doc, "Make sure the sshd subtree is correct"}]. 138sshd_subtree(Config) when is_list(Config) -> 139 HostIP = proplists:get_value(host_ip, Config), 140 Port = proplists:get_value(port, Config), 141 SystemDir = proplists:get_value(data_dir, Config), 142 {ok,Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir}, 143 {failfun, fun ssh_test_lib:failfun/2}, 144 {user_passwords, 145 [{?USER, ?PASSWD}]}]), 146 147 ct:log("Expect HostIP=~p, Port=~p, Daemon=~p",[HostIP,Port,Daemon]), 148 ?wait_match([{{server,ssh_system_sup, ListenIP, Port, ?DEFAULT_PROFILE}, 149 Daemon, supervisor, 150 [ssh_system_sup]}], 151 supervisor:which_children(sshd_sup), 152 [ListenIP,Daemon]), 153 true = ssh_test_lib:match_ip(HostIP, ListenIP), 154 check_sshd_system_tree(Daemon, Config), 155 ssh:stop_daemon(HostIP, Port), 156 ct:sleep(?WAIT_FOR_SHUTDOWN), 157 ?wait_match([], supervisor:which_children(sshd_sup)). 158 159%%------------------------------------------------------------------------- 160sshd_subtree_profile() -> 161 [{doc, "Make sure the sshd subtree using profile option is correct"}]. 162sshd_subtree_profile(Config) when is_list(Config) -> 163 HostIP = proplists:get_value(host_ip, Config), 164 Port = proplists:get_value(port, Config), 165 Profile = proplists:get_value(profile, Config), 166 SystemDir = proplists:get_value(data_dir, Config), 167 168 {ok, Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir}, 169 {failfun, fun ssh_test_lib:failfun/2}, 170 {user_passwords, 171 [{?USER, ?PASSWD}]}, 172 {profile, Profile}]), 173 ct:log("Expect HostIP=~p, Port=~p, Profile=~p, Daemon=~p",[HostIP,Port,Profile,Daemon]), 174 ?wait_match([{{server,ssh_system_sup, ListenIP,Port,Profile}, 175 Daemon, supervisor, 176 [ssh_system_sup]}], 177 supervisor:which_children(sshd_sup), 178 [ListenIP,Daemon]), 179 true = ssh_test_lib:match_ip(HostIP, ListenIP), 180 check_sshd_system_tree(Daemon, Config), 181 ssh:stop_daemon(HostIP, Port, Profile), 182 ct:sleep(?WAIT_FOR_SHUTDOWN), 183 ?wait_match([], supervisor:which_children(sshd_sup)). 184 185%%------------------------------------------------------------------------- 186killed_acceptor_restarts(Config) -> 187 Profile = proplists:get_value(profile, Config), 188 SystemDir = proplists:get_value(data_dir, Config), 189 UserDir = proplists:get_value(userdir, Config), 190 {ok, DaemonPid} = ssh:daemon(0, [{system_dir, SystemDir}, 191 {failfun, fun ssh_test_lib:failfun/2}, 192 {user_passwords, [{?USER, ?PASSWD}]}, 193 {profile, Profile}]), 194 195 {ok, DaemonPid2} = ssh:daemon(0, [{system_dir, SystemDir}, 196 {failfun, fun ssh_test_lib:failfun/2}, 197 {user_passwords, [{?USER, ?PASSWD}]}, 198 {profile, Profile}]), 199 200 Port = ssh_test_lib:daemon_port(DaemonPid), 201 Port2 = ssh_test_lib:daemon_port(DaemonPid2), 202 true = (Port /= Port2), 203 204 {ok,[{AccPid,ListenAddr,Port}]} = acceptor_pid(DaemonPid), 205 {ok,[{AccPid2,ListenAddr,Port2}]} = acceptor_pid(DaemonPid2), 206 207 true = (AccPid /= AccPid2), 208 209 %% Connect first client and check it is alive: 210 {ok,C1} = ssh:connect("localhost", Port, [{silently_accept_hosts, true}, 211 {user_interaction, false}, 212 {user, ?USER}, 213 {password, ?PASSWD}, 214 {user_dir, UserDir}]), 215 [{client_version,_}] = ssh:connection_info(C1,[client_version]), 216 217 ct:log("~s",[lists:flatten(ssh_info:string())]), 218 219 %% Make acceptor restart: 220 exit(AccPid, kill), 221 ?wait_match(undefined, process_info(AccPid)), 222 223 %% Check it is a new acceptor and wait if it is not: 224 ?wait_match({ok,[{AccPid1,ListenAddr,Port}]}, AccPid1=/=AccPid, 225 acceptor_pid(DaemonPid), 226 AccPid1, 227 500, 30), 228 229 true = (AccPid1 =/= AccPid2), 230 231 %% Connect second client and check it is alive: 232 C2 = 233 case ssh:connect("localhost", Port, [{silently_accept_hosts, true}, 234 {user_interaction, false}, 235 {user, ?USER}, 236 {password, ?PASSWD}, 237 {user_dir, UserDir}]) of 238 {ok,_C2} -> 239 _C2; 240 _Other -> 241 ct:log("new connect failed: ~p~n~n~s",[_Other,lists:flatten(ssh_info:string())]), 242 ct:fail("Re-connect failed!", []) 243 end, 244 245 [{client_version,_}] = ssh:connection_info(C2,[client_version]), 246 247 ct:log("~s",[lists:flatten(ssh_info:string())]), 248 249 %% Check first client is still alive: 250 [{client_version,_}] = ssh:connection_info(C1,[client_version]), 251 252 ok = ssh:stop_daemon(DaemonPid2), 253 ?wait_match(undefined, process_info(DaemonPid2), 1000, 30), 254 [{client_version,_}] = ssh:connection_info(C1,[client_version]), 255 [{client_version,_}] = ssh:connection_info(C2,[client_version]), 256 257 ok = ssh:stop_daemon(DaemonPid), 258 ?wait_match(undefined, process_info(DaemonPid), 1000, 30), 259 ?wait_match({error,closed}, ssh:connection_info(C1,[client_version]), 1000, 5), 260 ?wait_match({error,closed}, ssh:connection_info(C2,[client_version]), 1000, 5). 261 262%%------------------------------------------------------------------------- 263shell_channel_tree(Config) -> 264 PrivDir = proplists:get_value(priv_dir, Config), 265 UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth 266 file:make_dir(UserDir), 267 SysDir = proplists:get_value(data_dir, Config), 268 TimeoutShell = 269 fun() -> 270 io:format("TimeoutShell started!~n",[]), 271 timer:sleep(5000), 272 ct:log("~p TIMEOUT!",[self()]) 273 end, 274 {Daemon, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 275 {user_dir, UserDir}, 276 {password, "morot"}, 277 {shell, fun(_User) -> 278 spawn(TimeoutShell) 279 end 280 } 281 ]), 282 ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 283 {user, "foo"}, 284 {password, "morot"}, 285 {user_interaction, true}, 286 {user_dir, UserDir}]), 287 288 [ChannelSup|_] = Sups0 = chk_empty_con_daemon(Daemon), 289 290 {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), 291 ok = ssh_connection:shell(ConnectionRef,ChannelId0), 292 293 ?wait_match([{_, GroupPid,worker,[ssh_server_channel]}], 294 supervisor:which_children(ChannelSup), 295 [GroupPid]), 296 {links,GroupLinks} = erlang:process_info(GroupPid, links), 297 [ShellPid] = GroupLinks--[ChannelSup], 298 ct:log("GroupPid = ~p, ShellPid = ~p",[GroupPid,ShellPid]), 299 300 receive 301 {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"TimeoutShell started!\r\n">>}} -> 302 receive 303 %%---- wait for the subsystem to terminate 304 {ssh_cm,ConnectionRef,{closed,ChannelId0}} -> 305 ct:log("Subsystem terminated",[]), 306 case {chk_empty_con_daemon(Daemon), 307 process_info(GroupPid), 308 process_info(ShellPid)} of 309 {Sups0, undefined, undefined} -> 310 %% SUCCESS 311 ssh:stop_daemon(Daemon); 312 {Sups0, _, undefined} -> 313 ssh:stop_daemon(Daemon), 314 ct:fail("Group proc lives!"); 315 {Sups0, undefined, _} -> 316 ssh:stop_daemon(Daemon), 317 ct:fail("Shell proc lives!"); 318 _ -> 319 ssh:stop_daemon(Daemon), 320 ct:fail("Sup tree changed!") 321 end 322 after 10000 -> 323 ssh:close(ConnectionRef), 324 ssh:stop_daemon(Daemon), 325 ct:fail("CLI Timeout") 326 end 327 after 10000 -> 328 ssh:close(ConnectionRef), 329 ssh:stop_daemon(Daemon), 330 ct:fail("CLI Timeout") 331 end. 332 333 334chk_empty_con_daemon(Daemon) -> 335 ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]}, 336 {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}], 337 supervisor:which_children(Daemon), 338 [SubSysSup,AccSup]), 339 ?wait_match([{{server,ssh_connection_sup, _,_}, 340 ConnectionSup, supervisor, 341 [ssh_connection_sup]}, 342 {{server,ssh_server_channel_sup,_ ,_}, 343 ChannelSup,supervisor, 344 [ssh_server_channel_sup]}], 345 supervisor:which_children(SubSysSup), 346 [ConnectionSup,ChannelSup]), 347 ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}], 348 supervisor:which_children(AccSup)), 349 ?wait_match([{_, _, worker,[ssh_connection_handler]}], 350 supervisor:which_children(ConnectionSup)), 351 ?wait_match([], supervisor:which_children(ChannelSup)), 352 [ChannelSup, ConnectionSup, SubSysSup, AccSup]. 353 354%%------------------------------------------------------------------------- 355%% Help functions 356%%------------------------------------------------------------------------- 357check_sshd_system_tree(Daemon, Config) -> 358 Host = proplists:get_value(host, Config), 359 Port = proplists:get_value(port, Config), 360 UserDir = proplists:get_value(userdir, Config), 361 {ok, Client} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 362 {user_interaction, false}, 363 {user, ?USER}, 364 {password, ?PASSWD}, 365 {user_dir, UserDir}]), 366 367 ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]}, 368 {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}], 369 supervisor:which_children(Daemon), 370 [SubSysSup,AccSup]), 371 372 ?wait_match([{{server,ssh_connection_sup, _,_}, 373 ConnectionSup, supervisor, 374 [ssh_connection_sup]}, 375 {{server,ssh_server_channel_sup,_ ,_}, 376 ChannelSup,supervisor, 377 [ssh_server_channel_sup]}], 378 supervisor:which_children(SubSysSup), 379 [ConnectionSup,ChannelSup]), 380 381 ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}], 382 supervisor:which_children(AccSup)), 383 384 ?wait_match([{_, _, worker,[ssh_connection_handler]}], 385 supervisor:which_children(ConnectionSup)), 386 387 ?wait_match([], supervisor:which_children(ChannelSup)), 388 389 ssh_sftp:start_channel(Client), 390 391 ?wait_match([{_, _,worker,[ssh_server_channel]}], 392 supervisor:which_children(ChannelSup)), 393 ssh:close(Client). 394 395acceptor_pid(DaemonPid) -> 396 Parent = self(), 397 Pid = spawn(fun() -> 398 Parent ! {self(), supsearch, 399 [{AccPid,ListenAddr,Port} 400 401 || {{server,ssh_system_sup,ListenAddr,Port,NS}, 402 DPid,supervisor, 403 [ssh_system_sup]} <- supervisor:which_children(sshd_sup), 404 DPid == DaemonPid, 405 406 {{ssh_acceptor_sup,L1,P1,NS1}, 407 AccSupPid,supervisor, 408 [ssh_acceptor_sup]} <- supervisor:which_children(DaemonPid), 409 L1 == ListenAddr, 410 P1 == Port, 411 NS1 == NS1, 412 413 {{ssh_acceptor_sup,L2,P2,NS2}, 414 AccPid,worker, 415 [ssh_acceptor]} <- supervisor:which_children(AccSupPid), 416 L2 == ListenAddr, 417 P2 == Port, 418 NS2 == NS]} 419 end), 420 receive {Pid, supsearch, L} -> {ok,L} 421 after 2000 -> timeout 422 end. 423 424