1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2020. 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
23-module(ssh_basic_SUITE).
24
25-include_lib("common_test/include/ct.hrl").
26-include_lib("kernel/include/inet.hrl").
27-include_lib("kernel/include/file.hrl").
28-include("ssh_test_lib.hrl").
29
30-export([
31         suite/0,
32         all/0,
33         groups/0,
34         init_per_suite/1,
35         end_per_suite/1,
36         init_per_group/2,
37         end_per_group/2,
38         init_per_testcase/2,
39         end_per_testcase/2
40        ]).
41
42-export([
43         always_ok/1,
44         app_test/1,
45         appup_test/1,
46         basic_test/1,
47         check_error/1,
48         cli/1,
49         cli_exit_normal/1,
50         cli_exit_status/1,
51         close/1,
52         daemon_already_started/1,
53         daemon_error_closes_port/1,
54         daemon_opt_fd/1,
55         double_close/1,
56         exec/1,
57         exec_compressed/1,
58         exec_with_io_in/1,
59         exec_with_io_out/1,
60         host_equal/2,
61         idle_time_client/1,
62         idle_time_server/1,
63         inet6_option/0,
64         inet6_option/1,
65         inet_option/1,
66         internal_error/1,
67         ips/1,
68         key_callback/1,
69         key_callback_options/1,
70         known_hosts/1,
71         login_bad_pwd_no_retry1/1,
72         login_bad_pwd_no_retry2/1,
73         login_bad_pwd_no_retry3/1,
74         login_bad_pwd_no_retry4/1,
75         login_bad_pwd_no_retry5/1,
76         misc_ssh_options/1,
77         multi_daemon_opt_fd/1,
78         openssh_zlib_basic_test/1,
79         packet_size/1,
80         pass_phrase/1,
81         peername_sockname/1,
82         send/1,
83         setopts_getopts/1,
84         shell/1,
85         shell_exit_status/1,
86         shell_no_unicode/1,
87         shell_socket/1,
88         shell_ssh_conn/1,
89         shell_unicode_string/1,
90         ssh_file_is_auth_key/1,
91         ssh_file_is_host_key/0,
92         ssh_file_is_host_key/1,
93         ssh_file_is_host_key_misc/1,
94         ssh_info_print/1
95        ]).
96
97
98-define(NEWLINE, <<"\r\n">>).
99
100-define(REKEY_DATA_TMO, 1 * 60000). % Should be multiples of 60000
101
102%%--------------------------------------------------------------------
103%% Common Test interface functions -----------------------------------
104%%--------------------------------------------------------------------
105
106suite() ->
107    [{ct_hooks,[ts_install_cth]},
108     {timetrap,{seconds,90}}].
109
110all() ->
111    [{group, all_tests}
112    ].
113
114%%%-define(PARALLEL, ).
115-define(PARALLEL, parallel).
116
117groups() ->
118    [{all_tests, [?PARALLEL], [{group, sequential},
119                               {group, p_basic},
120                               {group, internal_error},
121                               {group, login_bad_pwd_no_retry},
122                               {group, key_cb}
123                              ]},
124
125     {sequential, [], [app_test,
126                       appup_test,
127                       daemon_already_started,
128                       daemon_error_closes_port, % Should be re-written..
129                       double_close,
130                       daemon_opt_fd,
131                       multi_daemon_opt_fd,
132                       packet_size,
133                       ssh_info_print,
134                       shell_exit_status,
135                       setopts_getopts,
136                       known_hosts,
137                       ssh_file_is_host_key,
138                       ssh_file_is_host_key_misc,
139                       ssh_file_is_auth_key
140                      ]},
141
142     {key_cb, [?PARALLEL], [key_callback, key_callback_options]},
143
144     {internal_error, [?PARALLEL], [internal_error]},
145
146     {login_bad_pwd_no_retry, [?PARALLEL], [login_bad_pwd_no_retry1,
147                                            login_bad_pwd_no_retry2,
148                                            login_bad_pwd_no_retry3,
149                                            login_bad_pwd_no_retry4,
150                                            login_bad_pwd_no_retry5
151                                           ]},
152
153     {p_basic, [?PARALLEL], [send, peername_sockname,
154                             exec, exec_compressed,
155                             exec_with_io_out, exec_with_io_in,
156                             cli, cli_exit_normal, cli_exit_status,
157                             idle_time_client, idle_time_server, openssh_zlib_basic_test,
158                             misc_ssh_options, inet_option, inet6_option,
159                             shell, shell_socket, shell_ssh_conn, shell_no_unicode, shell_unicode_string,
160                             close
161                            ]}
162    ].
163
164%%--------------------------------------------------------------------
165init_per_suite(Config) ->
166    ?CHECK_CRYPTO(begin
167                      ssh:start(),
168                      ct:log("Pub keys setup for: ~p",
169                             [ssh_test_lib:setup_all_user_host_keys(Config)]),
170                      Config
171                  end).
172
173end_per_suite(_Config) ->
174    ssh:stop().
175
176%%--------------------------------------------------------------------
177init_per_group(key_cb, Config) ->
178    case lists:member('ssh-rsa',
179		      ssh_transport:supported_algorithms(public_key)) of
180	true ->
181            DataDir = proplists:get_value(data_dir, Config),
182            PrivDir = proplists:get_value(priv_dir, Config),
183            ssh_test_lib:setup_user_key('ssh-rsa', DataDir, PrivDir),
184            ssh_test_lib:setup_host_key_create_dir('ssh-rsa', DataDir, PrivDir),
185            Config;
186	false ->
187	    {skip, unsupported_pub_key}
188    end;
189init_per_group(_, Config) ->
190    Config.
191
192
193end_per_group(_, Config) ->
194    Config.
195%%--------------------------------------------------------------------
196init_per_testcase(TC, Config) when TC==shell_no_unicode ;
197				   TC==shell_unicode_string ->
198    PrivDir = proplists:get_value(priv_dir, Config),
199    UserDir = proplists:get_value(priv_dir, Config),
200    SysDir =  proplists:get_value(data_dir, Config),
201    Sftpd = {_Pid, _Host, Port} =
202	ssh_test_lib:daemon([{system_dir, SysDir},
203			     {user_dir, PrivDir},
204			     {user_passwords, [{"foo", "bar"}]}]),
205    ct:sleep(500),
206    IO = ssh_test_lib:start_io_server(),
207    Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
208						{silently_accept_hosts, true},
209						{user,"foo"},{password,"bar"}]),
210    ct:log("IO=~p, Shell=~p, self()=~p",[IO,Shell,self()]),
211    ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p",
212	   [file:native_name_encoding(),io:getopts()]),
213    wait_for_erlang_first_line([{io,IO}, {shell,Shell}, {sftpd, Sftpd}  | Config]);
214
215init_per_testcase(inet6_option, Config) ->
216    case ssh_test_lib:has_inet6_address() of
217	true ->
218	    init_per_testcase('__default__', Config);
219	false ->
220	    {skip,"No ipv6 interface address"}
221    end;
222init_per_testcase(_TestCase, Config) ->
223    Config.
224
225end_per_testcase(TC, Config) when TC==shell_no_unicode ;
226				  TC==shell_unicode_string ->
227    case proplists:get_value(sftpd, Config) of
228	{Pid, _, _} ->
229	    catch ssh:stop_daemon(Pid);
230	_ ->
231	    ok
232    end,
233    end_per_testcase(Config);
234end_per_testcase(_TestCase, Config) ->
235    end_per_testcase(Config).
236
237end_per_testcase(_Config) ->
238    ok.
239
240%%--------------------------------------------------------------------
241%% Test Cases --------------------------------------------------------
242%%--------------------------------------------------------------------
243%%% Application consistency test.
244app_test(Config) when is_list(Config) ->
245    test_server:app_test(ssh),
246    ok.
247%%--------------------------------------------------------------------
248%%% Appup file consistency test.
249appup_test(Config) when is_list(Config) ->
250    ok = test_server:appup_test(ssh).
251%%--------------------------------------------------------------------
252%%% Test that we can set some misc options not tested elsewhere
253%%% some options not yet present are not decided if we should support or
254%%% if they need thier own test case.
255misc_ssh_options(Config) when is_list(Config) ->
256    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
257    UserDir = proplists:get_value(priv_dir, Config),
258
259    CMiscOpt0 = [{connect_timeout, 1000}, {user_dir, UserDir}, {silently_accept_hosts, true}],
260    CMiscOpt1 = [{connect_timeout, infinity}, {user_dir, UserDir}, {silently_accept_hosts, true}],
261    SMiscOpt0 =  [{user_dir, UserDir}, {system_dir, SystemDir}],
262    SMiscOpt1 =  [{user_dir, UserDir}, {system_dir, SystemDir}],
263
264    basic_test([{client_opts, CMiscOpt0}, {server_opts, SMiscOpt0}]),
265    basic_test([{client_opts, CMiscOpt1}, {server_opts, SMiscOpt1}]).
266
267%%--------------------------------------------------------------------
268%%% Test configuring IPv4
269inet_option(Config) when is_list(Config) ->
270    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
271    UserDir = proplists:get_value(priv_dir, Config),
272
273    ClientOpts =  [{silently_accept_hosts, true},
274		   {user_dir, UserDir},
275		   {user_interaction, false}],
276    ServerOpts = [{system_dir, SystemDir},
277		  {user_dir, UserDir},
278		  {failfun, fun ssh_test_lib:failfun/2}],
279
280    basic_test([{client_opts, [{inet, inet} | ClientOpts]},
281		{server_opts, [{inet, inet} | ServerOpts]}]).
282
283%%--------------------------------------------------------------------
284%%% Test configuring IPv6
285inet6_option() -> [{timetrap,{seconds,30}}].
286inet6_option(Config) when is_list(Config) ->
287    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
288    UserDir = proplists:get_value(priv_dir, Config),
289
290    ClientOpts =  [{silently_accept_hosts, true},
291		   {user_dir, UserDir},
292		   {user_interaction, false}],
293    ServerOpts = [{system_dir, SystemDir},
294		  {user_dir, UserDir},
295		  {failfun, fun ssh_test_lib:failfun/2}],
296
297    basic_test([{client_opts, [{inet, inet6} | ClientOpts]},
298		{server_opts, [{inet, inet6} | ServerOpts]}]).
299
300%%--------------------------------------------------------------------
301%%% Test api function ssh_connection:exec
302exec(Config) when is_list(Config) ->
303    process_flag(trap_exit, true),
304    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
305    UserDir = proplists:get_value(priv_dir, Config),
306
307    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
308					     {user_dir, UserDir},
309					     {failfun, fun ssh_test_lib:failfun/2}]),
310    ConnectionRef =
311	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
312					  {user_dir, UserDir},
313					  {user_interaction, false}]),
314    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
315    success = ssh_connection:exec(ConnectionRef, ChannelId0,
316				  "1+1.", infinity),
317    Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"2">>}},
318    case ssh_test_lib:receive_exec_result(Data0) of
319	expected ->
320	    ok;
321	Other0 ->
322	    ct:fail(Other0)
323    end,
324    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
325
326    %% Test that it is possible to start a new channel and
327    %% run an other exec on the same connection.
328    {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
329    success = ssh_connection:exec(ConnectionRef, ChannelId1,
330				  "2+2.", infinity),
331    Data1 = {ssh_cm, ConnectionRef, {data, ChannelId1, 0, <<"4">>}},
332    case ssh_test_lib:receive_exec_result(Data1) of
333	expected ->
334	    ok;
335	Other1 ->
336	    ct:fail(Other1)
337    end,
338    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1),
339    ssh:close(ConnectionRef),
340    ssh:stop_daemon(Pid).
341
342%%--------------------------------------------------------------------
343%%% Test api function ssh_connection:exec with erlang server and the Command
344%%% makes io
345exec_with_io_out(Config) when is_list(Config) ->
346    process_flag(trap_exit, true),
347    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
348    UserDir = proplists:get_value(priv_dir, Config),
349
350    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
351					     {user_dir, UserDir},
352					     {failfun, fun ssh_test_lib:failfun/2}]),
353    ConnectionRef =
354	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
355					  {user_dir, UserDir},
356					  {user_interaction, false}]),
357    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
358    success = ssh_connection:exec(ConnectionRef, ChannelId0,
359				  "io:write(hej).", infinity),
360    case ssh_test_lib:receive_exec_result(
361           [{ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"hej">>}},
362            {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"ok">>}},
363            {ssh_cm, ConnectionRef, {eof, ChannelId0}},
364            {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}},
365            {ssh_cm, ConnectionRef, {closed, ChannelId0}}
366           ]) of
367        expected ->
368            ok;
369        Other0 ->
370	    ct:fail(Other0)
371    end,
372    ssh:close(ConnectionRef),
373    ssh:stop_daemon(Pid).
374
375exec_with_io_in(Config) when is_list(Config) ->
376    process_flag(trap_exit, true),
377    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
378    UserDir = proplists:get_value(priv_dir, Config),
379
380    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
381					     {user_dir, UserDir},
382					     {failfun, fun ssh_test_lib:failfun/2}]),
383    C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
384					  {user_dir, UserDir},
385					  {user_interaction, false}]),
386    {ok, Ch} = ssh_connection:session_channel(C, infinity),
387    ssh_connection:exec(C, Ch, "io:read('% ').", 1000),
388
389    ssh_test_lib:receive_exec_result_or_fail({ssh_cm, C, {data,Ch,0,<<"% ">>}}),
390    ok = ssh_connection:send(C, Ch, "hej.\n", 10000),
391
392    ssh_test_lib:receive_exec_result_or_fail({ssh_cm, C, {data,Ch,0,<<"{ok,hej}">>}}),
393    ssh_test_lib:receive_exec_end(C, Ch),
394    ssh:close(C),
395    ssh:stop_daemon(Pid).
396
397%%--------------------------------------------------------------------
398%%% Test that compression option works
399exec_compressed(Config) when is_list(Config) ->
400    case ssh_test_lib:ssh_supports(zlib, compression) of
401	false ->
402	    {skip, "zlib compression is not supported"};
403
404	true ->
405	    process_flag(trap_exit, true),
406	    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
407	    UserDir = proplists:get_value(priv_dir, Config),
408
409	    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
410						     {preferred_algorithms,[{compression, [zlib]}]},
411						     {failfun, fun ssh_test_lib:failfun/2}]),
412
413	    ConnectionRef =
414		ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
415						  {user_dir, UserDir},
416						  {user_interaction, false}]),
417	    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
418	    success = ssh_connection:exec(ConnectionRef, ChannelId,
419					  "1+1.", infinity),
420	    Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2">>}},
421	    case ssh_test_lib:receive_exec_result(Data) of
422		expected ->
423		    ok;
424		Other ->
425		    ct:fail(Other)
426	    end,
427	    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
428	    ssh:close(ConnectionRef),
429	    ssh:stop_daemon(Pid)
430    end.
431
432%%--------------------------------------------------------------------
433%%% Idle timeout test
434idle_time_client(Config) -> idle_time_common([], [{idle_time, 2000}], Config).
435
436idle_time_server(Config) -> idle_time_common([{idle_time, 2000}], [], Config).
437
438
439idle_time_common(DaemonExtraOpts, ClientExtraOpts, Config) ->
440    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
441    UserDir = proplists:get_value(priv_dir, Config),
442
443    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
444					     {user_dir, UserDir},
445					     {failfun, fun ssh_test_lib:failfun/2}
446                                             | DaemonExtraOpts
447                                            ]),
448    ConnectionRef =
449	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
450					  {user_dir, UserDir},
451					  {user_interaction, false}
452                                          | ClientExtraOpts
453                                         ]),
454    {ok, Id1} = ssh_sftp:start_channel(ConnectionRef),
455    {ok, Id2} = ssh_sftp:start_channel(ConnectionRef),
456    ssh_sftp:stop_channel(Id2),
457    timer:sleep(2500),
458    {ok, Id3} = ssh_sftp:start_channel(ConnectionRef),
459    ssh_sftp:stop_channel(Id1),
460    ssh_sftp:stop_channel(Id3),
461    timer:sleep(1000),
462    {ok, Id4} = ssh_sftp:start_channel(ConnectionRef),
463    timer:sleep(2500),
464    {ok, Id5} = ssh_sftp:start_channel(ConnectionRef),
465    ssh_sftp:stop_channel(Id4),
466    ssh_sftp:stop_channel(Id5),
467    receive
468    after 10000 ->
469	    {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000)
470    end,
471    ssh:stop_daemon(Pid).
472
473%%--------------------------------------------------------------------
474%%% Test that ssh:shell/2 works
475shell(Config) when is_list(Config) ->
476    process_flag(trap_exit, true),
477    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
478    UserDir = proplists:get_value(priv_dir, Config),
479
480    {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
481					       {failfun, fun ssh_test_lib:failfun/2}]),
482    ct:sleep(500),
483
484    IO = ssh_test_lib:start_io_server(),
485    Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]),
486    receive
487	{'EXIT', _, _} = Exit ->
488            ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
489	    ct:fail(no_ssh_connection);
490	ErlShellStart ->
491	    ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
492	    do_shell(IO, Shell)
493    after
494	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
495    end.
496
497%%--------------------------------------------------------------------
498%%% Test that ssh:shell/2 works when attaching to a open TCP-connection
499shell_socket(Config) when is_list(Config) ->
500    process_flag(trap_exit, true),
501    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
502    UserDir = proplists:get_value(priv_dir, Config),
503
504    {_Pid, Host0, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
505					       {failfun, fun ssh_test_lib:failfun/2}]),
506    Host = ssh_test_lib:mangle_connect_address(Host0),
507    ct:sleep(500),
508
509    %% First test with active mode:
510    {ok,ActiveSock} = gen_tcp:connect(Host,
511                                      Port,
512                                      [{active,true}]),
513    {error,not_passive_mode} = ssh:shell(ActiveSock),
514    ct:log("~p:~p active tcp socket failed ok", [?MODULE,?LINE]),
515    gen_tcp:close(ActiveSock),
516
517    %% Secondly, test with an UDP socket:
518    {ok,BadSock} = gen_udp:open(0),
519    {error,not_tcp_socket} = ssh:shell(BadSock),
520    ct:log("~p:~p udp socket failed ok", [?MODULE,?LINE]),
521    gen_udp:close(BadSock),
522
523    %% And finaly test with passive mode (which should work):
524    IO = ssh_test_lib:start_io_server(),
525    {ok,Sock} = gen_tcp:connect(Host, Port, [{active,false}]),
526    Shell = ssh_test_lib:start_shell(Sock, IO, [{user_dir,UserDir}]),
527    gen_tcp:controlling_process(Sock, Shell),
528    Shell ! start,
529
530    receive
531	{'EXIT', _, _} = Exit ->
532            ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
533	    ct:fail(no_ssh_connection);
534	ErlShellStart ->
535	    ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
536	    do_shell(IO, Shell)
537    after
538	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
539    end.
540
541%%--------------------------------------------------------------------
542%%% Test that ssh:shell/2 works when attaching to a open SSH-connection
543shell_ssh_conn(Config) when is_list(Config) ->
544    process_flag(trap_exit, true),
545    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
546    UserDir = proplists:get_value(priv_dir, Config),
547
548    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
549					       {failfun, fun ssh_test_lib:failfun/2}]),
550    ct:sleep(500),
551
552    IO = ssh_test_lib:start_io_server(),
553    C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
554                                      {user_dir, UserDir},
555                                      {user_interaction, false}]),
556    Shell = ssh_test_lib:start_shell(C, IO, undefined),
557    receive
558	{'EXIT', _, _} = Exit ->
559            ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
560	    ct:fail(no_ssh_connection);
561	ErlShellStart ->
562	    ct:log("Erlang shell start: ~p~n", [ErlShellStart]),
563	    do_shell(IO, Shell)
564    after
565	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
566    end.
567
568%%--------------------------------------------------------------------
569cli(Config) when is_list(Config) ->
570    process_flag(trap_exit, true),
571    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
572    UserDir = proplists:get_value(priv_dir, Config),
573
574    TmpDir = filename:join(proplists:get_value(priv_dir,Config), "tmp"),
575    ok = ssh_test_lib:del_dirs(TmpDir),
576    ok = file:make_dir(TmpDir),
577
578    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
579					       {password, "morot"},
580					       {ssh_cli, {ssh_test_cli, [cli,TmpDir]}},
581					       {subsystems, []},
582					       {failfun, fun ssh_test_lib:failfun/2}]),
583    ct:sleep(500),
584
585    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
586						      {user, "foo"},
587						      {password, "morot"},
588						      {user_interaction, false},
589						      {user_dir, UserDir}]),
590
591    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
592    ssh_connection:shell(ConnectionRef, ChannelId),
593    ssh_connection:send(ConnectionRef, ChannelId, <<"q">>),
594    receive
595	{ssh_cm, ConnectionRef,
596	 {data,0,0, <<"\r\nYou are accessing a dummy, type \"q\" to exit\r\n\n">>}} ->
597	    ssh_connection:send(ConnectionRef, ChannelId, <<"q">>)
598    after
599	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
600    end,
601
602    receive
603     	{ssh_cm, ConnectionRef,{closed, ChannelId}} ->
604     	    ok
605    after
606	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
607    end.
608
609%%-----------------------------------------------------------------------------
610%%% Test that SSH client receives exit-status 0 on successful command execution
611cli_exit_normal(Config) when is_list(Config) ->
612    process_flag(trap_exit, true),
613    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
614    UserDir = proplists:get_value(priv_dir, Config),
615
616    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
617                           {password, "morot"},
618                           {ssh_cli, {ssh_cli, [fun (_) -> spawn(fun () -> ok end) end]}},
619                           {subsystems, []},
620                           {failfun, fun ssh_test_lib:failfun/2}]),
621    ct:sleep(500),
622
623    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
624                              {user, "foo"},
625                              {password, "morot"},
626                              {user_interaction, false},
627                              {user_dir, UserDir}]),
628
629    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
630    ssh_connection:shell(ConnectionRef, ChannelId),
631    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId, _ExpectedExitStatus = 0).
632
633%%---------------------------------------------------------
634%%% Test that SSH client receives user provided exit-status
635cli_exit_status(Config) when is_list(Config) ->
636    process_flag(trap_exit, true),
637    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
638    UserDir = proplists:get_value(priv_dir, Config),
639    NonZeroExitStatus = 7,
640
641    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
642                           {password, "morot"},
643                           {ssh_cli, {ssh_cli, [fun (_) ->
644                                                        spawn(fun () -> exit({exit_status, NonZeroExitStatus}) end)
645                                                end]}},
646                           {subsystems, []},
647                           {failfun, fun ssh_test_lib:failfun/2}]),
648    ct:sleep(500),
649
650    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
651                              {user, "foo"},
652                              {password, "morot"},
653                              {user_interaction, false},
654                              {user_dir, UserDir}]),
655
656    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
657    ssh_connection:shell(ConnectionRef, ChannelId),
658    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId, NonZeroExitStatus).
659
660%%--------------------------------------------------------------------
661%%% Test that get correct error message if you try to start a daemon
662%%% on an adress that already runs a daemon see also seq10667
663daemon_already_started(Config) when is_list(Config) ->
664    SystemDir = proplists:get_value(data_dir, Config),
665    UserDir = proplists:get_value(priv_dir, Config),
666
667    {Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
668					      {user_dir, UserDir},
669					      {failfun, fun ssh_test_lib:failfun/2}]),
670    {error, eaddrinuse} = ssh_test_lib:daemon(Port, [{system_dir, SystemDir},
671						     {user_dir, UserDir},
672						     {failfun,
673						      fun ssh_test_lib:failfun/2}]),
674    ssh:stop_daemon(Pid).
675
676%%--------------------------------------------------------------------
677%%% Test that a failed daemon start does not leave the port open
678
679%%%%%%%%%%%%%%%%%%%%%% REWRITE! %%%%%%%%%%%%%%%%%%%%
680%%% 1) check that {error,_} is not  {error,eaddrinuse}
681%%% 2) instead of ssh_test_lib:daemon second time, use gen_tcp:listen
682
683daemon_error_closes_port(Config) ->
684    GoodSystemDir = proplists:get_value(data_dir, Config),
685    Port = inet_port(),
686    {error,_} = ssh_test_lib:daemon(Port, []), % No system dir
687    case ssh_test_lib:daemon(Port, [{system_dir, GoodSystemDir}]) of
688        {error,eaddrinuse} ->
689            {fail, "Port leakage"};
690        {error,Error} ->
691            ct:log("Strange error: ~p",[Error]),
692            {fail, "Strange error"};
693        {Pid, _Host, Port} ->
694            %% Ok
695            ssh:stop_daemon(Pid)
696    end.
697
698inet_port() ->
699    {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
700    {ok, Port} = inet:port(Socket),
701    gen_tcp:close(Socket),
702    Port.
703
704%%--------------------------------------------------------------------
705%%% check that known_hosts is updated correctly
706known_hosts(Config) when is_list(Config) ->
707    SystemDir = proplists:get_value(data_dir, Config),
708    PrivDir = proplists:get_value(priv_dir, Config),
709
710    {Pid, Host, Port} = ssh_test_lib:daemon([{user_dir, PrivDir},{system_dir, SystemDir},
711					     {failfun, fun ssh_test_lib:failfun/2}]),
712
713    KnownHosts = filename:join(PrivDir, "known_hosts"),
714    file:delete(KnownHosts),
715    {error, enoent} = file:read_file(KnownHosts),
716    ConnectionRef =
717	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
718					  {user_interaction, false},
719					  {silently_accept_hosts, true},
720                                          {save_accepted_host, true}
721                                         ]),
722    {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
723    ok = ssh:close(ConnectionRef),
724    {ok, Binary} = file:read_file(KnownHosts),
725    ct:log("known_hosts:~n~p",[Binary]),
726    Lines = string:tokens(binary_to_list(Binary), "\n"),
727    [Line] = Lines,
728    [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
729
730    {StoredHost,StoredPort} =
731        case HostAndIp of
732            "["++X -> [Hpart,":"++Pstr] = string:tokens(X, "]"),
733                      {Hpart,list_to_integer(Pstr)};
734            _ -> {HostAndIp,Port}
735        end,
736
737    true = ssh_test_lib:match_ip(StoredHost, Host) andalso (Port==StoredPort),
738    "ssh-" ++ _ = Alg,
739    NLines = length(binary:split(Binary, <<"\n">>, [global,trim_all])),
740    ct:log("NLines = ~p~n~p", [NLines,Binary]),
741    if
742        NLines>1 -> ct:fail("wrong num lines", []);
743        NLines<1 -> ct:fail("wrong num lines", []);
744        true -> ok
745    end,
746
747    _ConnectionRef2 =
748	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
749					  {user_interaction, false},
750					  {silently_accept_hosts, true},
751                                          {save_accepted_host, true}
752                                         ]),
753    {ok, Binary2} = file:read_file(KnownHosts),
754    case Binary of
755        Binary2 -> ok;
756        _ -> ct:log("2nd differ~n~p", [Binary2]),
757             ct:fail("wrong num lines", [])
758    end,
759
760    Binary3 = <<"localhost,",Binary/binary>>,
761    ok = file:write_file(KnownHosts, Binary3),
762     _ConnectionRef3 =
763	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
764					  {user_interaction, false},
765					  {silently_accept_hosts, true},
766                                          {save_accepted_host, true}
767                                         ]),
768    ct:log("New known_hosts:~n~p",[Binary3]),
769    {ok, Binary4} = file:read_file(KnownHosts),
770    case Binary3 of
771        Binary4 -> ok;
772        _ -> ct:log("2nd differ~n~p", [Binary4]),
773             ct:fail("wrong num lines", [])
774    end,
775
776
777    ssh:stop_daemon(Pid).
778
779%%--------------------------------------------------------------------
780ssh_file_is_host_key() -> [{timetrap,{seconds,240}}]. % Some machines are S L O W !
781ssh_file_is_host_key(Config) ->
782    Dir = ssh_test_lib:create_random_dir(Config),
783    ct:log("Dir = ~p", [Dir]),
784    KnownHosts = filename:join(Dir, "known_hosts"),
785
786    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
787                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
788    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
789                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
790                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
791                           190,175,232,37,97,128>>},
792    Key3 = {'RSAPublicKey',26565213557098441060571713941539431805641814292761836797158846333985276408616038302348064841541244792430014595960643885863857366044141899534486816837416587694213836843799730043696945690516841209754307951050689906601353687467659852190777927968674989320642319504162787468947018505175948989102544757855693228490011564030927714896252701919941617689227585365348356580525802093985552564228730275431222515673065363441446158870936027338182083252824862151536327733046243804704721201548991176621134884093279416695997338124856506800535228380202243308550318880784741179703553922258881924287662178348044420509921666661119986374777,
793            65537},
794
795    FileContents = <<"h11,h12,[h13]:*,h14 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
796                     "h21,[h22]:2345,h23 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
797                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
798                     "  \n",
799                     "\n",
800                     "h31 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSb+D77XKvkMDWGu05CD6gWlEXJ+exSvxmegU1pvicPds090qTK3HwSzV7Hg1YVEV6bUiO74Om9Da4EMQponiSeLfVlIkBY5Ko4am4HMNOPTi5Ac4zR1B36nPvyTJluHKOZiCE0ZkSjKYvLEua0Y4Gqd+4RS93Q6r31OO8ukEVM+gG7z0tvhVLkAo8G5QnGRPW0z11tkfEeyjJzhk8H+4lmNjJRK4m6z71P0ACAEBJCpYKpKY3+AjksWuEZnWLgfuk9aPI4q8tI/TO3lF1BmyTPj7/QTFMiWgL7lNM94oaRHTjZ1CdB0UAW1+TMABu155z5KxVUIzrMoVKGBmJPhh5"
801                   >>,
802    ok = file:write_file(KnownHosts, FileContents),
803
804    true = ssh_file:is_host_key(Key1, "h11",   22, 'ssh-ed25519', [{user_dir,Dir}]),
805    true = ssh_file:is_host_key(Key1, "h12",   22, 'ssh-ed25519', [{user_dir,Dir}]),
806    true = ssh_file:is_host_key(Key1, "h13", 1234, 'ssh-ed25519', [{user_dir,Dir}]),
807    true = ssh_file:is_host_key(Key1, "h13",   22, 'ssh-ed25519', [{user_dir,Dir}]),
808    true = ssh_file:is_host_key(Key1, "h14",   22, 'ssh-ed25519', [{user_dir,Dir}]),
809
810    true = ssh_file:is_host_key(Key1, ["h11","noh1"],        22, 'ssh-ed25519', [{user_dir,Dir}]),
811    true = ssh_file:is_host_key(Key1, ["noh1","h11"],        22, 'ssh-ed25519', [{user_dir,Dir}]),
812    true = ssh_file:is_host_key(Key1, ["noh1","h12","noh2"], 22, 'ssh-ed25519', [{user_dir,Dir}]),
813
814    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448', [{user_dir,Dir}]),
815    false= ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448', [{user_dir,Dir}]),
816    true = ssh_file:is_host_key(Key2, "h22", 2345, 'ssh-ed448', [{user_dir,Dir}]),
817    false= ssh_file:is_host_key(Key2, "h22", 1234, 'ssh-ed448', [{user_dir,Dir}]),
818    true = ssh_file:is_host_key(Key2, "h23",   22, 'ssh-ed448', [{user_dir,Dir}]),
819
820    false =  ssh_file:is_host_key(Key2, "h11", 22, 'ssh-ed448', [{user_dir,Dir}]),
821    false =  ssh_file:is_host_key(Key1, "h21", 22, 'ssh-ed25519', [{user_dir,Dir}]),
822
823    true = ssh_file:is_host_key(Key3, "h31",   22, 'ssh-rsa',     [{user_dir,Dir}]),
824    true = ssh_file:is_host_key(Key3, "h31",   22, 'rsa-sha2-256',[{user_dir,Dir}]),
825
826    ok.
827
828%%--------------------------------------------------------------------
829ssh_file_is_host_key_misc(Config) ->
830    Dir = ssh_test_lib:create_random_dir(Config),
831    ct:log("Dir = ~p", [Dir]),
832    KnownHosts = filename:join(Dir, "known_hosts"),
833
834    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
835                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
836    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
837                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
838                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
839                           190,175,232,37,97,128>>},
840
841    FileContents = <<"h11,h12,!h12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
842                     %% Key revoked later in file:
843                     "h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
844                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
845                     "@revoked h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
846                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
847                     "h21 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
848                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n"
849                   >>,
850    ok = file:write_file(KnownHosts, FileContents),
851
852    true = ssh_file:is_host_key(Key1, "h11",   22, 'ssh-ed25519', [{user_dir,Dir}]),
853    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448',   [{user_dir,Dir}]),
854
855    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448',   [{user_dir,Dir},
856                                                                   {key_cb_private,[{optimize,space}]}]),
857    %% Check revoked key:
858    {error,revoked_key} =
859        ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448',   [{user_dir,Dir}]),
860    {error,revoked_key} =
861        ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448',   [{user_dir,Dir},
862                                                                {key_cb_private,[{optimize,space}]}]),
863    %% Check key with "!" in pattern:
864    false= ssh_file:is_host_key(Key1, "h12",   22, 'ssh-ed25519', [{user_dir,Dir}]),
865
866    ok.
867
868%%--------------------------------------------------------------------
869ssh_file_is_auth_key(Config) ->
870    Dir = ssh_test_lib:create_random_dir(Config),
871    ct:log("Dir = ~p", [Dir]),
872    AuthKeys = filename:join(Dir, "authorized_keys"),
873
874    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
875                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
876    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
877                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
878                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
879                           190,175,232,37,97,128>>},
880
881    FileContents = <<" \n",
882                     "# A test file\n",
883                     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 foo@example.com\n",
884                     "no-X11-forwarding,pty ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
885                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= bar@example.com\n"
886                   >>,
887    ok = file:write_file(AuthKeys, FileContents),
888
889    true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir}]),
890    true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir}]),
891
892    true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
893    true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
894
895    ok.
896
897%%--------------------------------------------------------------------
898
899%%% Test that we can use keyes protected by pass phrases
900pass_phrase(Config) when is_list(Config) ->
901    process_flag(trap_exit, true),
902    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
903    UserDir = proplists:get_value(priv_dir, Config),
904    PhraseArg = proplists:get_value(pass_phrase, Config),
905
906    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
907					     {user_dir, UserDir},
908					     {failfun, fun ssh_test_lib:failfun/2}]),
909    ConnectionRef =
910	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
911					  PhraseArg,
912					  {user_dir, UserDir},
913					  {user_interaction, false}]),
914    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
915    ssh:stop_daemon(Pid).
916
917%%--------------------------------------------------------------------
918%%% Test that we can use key callback
919key_callback(Config) when is_list(Config) ->
920    process_flag(trap_exit, true),
921    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
922    UserDir = proplists:get_value(priv_dir, Config),
923    NoPubKeyDir = filename:join(UserDir, "nopubkey"),
924    file:make_dir(NoPubKeyDir),
925
926    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
927					     {user_dir, UserDir},
928					     {failfun, fun ssh_test_lib:failfun/2}]),
929
930    ConnectOpts = [{silently_accept_hosts, true},
931                   {user_dir, NoPubKeyDir},
932                   {user_interaction, false},
933                   {key_cb, ssh_key_cb}],
934
935    ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
936
937    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
938    ssh:stop_daemon(Pid).
939
940
941%%--------------------------------------------------------------------
942%%% Test that we can use key callback with callback options
943key_callback_options(Config) when is_list(Config) ->
944    process_flag(trap_exit, true),
945    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
946    UserDir = proplists:get_value(priv_dir, Config),
947
948    NoPubKeyDir = filename:join(UserDir, "nopubkey"),
949    file:make_dir(NoPubKeyDir),
950
951    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
952                                             {user_dir, UserDir},
953                                             {failfun, fun ssh_test_lib:failfun/2}]),
954
955    {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_rsa")),
956
957    ConnectOpts = [{silently_accept_hosts, true},
958                   {user_dir, NoPubKeyDir},
959                   {user_interaction, false},
960                   {key_cb, {ssh_key_cb_options, [{priv_key, PrivKey}]}}],
961
962    ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
963
964    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
965    ssh:stop_daemon(Pid).
966
967
968%%--------------------------------------------------------------------
969%%% Test that client does not hang if disconnects due to internal error
970internal_error(Config) when is_list(Config) ->
971    process_flag(trap_exit, true),
972    PrivDir = proplists:get_value(priv_dir, Config),
973    UserDir = proplists:get_value(priv_dir, Config),
974    SystemDir = filename:join(PrivDir, system),
975    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
976                                             {user_dir, UserDir},
977                                             {failfun, fun ssh_test_lib:failfun/2}]),
978
979    %% Now provoke an error in the following connect:
980    file:delete(filename:join(PrivDir, "system/ssh_host_rsa_key")),
981    file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
982    file:delete(filename:join(PrivDir, "system/ssh_host_ecdsa_key")),
983    file:delete(filename:join(PrivDir, "system/ssh_host_ed25519_key")),
984    file:delete(filename:join(PrivDir, "system/ssh_host_ed448_key")),
985
986    {error, Error} =
987        ssh:connect(Host, Port, [{silently_accept_hosts, true},
988                                 {save_accepted_host, false},
989                                 {user_dir, UserDir},
990                                 {user_interaction, false}]),
991    check_error(Error),
992    ssh:stop_daemon(Pid).
993
994%%--------------------------------------------------------------------
995%%% Test ssh_connection:send/3
996send(Config) when is_list(Config) ->
997    process_flag(trap_exit, true),
998    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
999    UserDir = proplists:get_value(priv_dir, Config),
1000    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1001                                             {preferred_algorithms, ssh_transport:supported_algorithms()},
1002					     {user_dir, UserDir},
1003					     {failfun, fun ssh_test_lib:failfun/2}]),
1004    ConnectionRef =
1005	ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()},
1006                                          {silently_accept_hosts, true},
1007					  {user_dir, UserDir},
1008					  {user_interaction, false}]),
1009    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1010    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
1011    ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
1012    ssh:stop_daemon(Pid).
1013
1014
1015%%--------------------------------------------------------------------
1016%%% Test ssh:connection_info([peername, sockname])
1017peername_sockname(Config) when is_list(Config) ->
1018    process_flag(trap_exit, true),
1019    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1020    UserDir = proplists:get_value(priv_dir, Config),
1021
1022    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1023					     {user_dir, UserDir},
1024					     {subsystems, [{"peername_sockname",
1025							    {ssh_peername_sockname_server, []}}
1026							  ]}
1027					    ]),
1028    ConnectionRef =
1029	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1030					  {user_dir, UserDir},
1031					  {user_interaction, false}]),
1032    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1033    success = ssh_connection:subsystem(ConnectionRef, ChannelId, "peername_sockname", infinity),
1034    [{peer, {_Name, {HostPeerClient,PortPeerClient} = ClientPeer}}] =
1035	ssh:connection_info(ConnectionRef, [peer]),
1036    [{sockname, {HostSockClient,PortSockClient} = ClientSock}] =
1037	ssh:connection_info(ConnectionRef, [sockname]),
1038    ct:log("Client: ~p ~p", [ClientPeer, ClientSock]),
1039    receive
1040	{ssh_cm, ConnectionRef, {data, ChannelId, _, Response}} ->
1041	    {PeerNameSrv,SockNameSrv} = binary_to_term(Response),
1042	    {HostPeerSrv,PortPeerSrv} = PeerNameSrv,
1043	    {HostSockSrv,PortSockSrv} = SockNameSrv,
1044	    ct:log("Server: ~p ~p", [PeerNameSrv, SockNameSrv]),
1045	    host_equal(HostPeerSrv, HostSockClient),
1046	    PortPeerSrv = PortSockClient,
1047	    host_equal(HostSockSrv, HostPeerClient),
1048	    PortSockSrv = PortPeerClient,
1049	    host_equal(HostSockSrv, Host),
1050	    PortSockSrv = Port
1051    after 10000 ->
1052	    ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1053    end.
1054
1055host_equal(H1, H2) ->
1056    not ordsets:is_disjoint(ips(H1), ips(H2)).
1057
1058ips(IP) when is_tuple(IP) -> ordsets:from_list([IP]);
1059ips(Name) when is_list(Name) ->
1060    {ok,#hostent{h_addr_list=IPs4}} = inet:gethostbyname(Name,inet),
1061    {ok,#hostent{h_addr_list=IPs6}} = inet:gethostbyname(Name,inet6),
1062    ordsets:from_list(IPs4++IPs6).
1063
1064%%--------------------------------------------------------------------
1065
1066%%% Client receives close when server closes
1067close(Config) when is_list(Config) ->
1068    process_flag(trap_exit, true),
1069    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1070    UserDir = proplists:get_value(priv_dir, Config),
1071
1072    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1073					     {user_dir, UserDir},
1074					     {failfun, fun ssh_test_lib:failfun/2}]),
1075    Client =
1076	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1077					  {user_dir, UserDir},
1078					  {user_interaction, false}]),
1079    {ok, ChannelId} = ssh_connection:session_channel(Client, infinity),
1080
1081    ssh:stop_daemon(Server),
1082    receive
1083	{ssh_cm, Client,{closed, ChannelId}} ->
1084	    ok
1085    after 5000 ->
1086	    ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1087    end.
1088
1089%%--------------------------------------------------------------------
1090%%% Simulate that we try to close an already closed connection
1091double_close(Config) when is_list(Config) ->
1092    SystemDir = proplists:get_value(data_dir, Config),
1093    PrivDir = proplists:get_value(priv_dir, Config),
1094    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1095    file:make_dir(UserDir),
1096
1097    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1098					     {user_dir, UserDir},
1099					     {user_passwords, [{"vego", "morot"}]},
1100					     {failfun, fun ssh_test_lib:failfun/2}]),
1101    CM = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1102					   {user_dir, UserDir},
1103					    {user, "vego"},
1104					    {password, "morot"},
1105					    {user_interaction, false}]),
1106
1107    exit(CM, {shutdown, normal}),
1108    ok = ssh:close(CM).
1109
1110%%--------------------------------------------------------------------
1111daemon_opt_fd(Config) ->
1112    SystemDir = proplists:get_value(data_dir, Config),
1113    PrivDir = proplists:get_value(priv_dir, Config),
1114    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1115    file:make_dir(UserDir),
1116
1117    {ok,S1} = gen_tcp:listen(0,[]),
1118    ct:log("Socket S1 = ~p", [S1]),
1119
1120    {ok,Fd1} = prim_inet:getfd(S1),
1121
1122    {ok,Pid1} = ssh:daemon(0, [{system_dir, SystemDir},
1123			       {fd,Fd1},
1124			       {user_dir, UserDir},
1125			       {user_passwords, [{"vego", "morot"}]},
1126			       {failfun, fun ssh_test_lib:failfun/2}]),
1127
1128    {ok,{_Host1,Port1}} = inet:sockname(S1),
1129    C1 = ssh_test_lib:connect(Port1, [{silently_accept_hosts, true},
1130					  {user_dir, UserDir},
1131					  {user, "vego"},
1132					  {password, "morot"},
1133					  {user_interaction, false}]),
1134    exit(C1, {shutdown, normal}),
1135    ssh:stop_daemon(Pid1),
1136    gen_tcp:close(S1).
1137
1138
1139%%--------------------------------------------------------------------
1140multi_daemon_opt_fd(Config) ->
1141    SystemDir = proplists:get_value(data_dir, Config),
1142    PrivDir = proplists:get_value(priv_dir, Config),
1143    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1144    file:make_dir(UserDir),
1145
1146    Test =
1147	fun() ->
1148		{ok,S} = gen_tcp:listen(0,[]),
1149                ct:log("Socket S = ~p", [S]),
1150		{ok,Fd} = prim_inet:getfd(S),
1151
1152		{ok,Pid} = ssh:daemon(0, [{system_dir, SystemDir},
1153					  {fd,Fd},
1154					  {user_dir, UserDir},
1155					  {user_passwords, [{"vego", "morot"}]},
1156					  {failfun, fun ssh_test_lib:failfun/2}]),
1157
1158		{ok,{_Host,Port}} = inet:sockname(S),
1159		C = ssh_test_lib:connect(Port, [{silently_accept_hosts, true},
1160							  {user_dir, UserDir},
1161							  {user, "vego"},
1162							  {password, "morot"},
1163							  {user_interaction, false}]),
1164		{S,Pid,C}
1165	end,
1166
1167    Tests = [Test(),Test(),Test(),Test(),Test(),Test()],
1168
1169    [begin
1170	 gen_tcp:close(S),
1171	 ssh:stop_daemon(Pid),
1172	 exit(C, {shutdown, normal})
1173     end || {S,Pid,C} <- Tests].
1174
1175%%--------------------------------------------------------------------
1176packet_size(Config) ->
1177    SystemDir = proplists:get_value(data_dir, Config),
1178    PrivDir = proplists:get_value(priv_dir, Config),
1179    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1180    file:make_dir(UserDir),
1181
1182    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1183						{user_dir, UserDir},
1184						{user_passwords, [{"vego", "morot"}]}]),
1185    Conn =
1186	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1187					  {user_dir, UserDir},
1188					  {user_interaction, false},
1189					  {user, "vego"},
1190					  {password, "morot"}]),
1191    lists:foreach(
1192      fun(MaxPacketSize) ->
1193              ct:log("Try max_packet_size=~p",[MaxPacketSize]),
1194              {ok,Ch} = ssh_connection:session_channel(Conn, 1000, MaxPacketSize, 60000),
1195              ok = ssh_connection:shell(Conn, Ch),
1196              rec(Server, Conn, Ch, MaxPacketSize),
1197              ssh_connection:close(Conn, Ch)
1198      end, [0, 1, 10, 25]),
1199
1200    ssh:close(Conn),
1201    ssh:stop_daemon(Server),
1202    ok.
1203
1204
1205rec(Server, Conn, Ch, MaxSz) ->
1206    receive
1207        {ssh_cm,Conn,{data,Ch,_,M}} when size(M) =< MaxSz ->
1208            ct:log("~p: ~p",[MaxSz,M]),
1209            rec(Server, Conn, Ch, MaxSz);
1210        {ssh_cm,Conn,{data,Ch,_,_}} = M ->
1211            ct:log("Max pkt size=~p. Got ~p",[MaxSz,M]),
1212            ssh:close(Conn),
1213            ssh:stop_daemon(Server),
1214            ct:fail("Does not obey max_packet_size=~p",[MaxSz])
1215    after
1216        2000 ->
1217            ct:log("~p: ok!",[MaxSz]),
1218            ok
1219    end.
1220
1221%%--------------------------------------------------------------------
1222shell_no_unicode(Config) ->
1223    new_do_shell(proplists:get_value(io,Config),
1224		 [new_prompt,
1225		  {type,"io:format(\"hej ~p~n\",[42])."},
1226		  {expect,"hej 42"},
1227		  {expect,"ok"},
1228		  new_prompt,
1229		  {type,"exit()."}
1230		 ]).
1231
1232%%--------------------------------------------------------------------
1233shell_unicode_string(Config) ->
1234    new_do_shell(proplists:get_value(io,Config),
1235		 [new_prompt,
1236		  {type,"io:format(\"こにちわ~ts~n\",[\"四二\"])."},
1237		  {expect,"こにちわ四二"},
1238		  {expect,"ok"},
1239		  new_prompt,
1240		  {type,"exit()."}
1241		 ]).
1242
1243%%--------------------------------------------------------------------
1244%%% Test basic connection with openssh_zlib
1245openssh_zlib_basic_test(Config) ->
1246    case ssh_test_lib:ssh_supports(['zlib@openssh.com',none], compression) of
1247	{false,L} ->
1248	    {skip, io_lib:format("~p compression is not supported",[L])};
1249
1250	true ->
1251	    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1252	    UserDir = proplists:get_value(priv_dir, Config),
1253
1254	    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1255						     {user_dir, UserDir},
1256						     {preferred_algorithms,[{compression, ['zlib@openssh.com']}]},
1257						     {failfun, fun ssh_test_lib:failfun/2}]),
1258	    ConnectionRef =
1259		ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1260						  {user_dir, UserDir},
1261						  {user_interaction, false},
1262						  {preferred_algorithms,[{compression, ['zlib@openssh.com',
1263											none]}]}
1264						 ]),
1265	    ok = ssh:close(ConnectionRef),
1266	    ssh:stop_daemon(Pid)
1267    end.
1268
1269%%--------------------------------------------------------------------
1270ssh_info_print(Config) ->
1271    %% Just check that ssh_print:info() crashes
1272    PrivDir = proplists:get_value(priv_dir, Config),
1273    PrintFile = filename:join(PrivDir,info),
1274    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1275    file:make_dir(UserDir),
1276    SysDir = proplists:get_value(data_dir, Config),
1277
1278    Parent = self(),
1279    UnexpFun = fun(Msg,_Peer) ->
1280		       Parent ! {unexpected,Msg,self()},
1281		       skip
1282	       end,
1283    ConnFun = fun(_,_,_) -> Parent ! {connect,self()} end,
1284
1285    {DaemonRef, Host, Port} =
1286	ssh_test_lib:daemon([{system_dir, SysDir},
1287			     {user_dir, UserDir},
1288			     {password, "morot"},
1289			     {unexpectedfun, UnexpFun},
1290			     {connectfun, ConnFun},
1291			     {failfun, fun ssh_test_lib:failfun/2}]),
1292    ClientConnRef1 =
1293	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1294					  {user, "foo"},
1295					  {password, "morot"},
1296					  {user_dir, UserDir},
1297					  {unexpectedfun, UnexpFun},
1298					  {user_interaction, false}]),
1299    ClientConnRef2 =
1300	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1301					  {user, "foo"},
1302					  {password, "morot"},
1303					  {user_dir, UserDir},
1304					  {unexpectedfun, UnexpFun},
1305					  {user_interaction, false}]),
1306    receive
1307	{connect,DaemonConnRef} ->
1308	    ct:log("DaemonRef=~p, DaemonConnRef=~p, ClientConnRefs=~p",[DaemonRef, DaemonConnRef,
1309									[ClientConnRef1,ClientConnRef2]
1310								       ])
1311    after 2000 ->
1312	    ok
1313    end,
1314
1315    {ok,D} = file:open(PrintFile, write),
1316    ssh_info:print(D),
1317    ok = file:close(D),
1318
1319    {ok,Bin} = file:read_file(PrintFile),
1320    ct:log("~s",[Bin]),
1321
1322    receive
1323	{unexpected, Msg, Pid} ->
1324	    ct:log("~p got unexpected msg ~p",[Pid,Msg]),
1325	    ct:log("process_info(~p) = ~n~p",[Pid,process_info(Pid)]),
1326	    ok = ssh:close(ClientConnRef1),
1327	    ok = ssh:close(ClientConnRef2),
1328	    ok = ssh:stop_daemon(DaemonRef),
1329	    {fail,"unexpected msg"}
1330    after 1000 ->
1331	    ok = ssh:close(ClientConnRef1),
1332	    ok = ssh:close(ClientConnRef2),
1333	    ok = ssh:stop_daemon(DaemonRef)
1334    end.
1335
1336
1337%%--------------------------------------------------------------------
1338%% Check that a basd pwd is not tried more times. Could cause lock-out
1339%% on server
1340
1341login_bad_pwd_no_retry1(Config) ->
1342    login_bad_pwd_no_retry(Config, "keyboard-interactive,password").
1343
1344login_bad_pwd_no_retry2(Config) ->
1345    login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
1346
1347login_bad_pwd_no_retry3(Config) ->
1348    login_bad_pwd_no_retry(Config, "password,publickey,keyboard-interactive").
1349
1350login_bad_pwd_no_retry4(Config) ->
1351    login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
1352
1353login_bad_pwd_no_retry5(Config) ->
1354    login_bad_pwd_no_retry(Config, "password,keyboard-interactive,password,password").
1355
1356
1357login_bad_pwd_no_retry(Config, AuthMethods) ->
1358    PrivDir = proplists:get_value(priv_dir, Config),
1359    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1360    file:make_dir(UserDir),
1361    SysDir = proplists:get_value(data_dir, Config),
1362
1363    Parent = self(),
1364    PwdFun = fun(_, _, _, undefined) -> {false, 1};
1365		(_, _, _,         _) -> Parent ! retry_bad_pwd,
1366					false
1367	     end,
1368
1369    {DaemonRef, _Host, Port} =
1370	ssh_test_lib:daemon([{system_dir, SysDir},
1371			     {user_dir, UserDir},
1372			     {auth_methods, AuthMethods},
1373			     {user_passwords, [{"foo","somepwd"}]},
1374			     {pwdfun, PwdFun}
1375			    ]),
1376
1377    ConnRes = ssh:connect("localhost", Port,
1378			  [{silently_accept_hosts, true},
1379			   {user, "foo"},
1380			   {password, "badpwd"},
1381			   {user_dir, UserDir},
1382			   {user_interaction, false}]),
1383
1384    receive
1385	retry_bad_pwd ->
1386	    ssh:stop_daemon(DaemonRef),
1387	    {fail, "Retry bad password"}
1388    after 0 ->
1389	    case ConnRes of
1390		{error,"Unable to connect using the available authentication methods"} ->
1391		    ssh:stop_daemon(DaemonRef),
1392		    ok;
1393		{ok,Conn} ->
1394		    ssh:close(Conn),
1395		    ssh:stop_daemon(DaemonRef),
1396		    {fail, "Connect erroneosly succeded"}
1397	    end
1398    end.
1399
1400
1401%%----------------------------------------------------------------------------
1402%%% Test that when shell REPL exit with reason normal client receives status 0
1403shell_exit_status(Config) when is_list(Config) ->
1404    process_flag(trap_exit, true),
1405    SystemDir = proplists:get_value(data_dir, Config),
1406    UserDir = proplists:get_value(priv_dir, Config),
1407
1408    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1409                                             {user_dir, UserDir},
1410                                             {user_passwords, [{"vego", "morot"}]},
1411                                             {shell, {?MODULE,always_ok,[]}},
1412                                             {failfun, fun ssh_test_lib:failfun/2}]),
1413    ConnectionRef =
1414        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1415                                          {user_dir, UserDir},
1416                                          {user, "vego"},
1417                                          {password, "morot"},
1418                                          {user_interaction, false}]),
1419
1420    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1421    ok = ssh_connection:shell(ConnectionRef, ChannelId),
1422    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
1423    ssh:stop_daemon(Pid).
1424
1425always_ok(_) -> ok.
1426
1427%%----------------------------------------------------------------------------
1428setopts_getopts(Config) ->
1429    process_flag(trap_exit, true),
1430    SystemDir = proplists:get_value(data_dir, Config),
1431    UserDir = proplists:get_value(priv_dir, Config),
1432
1433    ShellFun = fun (_User, _Peer) -> spawn(fun() -> ok end) end,
1434    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1435                                             {user_dir, UserDir},
1436                                             {user_passwords, [{"vego", "morot"}]},
1437                                             {shell, ShellFun},
1438                                             {failfun, fun ssh_test_lib:failfun/2}]),
1439    ConnectionRef =
1440        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1441                                          {quiet_mode, true}, % Just to use quiet_mode once
1442                                          {user_dir, UserDir},
1443                                          {user, "vego"},
1444                                          {password, "morot"},
1445                                          {user_interaction, false}]),
1446    %% Test get_sock_opts
1447    {ok,[{active,once},{deliver,term},{mode,binary},{packet,0}]} =
1448        ssh:get_sock_opts(ConnectionRef, [active, deliver, mode, packet]),
1449
1450    %% Test to set forbidden opts
1451    {error,{not_allowed,[active,deliver,mode,packet]}} =
1452        ssh:set_sock_opts(ConnectionRef, [{active,once},{deliver,term},{mode,binary},{packet,0}]),
1453
1454    %% Test to set some other opt
1455    {ok,[{delay_send,DS0}]} =
1456        ssh:get_sock_opts(ConnectionRef, [delay_send]),
1457    DS1 = not DS0,
1458    ok = ssh:set_sock_opts(ConnectionRef, [{delay_send,DS1}]),
1459    {ok,[{delay_send,DS1}]} =
1460        ssh:get_sock_opts(ConnectionRef, [delay_send]),
1461
1462     ssh:stop_daemon(Pid).
1463
1464%%--------------------------------------------------------------------
1465%% Internal functions ------------------------------------------------
1466%%--------------------------------------------------------------------
1467%% Due to timing the error message may or may not be delivered to
1468%% the "tcp-application" before the socket closed message is recived
1469check_error("Invalid state") -> ok;
1470check_error("Connection closed") -> ok;
1471check_error("Selection of key exchange algorithm failed"++_) -> ok;
1472check_error("No host key available") -> ok;
1473check_error(Error) -> ct:fail(Error).
1474
1475basic_test(Config) ->
1476    ClientOpts = proplists:get_value(client_opts, Config),
1477    ServerOpts = proplists:get_value(server_opts, Config),
1478
1479    {Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts),
1480    CM = ssh_test_lib:connect(Host, Port, ClientOpts),
1481    ok = ssh:close(CM),
1482    ssh:stop_daemon(Pid).
1483
1484do_shell(IO, _Shell) ->
1485    new_do_shell(IO, [new_prompt,
1486                      {type,"1+1."},
1487                      {expect,"2"},
1488                      new_prompt,
1489                      {type,"exit()."}
1490                     ]).
1491
1492%%--------------------------------------------------------------------
1493wait_for_erlang_first_line(Config) ->
1494    receive
1495	{'EXIT', _, _} = Exit ->
1496            ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
1497	    {fail,no_ssh_connection};
1498	<<"Eshell ",_/binary>> = _ErlShellStart ->
1499	    ct:log("Erlang shell start: ~p~n", [_ErlShellStart]),
1500	    Config;
1501	Other ->
1502	    ct:log("Unexpected answer from ssh server: ~p",[Other]),
1503	    {fail,unexpected_answer}
1504    after 10000 ->
1505	    ct:log("No answer from ssh-server"),
1506	    {fail,timeout}
1507    end.
1508
1509
1510
1511new_do_shell(IO, List) -> new_do_shell(IO, 0, List).
1512
1513new_do_shell(IO, N, [new_prompt|More]) ->
1514    new_do_shell(IO, N+1, More);
1515
1516new_do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
1517    Pfx = prompt_prefix(),
1518    PfxSize = size(Pfx),
1519    receive
1520	_X = <<"\r\n">> ->
1521	    ct:log("Skip newline ~p",[_X]),
1522	    new_do_shell(IO, N, Ops);
1523
1524	<<P1,"> ">> when (P1-$0)==N ->
1525	    new_do_shell_prompt(IO, N, Order, Arg, More);
1526	<<"(",Pfx:PfxSize/binary,")",P1,"> ">> when (P1-$0)==N ->
1527	    new_do_shell_prompt(IO, N, Order, Arg, More);
1528	<<"('",Pfx:PfxSize/binary,"')",P1,"> ">> when (P1-$0)==N ->
1529	    new_do_shell_prompt(IO, N, Order, Arg, More);
1530
1531	<<P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1532	    new_do_shell_prompt(IO, N, Order, Arg, More);
1533	<<"(",Pfx:PfxSize/binary,")",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1534	    new_do_shell_prompt(IO, N, Order, Arg, More);
1535	<<"('",Pfx:PfxSize/binary,"')",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1536	    new_do_shell_prompt(IO, N, Order, Arg, More);
1537
1538	<<P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1539	    new_do_shell_prompt(IO, N, Order, Arg, More);
1540	<<"(",Pfx:PfxSize/binary,")",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1541	    new_do_shell_prompt(IO, N, Order, Arg, More);
1542	<<"('",Pfx:PfxSize/binary,"')",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1543	    new_do_shell_prompt(IO, N, Order, Arg, More);
1544
1545	Err when element(1,Err)==error ->
1546	    ct:fail("new_do_shell error: ~p~n",[Err]);
1547
1548	RecBin when Order==expect ; Order==expect_echo ->
1549	    ct:log("received ~p",[RecBin]),
1550	    RecStr = string:strip(unicode:characters_to_list(RecBin)),
1551	    ExpStr = string:strip(Arg),
1552	    case lists:prefix(ExpStr, RecStr) of
1553		true when Order==expect ->
1554		    ct:log("Matched ~ts",[RecStr]),
1555		    new_do_shell(IO, N, More);
1556		true when Order==expect_echo ->
1557		    ct:log("Matched echo ~ts",[RecStr]),
1558		    new_do_shell(IO, N, More);
1559		false ->
1560		    ct:fail("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr])
1561	    end
1562    after 30000 ->
1563	    ct:log("Message queue of ~p:~n~p",
1564		   [self(), erlang:process_info(self(), messages)]),
1565	    case Order of
1566		expect -> ct:fail("timeout, expected ~p",[string:strip(Arg)]);
1567		type ->  ct:fail("timeout, no prompt")
1568	    end
1569    end;
1570
1571new_do_shell(_, _, []) ->
1572    ok.
1573
1574prompt_prefix() ->
1575    case node() of
1576	nonode@nohost -> <<>>;
1577	Node -> list_to_binary(
1578                  atom_to_list(Node))
1579    end.
1580
1581
1582new_do_shell_prompt(IO, N, type, Str, More) ->
1583    ct:log("Matched prompt ~p to trigger sending of next line to server",[N]),
1584    IO ! {input, self(), Str++"\r\n"},
1585    ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
1586    new_do_shell(IO, N, More);
1587new_do_shell_prompt(IO, N, Op, Str, More) ->
1588    ct:log("Matched prompt ~p",[N]),
1589    new_do_shell(IO, N, [{Op,Str}|More]).
1590
1591