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