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    ?t:app_test(ssh),
246    ok.
247%%--------------------------------------------------------------------
248%%% Appup file consistency test.
249appup_test(Config) when is_list(Config) ->
250    ok = ?t: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    {ok,C} = ssh: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]),
720    {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
721    ok = ssh:close(ConnectionRef),
722    {ok, Binary} = file:read_file(KnownHosts),
723    ct:log("known_hosts:~n~p",[Binary]),
724    Lines = string:tokens(binary_to_list(Binary), "\n"),
725    [Line] = Lines,
726    [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
727
728    {StoredHost,StoredPort} =
729        case HostAndIp of
730            "["++X -> [Hpart,":"++Pstr] = string:tokens(X, "]"),
731                      {Hpart,list_to_integer(Pstr)};
732            _ -> {HostAndIp,Port}
733        end,
734
735    true = ssh_test_lib:match_ip(StoredHost, Host) andalso (Port==StoredPort),
736    "ssh-" ++ _ = Alg,
737    NLines = length(binary:split(Binary, <<"\n">>, [global,trim_all])),
738    ct:log("NLines = ~p~n~p", [NLines,Binary]),
739    if
740        NLines>1 -> ct:fail("wrong num lines", []);
741        NLines<1 -> ct:fail("wrong num lines", []);
742        true -> ok
743    end,
744
745    _ConnectionRef2 =
746	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
747					  {user_interaction, false},
748					  silently_accept_hosts]),
749    {ok, Binary2} = file:read_file(KnownHosts),
750    case Binary of
751        Binary2 -> ok;
752        _ -> ct:log("2nd differ~n~p", [Binary2]),
753             ct:fail("wrong num lines", [])
754    end,
755
756    Binary3 = <<"localhost,",Binary/binary>>,
757    ok = file:write_file(KnownHosts, Binary3),
758     _ConnectionRef3 =
759	ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
760					  {user_interaction, false},
761					  silently_accept_hosts]),
762    ct:log("New known_hosts:~n~p",[Binary3]),
763    {ok, Binary4} = file:read_file(KnownHosts),
764    case Binary3 of
765        Binary4 -> ok;
766        _ -> ct:log("2nd differ~n~p", [Binary4]),
767             ct:fail("wrong num lines", [])
768    end,
769
770
771    ssh:stop_daemon(Pid).
772
773%%--------------------------------------------------------------------
774ssh_file_is_host_key() -> [{timetrap,{seconds,240}}]. % Some machines are S L O W !
775ssh_file_is_host_key(Config) ->
776    Dir = ssh_test_lib:create_random_dir(Config),
777    ct:log("Dir = ~p", [Dir]),
778    KnownHosts = filename:join(Dir, "known_hosts"),
779
780    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
781                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
782    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
783                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
784                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
785                           190,175,232,37,97,128>>},
786    Key3 = {'RSAPublicKey',26565213557098441060571713941539431805641814292761836797158846333985276408616038302348064841541244792430014595960643885863857366044141899534486816837416587694213836843799730043696945690516841209754307951050689906601353687467659852190777927968674989320642319504162787468947018505175948989102544757855693228490011564030927714896252701919941617689227585365348356580525802093985552564228730275431222515673065363441446158870936027338182083252824862151536327733046243804704721201548991176621134884093279416695997338124856506800535228380202243308550318880784741179703553922258881924287662178348044420509921666661119986374777,
787            65537},
788
789    FileContents = <<"h11,h12,[h13]:*,h14 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
790                     "h21,[h22]:2345,h23 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
791                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
792                     "  \n",
793                     "\n",
794                     "h31 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSb+D77XKvkMDWGu05CD6gWlEXJ+exSvxmegU1pvicPds090qTK3HwSzV7Hg1YVEV6bUiO74Om9Da4EMQponiSeLfVlIkBY5Ko4am4HMNOPTi5Ac4zR1B36nPvyTJluHKOZiCE0ZkSjKYvLEua0Y4Gqd+4RS93Q6r31OO8ukEVM+gG7z0tvhVLkAo8G5QnGRPW0z11tkfEeyjJzhk8H+4lmNjJRK4m6z71P0ACAEBJCpYKpKY3+AjksWuEZnWLgfuk9aPI4q8tI/TO3lF1BmyTPj7/QTFMiWgL7lNM94oaRHTjZ1CdB0UAW1+TMABu155z5KxVUIzrMoVKGBmJPhh5"
795                   >>,
796    ok = file:write_file(KnownHosts, FileContents),
797
798    true = ssh_file:is_host_key(Key1, "h11",   22, 'ssh-ed25519', [{user_dir,Dir}]),
799    true = ssh_file:is_host_key(Key1, "h12",   22, 'ssh-ed25519', [{user_dir,Dir}]),
800    true = ssh_file:is_host_key(Key1, "h13", 1234, 'ssh-ed25519', [{user_dir,Dir}]),
801    true = ssh_file:is_host_key(Key1, "h13",   22, 'ssh-ed25519', [{user_dir,Dir}]),
802    true = ssh_file:is_host_key(Key1, "h14",   22, 'ssh-ed25519', [{user_dir,Dir}]),
803
804    true = ssh_file:is_host_key(Key1, ["h11","noh1"],        22, 'ssh-ed25519', [{user_dir,Dir}]),
805    true = ssh_file:is_host_key(Key1, ["noh1","h11"],        22, 'ssh-ed25519', [{user_dir,Dir}]),
806    true = ssh_file:is_host_key(Key1, ["noh1","h12","noh2"], 22, 'ssh-ed25519', [{user_dir,Dir}]),
807
808    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448', [{user_dir,Dir}]),
809    false= ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448', [{user_dir,Dir}]),
810    true = ssh_file:is_host_key(Key2, "h22", 2345, 'ssh-ed448', [{user_dir,Dir}]),
811    false= ssh_file:is_host_key(Key2, "h22", 1234, 'ssh-ed448', [{user_dir,Dir}]),
812    true = ssh_file:is_host_key(Key2, "h23",   22, 'ssh-ed448', [{user_dir,Dir}]),
813
814    false =  ssh_file:is_host_key(Key2, "h11", 22, 'ssh-ed448', [{user_dir,Dir}]),
815    false =  ssh_file:is_host_key(Key1, "h21", 22, 'ssh-ed25519', [{user_dir,Dir}]),
816
817    true = ssh_file:is_host_key(Key3, "h31",   22, 'ssh-rsa',     [{user_dir,Dir}]),
818    true = ssh_file:is_host_key(Key3, "h31",   22, 'rsa-sha2-256',[{user_dir,Dir}]),
819
820    ok.
821
822%%--------------------------------------------------------------------
823ssh_file_is_host_key_misc(Config) ->
824    Dir = ssh_test_lib:create_random_dir(Config),
825    ct:log("Dir = ~p", [Dir]),
826    KnownHosts = filename:join(Dir, "known_hosts"),
827
828    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
829                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
830    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
831                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
832                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
833                           190,175,232,37,97,128>>},
834
835    FileContents = <<"h11,h12,!h12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
836                     %% Key revoked later in file:
837                     "h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
838                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
839                     "@revoked h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
840                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n",
841                     "h21 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
842                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA=\n"
843                   >>,
844    ok = file:write_file(KnownHosts, FileContents),
845
846    true = ssh_file:is_host_key(Key1, "h11",   22, 'ssh-ed25519', [{user_dir,Dir}]),
847    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448',   [{user_dir,Dir}]),
848
849    true = ssh_file:is_host_key(Key2, "h21",   22, 'ssh-ed448',   [{user_dir,Dir},
850                                                                   {key_cb_private,[{optimize,space}]}]),
851    %% Check revoked key:
852    {error,revoked_key} =
853        ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448',   [{user_dir,Dir}]),
854    {error,revoked_key} =
855        ssh_file:is_host_key(Key2, "h22",   22, 'ssh-ed448',   [{user_dir,Dir},
856                                                                {key_cb_private,[{optimize,space}]}]),
857    %% Check key with "!" in pattern:
858    false= ssh_file:is_host_key(Key1, "h12",   22, 'ssh-ed25519', [{user_dir,Dir}]),
859
860    ok.
861
862%%--------------------------------------------------------------------
863ssh_file_is_auth_key(Config) ->
864    Dir = ssh_test_lib:create_random_dir(Config),
865    ct:log("Dir = ~p", [Dir]),
866    AuthKeys = filename:join(Dir, "authorized_keys"),
867
868    Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
869                             214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
870    Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
871                           161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
872                           36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
873                           190,175,232,37,97,128>>},
874
875    FileContents = <<" \n",
876                     "# A test file\n",
877                     "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 foo@example.com\n",
878                     "no-X11-forwarding,pty ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
879                                                             "+KDqddgOlneO3xJHabRscGG9Z4PfHlD2zR+hq+r+glYYA= bar@example.com\n"
880                   >>,
881    ok = file:write_file(AuthKeys, FileContents),
882
883    true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir}]),
884    true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir}]),
885
886    true = ssh_file:is_auth_key(Key1, "donald_duck", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
887    true = ssh_file:is_auth_key(Key2, "mickey_mouse", [{user_dir,Dir},{key_cb_private,[{optimize,space}]}]),
888
889    ok.
890
891%%--------------------------------------------------------------------
892
893%%% Test that we can use keyes protected by pass phrases
894pass_phrase(Config) when is_list(Config) ->
895    process_flag(trap_exit, true),
896    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
897    UserDir = proplists:get_value(priv_dir, Config),
898    PhraseArg = proplists:get_value(pass_phrase, Config),
899
900    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
901					     {user_dir, UserDir},
902					     {failfun, fun ssh_test_lib:failfun/2}]),
903    ConnectionRef =
904	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
905					  PhraseArg,
906					  {user_dir, UserDir},
907					  {user_interaction, false}]),
908    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
909    ssh:stop_daemon(Pid).
910
911%%--------------------------------------------------------------------
912%%% Test that we can use key callback
913key_callback(Config) when is_list(Config) ->
914    process_flag(trap_exit, true),
915    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
916    UserDir = proplists:get_value(priv_dir, Config),
917    NoPubKeyDir = filename:join(UserDir, "nopubkey"),
918    file:make_dir(NoPubKeyDir),
919
920    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
921					     {user_dir, UserDir},
922					     {failfun, fun ssh_test_lib:failfun/2}]),
923
924    ConnectOpts = [{silently_accept_hosts, true},
925                   {user_dir, NoPubKeyDir},
926                   {user_interaction, false},
927                   {key_cb, ssh_key_cb}],
928
929    ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
930
931    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
932    ssh:stop_daemon(Pid).
933
934
935%%--------------------------------------------------------------------
936%%% Test that we can use key callback with callback options
937key_callback_options(Config) when is_list(Config) ->
938    process_flag(trap_exit, true),
939    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
940    UserDir = proplists:get_value(priv_dir, Config),
941
942    NoPubKeyDir = filename:join(UserDir, "nopubkey"),
943    file:make_dir(NoPubKeyDir),
944
945    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
946                                             {user_dir, UserDir},
947                                             {failfun, fun ssh_test_lib:failfun/2}]),
948
949    {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_rsa")),
950
951    ConnectOpts = [{silently_accept_hosts, true},
952                   {user_dir, NoPubKeyDir},
953                   {user_interaction, false},
954                   {key_cb, {ssh_key_cb_options, [{priv_key, PrivKey}]}}],
955
956    ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts),
957
958    {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
959    ssh:stop_daemon(Pid).
960
961
962%%--------------------------------------------------------------------
963%%% Test that client does not hang if disconnects due to internal error
964internal_error(Config) when is_list(Config) ->
965    process_flag(trap_exit, true),
966    PrivDir = proplists:get_value(priv_dir, Config),
967    UserDir = proplists:get_value(priv_dir, Config),
968    SystemDir = filename:join(PrivDir, system),
969    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
970                                             {user_dir, UserDir},
971                                             {failfun, fun ssh_test_lib:failfun/2}]),
972
973    %% Now provoke an error in the following connect:
974    file:delete(filename:join(PrivDir, "system/ssh_host_rsa_key")),
975    file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
976    file:delete(filename:join(PrivDir, "system/ssh_host_ecdsa_key")),
977    file:delete(filename:join(PrivDir, "system/ssh_host_ed25519_key")),
978    file:delete(filename:join(PrivDir, "system/ssh_host_ed448_key")),
979
980    {error, Error} =
981        ssh:connect(Host, Port, [{silently_accept_hosts, true},
982                                 {user_dir, UserDir},
983                                 {user_interaction, false}]),
984    check_error(Error),
985    ssh:stop_daemon(Pid).
986
987%%--------------------------------------------------------------------
988%%% Test ssh_connection:send/3
989send(Config) when is_list(Config) ->
990    process_flag(trap_exit, true),
991    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
992    UserDir = proplists:get_value(priv_dir, Config),
993    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
994                                             {preferred_algorithms, ssh_transport:supported_algorithms()},
995					     {user_dir, UserDir},
996					     {failfun, fun ssh_test_lib:failfun/2}]),
997    ConnectionRef =
998	ssh_test_lib:connect(Host, Port, [{preferred_algorithms, ssh_transport:supported_algorithms()},
999                                          {silently_accept_hosts, true},
1000					  {user_dir, UserDir},
1001					  {user_interaction, false}]),
1002    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1003    ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
1004    ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
1005    ssh:stop_daemon(Pid).
1006
1007
1008%%--------------------------------------------------------------------
1009%%% Test ssh:connection_info([peername, sockname])
1010peername_sockname(Config) when is_list(Config) ->
1011    process_flag(trap_exit, true),
1012    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1013    UserDir = proplists:get_value(priv_dir, Config),
1014
1015    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1016					     {user_dir, UserDir},
1017					     {subsystems, [{"peername_sockname",
1018							    {ssh_peername_sockname_server, []}}
1019							  ]}
1020					    ]),
1021    ConnectionRef =
1022	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1023					  {user_dir, UserDir},
1024					  {user_interaction, false}]),
1025    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1026    success = ssh_connection:subsystem(ConnectionRef, ChannelId, "peername_sockname", infinity),
1027    [{peer, {_Name, {HostPeerClient,PortPeerClient} = ClientPeer}}] =
1028	ssh:connection_info(ConnectionRef, [peer]),
1029    [{sockname, {HostSockClient,PortSockClient} = ClientSock}] =
1030	ssh:connection_info(ConnectionRef, [sockname]),
1031    ct:log("Client: ~p ~p", [ClientPeer, ClientSock]),
1032    receive
1033	{ssh_cm, ConnectionRef, {data, ChannelId, _, Response}} ->
1034	    {PeerNameSrv,SockNameSrv} = binary_to_term(Response),
1035	    {HostPeerSrv,PortPeerSrv} = PeerNameSrv,
1036	    {HostSockSrv,PortSockSrv} = SockNameSrv,
1037	    ct:log("Server: ~p ~p", [PeerNameSrv, SockNameSrv]),
1038	    host_equal(HostPeerSrv, HostSockClient),
1039	    PortPeerSrv = PortSockClient,
1040	    host_equal(HostSockSrv, HostPeerClient),
1041	    PortSockSrv = PortPeerClient,
1042	    host_equal(HostSockSrv, Host),
1043	    PortSockSrv = Port
1044    after 10000 ->
1045	    ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1046    end.
1047
1048host_equal(H1, H2) ->
1049    not ordsets:is_disjoint(ips(H1), ips(H2)).
1050
1051ips(IP) when is_tuple(IP) -> ordsets:from_list([IP]);
1052ips(Name) when is_list(Name) ->
1053    {ok,#hostent{h_addr_list=IPs4}} = inet:gethostbyname(Name,inet),
1054    {ok,#hostent{h_addr_list=IPs6}} = inet:gethostbyname(Name,inet6),
1055    ordsets:from_list(IPs4++IPs6).
1056
1057%%--------------------------------------------------------------------
1058
1059%%% Client receives close when server closes
1060close(Config) when is_list(Config) ->
1061    process_flag(trap_exit, true),
1062    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1063    UserDir = proplists:get_value(priv_dir, Config),
1064
1065    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1066					     {user_dir, UserDir},
1067					     {failfun, fun ssh_test_lib:failfun/2}]),
1068    Client =
1069	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1070					  {user_dir, UserDir},
1071					  {user_interaction, false}]),
1072    {ok, ChannelId} = ssh_connection:session_channel(Client, infinity),
1073
1074    ssh:stop_daemon(Server),
1075    receive
1076	{ssh_cm, Client,{closed, ChannelId}} ->
1077	    ok
1078    after 5000 ->
1079	    ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1080    end.
1081
1082%%--------------------------------------------------------------------
1083%%% Simulate that we try to close an already closed connection
1084double_close(Config) when is_list(Config) ->
1085    SystemDir = proplists:get_value(data_dir, Config),
1086    PrivDir = proplists:get_value(priv_dir, Config),
1087    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1088    file:make_dir(UserDir),
1089
1090    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1091					     {user_dir, UserDir},
1092					     {user_passwords, [{"vego", "morot"}]},
1093					     {failfun, fun ssh_test_lib:failfun/2}]),
1094    {ok, CM} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
1095					   {user_dir, UserDir},
1096					    {user, "vego"},
1097					    {password, "morot"},
1098					    {user_interaction, false}]),
1099
1100    exit(CM, {shutdown, normal}),
1101    ok = ssh:close(CM).
1102
1103%%--------------------------------------------------------------------
1104daemon_opt_fd(Config) ->
1105    SystemDir = proplists:get_value(data_dir, Config),
1106    PrivDir = proplists:get_value(priv_dir, Config),
1107    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1108    file:make_dir(UserDir),
1109
1110    {ok,S1} = gen_tcp:listen(0,[]),
1111    {ok,Fd1} = prim_inet:getfd(S1),
1112
1113    {ok,Pid1} = ssh:daemon(0, [{system_dir, SystemDir},
1114			       {fd,Fd1},
1115			       {user_dir, UserDir},
1116			       {user_passwords, [{"vego", "morot"}]},
1117			       {failfun, fun ssh_test_lib:failfun/2}]),
1118
1119    {ok,{_Host1,Port1}} = inet:sockname(S1),
1120    {ok, C1} = ssh:connect("localhost", Port1, [{silently_accept_hosts, true},
1121					  {user_dir, UserDir},
1122					  {user, "vego"},
1123					  {password, "morot"},
1124					  {user_interaction, false}]),
1125    exit(C1, {shutdown, normal}),
1126    ssh:stop_daemon(Pid1),
1127    gen_tcp:close(S1).
1128
1129
1130%%--------------------------------------------------------------------
1131multi_daemon_opt_fd(Config) ->
1132    SystemDir = proplists:get_value(data_dir, Config),
1133    PrivDir = proplists:get_value(priv_dir, Config),
1134    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1135    file:make_dir(UserDir),
1136
1137    Test =
1138	fun() ->
1139		{ok,S} = gen_tcp:listen(0,[]),
1140		{ok,Fd} = prim_inet:getfd(S),
1141
1142		{ok,Pid} = ssh:daemon(0, [{system_dir, SystemDir},
1143					  {fd,Fd},
1144					  {user_dir, UserDir},
1145					  {user_passwords, [{"vego", "morot"}]},
1146					  {failfun, fun ssh_test_lib:failfun/2}]),
1147
1148		{ok,{_Host,Port}} = inet:sockname(S),
1149		{ok, C} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
1150							  {user_dir, UserDir},
1151							  {user, "vego"},
1152							  {password, "morot"},
1153							  {user_interaction, false}]),
1154		{S,Pid,C}
1155	end,
1156
1157    Tests = [Test(),Test(),Test(),Test(),Test(),Test()],
1158
1159    [begin
1160	 gen_tcp:close(S),
1161	 ssh:stop_daemon(Pid),
1162	 exit(C, {shutdown, normal})
1163     end || {S,Pid,C} <- Tests].
1164
1165%%--------------------------------------------------------------------
1166packet_size(Config) ->
1167    SystemDir = proplists:get_value(data_dir, Config),
1168    PrivDir = proplists:get_value(priv_dir, Config),
1169    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1170    file:make_dir(UserDir),
1171
1172    {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1173						{user_dir, UserDir},
1174						{user_passwords, [{"vego", "morot"}]}]),
1175    Conn =
1176	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1177					  {user_dir, UserDir},
1178					  {user_interaction, false},
1179					  {user, "vego"},
1180					  {password, "morot"}]),
1181    lists:foreach(
1182      fun(MaxPacketSize) ->
1183              ct:log("Try max_packet_size=~p",[MaxPacketSize]),
1184              {ok,Ch} = ssh_connection:session_channel(Conn, 1000, MaxPacketSize, 60000),
1185              ok = ssh_connection:shell(Conn, Ch),
1186              rec(Server, Conn, Ch, MaxPacketSize),
1187              ssh_connection:close(Conn, Ch)
1188      end, [0, 1, 10, 25]),
1189
1190    ssh:close(Conn),
1191    ssh:stop_daemon(Server),
1192    ok.
1193
1194
1195rec(Server, Conn, Ch, MaxSz) ->
1196    receive
1197        {ssh_cm,Conn,{data,Ch,_,M}} when size(M) =< MaxSz ->
1198            ct:log("~p: ~p",[MaxSz,M]),
1199            rec(Server, Conn, Ch, MaxSz);
1200        {ssh_cm,Conn,{data,Ch,_,_}} = M ->
1201            ct:log("Max pkt size=~p. Got ~p",[MaxSz,M]),
1202            ssh:close(Conn),
1203            ssh:stop_daemon(Server),
1204            ct:fail("Does not obey max_packet_size=~p",[MaxSz])
1205    after
1206        2000 ->
1207            ct:log("~p: ok!",[MaxSz]),
1208            ok
1209    end.
1210
1211%%--------------------------------------------------------------------
1212shell_no_unicode(Config) ->
1213    new_do_shell(proplists:get_value(io,Config),
1214		 [new_prompt,
1215		  {type,"io:format(\"hej ~p~n\",[42])."},
1216		  {expect,"hej 42"},
1217		  {expect,"ok"},
1218		  new_prompt,
1219		  {type,"exit()."}
1220		 ]).
1221
1222%%--------------------------------------------------------------------
1223shell_unicode_string(Config) ->
1224    new_do_shell(proplists:get_value(io,Config),
1225		 [new_prompt,
1226		  {type,"io:format(\"こにちわ~ts~n\",[\"四二\"])."},
1227		  {expect,"こにちわ四二"},
1228		  {expect,"ok"},
1229		  new_prompt,
1230		  {type,"exit()."}
1231		 ]).
1232
1233%%--------------------------------------------------------------------
1234%%% Test basic connection with openssh_zlib
1235openssh_zlib_basic_test(Config) ->
1236    case ssh_test_lib:ssh_supports(['zlib@openssh.com',none], compression) of
1237	{false,L} ->
1238	    {skip, io_lib:format("~p compression is not supported",[L])};
1239
1240	true ->
1241	    SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
1242	    UserDir = proplists:get_value(priv_dir, Config),
1243
1244	    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1245						     {user_dir, UserDir},
1246						     {preferred_algorithms,[{compression, ['zlib@openssh.com']}]},
1247						     {failfun, fun ssh_test_lib:failfun/2}]),
1248	    ConnectionRef =
1249		ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1250						  {user_dir, UserDir},
1251						  {user_interaction, false},
1252						  {preferred_algorithms,[{compression, ['zlib@openssh.com',
1253											none]}]}
1254						 ]),
1255	    ok = ssh:close(ConnectionRef),
1256	    ssh:stop_daemon(Pid)
1257    end.
1258
1259%%--------------------------------------------------------------------
1260ssh_info_print(Config) ->
1261    %% Just check that ssh_print:info() crashes
1262    PrivDir = proplists:get_value(priv_dir, Config),
1263    PrintFile = filename:join(PrivDir,info),
1264    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1265    file:make_dir(UserDir),
1266    SysDir = proplists:get_value(data_dir, Config),
1267
1268    Parent = self(),
1269    UnexpFun = fun(Msg,_Peer) ->
1270		       Parent ! {unexpected,Msg,self()},
1271		       skip
1272	       end,
1273    ConnFun = fun(_,_,_) -> Parent ! {connect,self()} end,
1274
1275    {DaemonRef, Host, Port} =
1276	ssh_test_lib:daemon([{system_dir, SysDir},
1277			     {user_dir, UserDir},
1278			     {password, "morot"},
1279			     {unexpectedfun, UnexpFun},
1280			     {connectfun, ConnFun},
1281			     {failfun, fun ssh_test_lib:failfun/2}]),
1282    ClientConnRef1 =
1283	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1284					  {user, "foo"},
1285					  {password, "morot"},
1286					  {user_dir, UserDir},
1287					  {unexpectedfun, UnexpFun},
1288					  {user_interaction, false}]),
1289    ClientConnRef2 =
1290	ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1291					  {user, "foo"},
1292					  {password, "morot"},
1293					  {user_dir, UserDir},
1294					  {unexpectedfun, UnexpFun},
1295					  {user_interaction, false}]),
1296    receive
1297	{connect,DaemonConnRef} ->
1298	    ct:log("DaemonRef=~p, DaemonConnRef=~p, ClientConnRefs=~p",[DaemonRef, DaemonConnRef,
1299									[ClientConnRef1,ClientConnRef2]
1300								       ])
1301    after 2000 ->
1302	    ok
1303    end,
1304
1305    {ok,D} = file:open(PrintFile, write),
1306    ssh_info:print(D),
1307    ok = file:close(D),
1308
1309    {ok,Bin} = file:read_file(PrintFile),
1310    ct:log("~s",[Bin]),
1311
1312    receive
1313	{unexpected, Msg, Pid} ->
1314	    ct:log("~p got unexpected msg ~p",[Pid,Msg]),
1315	    ct:log("process_info(~p) = ~n~p",[Pid,process_info(Pid)]),
1316	    ok = ssh:close(ClientConnRef1),
1317	    ok = ssh:close(ClientConnRef2),
1318	    ok = ssh:stop_daemon(DaemonRef),
1319	    {fail,"unexpected msg"}
1320    after 1000 ->
1321	    ok = ssh:close(ClientConnRef1),
1322	    ok = ssh:close(ClientConnRef2),
1323	    ok = ssh:stop_daemon(DaemonRef)
1324    end.
1325
1326
1327%%--------------------------------------------------------------------
1328%% Check that a basd pwd is not tried more times. Could cause lock-out
1329%% on server
1330
1331login_bad_pwd_no_retry1(Config) ->
1332    login_bad_pwd_no_retry(Config, "keyboard-interactive,password").
1333
1334login_bad_pwd_no_retry2(Config) ->
1335    login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
1336
1337login_bad_pwd_no_retry3(Config) ->
1338    login_bad_pwd_no_retry(Config, "password,publickey,keyboard-interactive").
1339
1340login_bad_pwd_no_retry4(Config) ->
1341    login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
1342
1343login_bad_pwd_no_retry5(Config) ->
1344    login_bad_pwd_no_retry(Config, "password,keyboard-interactive,password,password").
1345
1346
1347login_bad_pwd_no_retry(Config, AuthMethods) ->
1348    PrivDir = proplists:get_value(priv_dir, Config),
1349    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1350    file:make_dir(UserDir),
1351    SysDir = proplists:get_value(data_dir, Config),
1352
1353    Parent = self(),
1354    PwdFun = fun(_, _, _, undefined) -> {false, 1};
1355		(_, _, _,         _) -> Parent ! retry_bad_pwd,
1356					false
1357	     end,
1358
1359    {DaemonRef, _Host, Port} =
1360	ssh_test_lib:daemon([{system_dir, SysDir},
1361			     {user_dir, UserDir},
1362			     {auth_methods, AuthMethods},
1363			     {user_passwords, [{"foo","somepwd"}]},
1364			     {pwdfun, PwdFun}
1365			    ]),
1366
1367    ConnRes = ssh:connect("localhost", Port,
1368			  [{silently_accept_hosts, true},
1369			   {user, "foo"},
1370			   {password, "badpwd"},
1371			   {user_dir, UserDir},
1372			   {user_interaction, false}]),
1373
1374    receive
1375	retry_bad_pwd ->
1376	    ssh:stop_daemon(DaemonRef),
1377	    {fail, "Retry bad password"}
1378    after 0 ->
1379	    case ConnRes of
1380		{error,"Unable to connect using the available authentication methods"} ->
1381		    ssh:stop_daemon(DaemonRef),
1382		    ok;
1383		{ok,Conn} ->
1384		    ssh:close(Conn),
1385		    ssh:stop_daemon(DaemonRef),
1386		    {fail, "Connect erroneosly succeded"}
1387	    end
1388    end.
1389
1390
1391%%----------------------------------------------------------------------------
1392%%% Test that when shell REPL exit with reason normal client receives status 0
1393shell_exit_status(Config) when is_list(Config) ->
1394    process_flag(trap_exit, true),
1395    SystemDir = proplists:get_value(data_dir, Config),
1396    UserDir = proplists:get_value(priv_dir, Config),
1397
1398    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1399                                             {user_dir, UserDir},
1400                                             {user_passwords, [{"vego", "morot"}]},
1401                                             {shell, {?MODULE,always_ok,[]}},
1402                                             {failfun, fun ssh_test_lib:failfun/2}]),
1403    ConnectionRef =
1404        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1405                                          {user_dir, UserDir},
1406                                          {user, "vego"},
1407                                          {password, "morot"},
1408                                          {user_interaction, false}]),
1409
1410    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1411    ok = ssh_connection:shell(ConnectionRef, ChannelId),
1412    ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
1413    ssh:stop_daemon(Pid).
1414
1415always_ok(_) -> ok.
1416
1417%%----------------------------------------------------------------------------
1418setopts_getopts(Config) ->
1419    process_flag(trap_exit, true),
1420    SystemDir = proplists:get_value(data_dir, Config),
1421    UserDir = proplists:get_value(priv_dir, Config),
1422
1423    ShellFun = fun (_User, _Peer) -> spawn(fun() -> ok end) end,
1424    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
1425                                             {user_dir, UserDir},
1426                                             {user_passwords, [{"vego", "morot"}]},
1427                                             {shell, ShellFun},
1428                                             {failfun, fun ssh_test_lib:failfun/2}]),
1429    ConnectionRef =
1430        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1431                                          {quiet_mode, true}, % Just to use quiet_mode once
1432                                          {user_dir, UserDir},
1433                                          {user, "vego"},
1434                                          {password, "morot"},
1435                                          {user_interaction, false}]),
1436    %% Test get_sock_opts
1437    {ok,[{active,once},{deliver,term},{mode,binary},{packet,0}]} =
1438        ssh:get_sock_opts(ConnectionRef, [active, deliver, mode, packet]),
1439
1440    %% Test to set forbidden opts
1441    {error,{not_allowed,[active,deliver,mode,packet]}} =
1442        ssh:set_sock_opts(ConnectionRef, [{active,once},{deliver,term},{mode,binary},{packet,0}]),
1443
1444    %% Test to set some other opt
1445    {ok,[{delay_send,DS0}]} =
1446        ssh:get_sock_opts(ConnectionRef, [delay_send]),
1447    DS1 = not DS0,
1448    ok = ssh:set_sock_opts(ConnectionRef, [{delay_send,DS1}]),
1449    {ok,[{delay_send,DS1}]} =
1450        ssh:get_sock_opts(ConnectionRef, [delay_send]),
1451
1452     ssh:stop_daemon(Pid).
1453
1454%%--------------------------------------------------------------------
1455%% Internal functions ------------------------------------------------
1456%%--------------------------------------------------------------------
1457%% Due to timing the error message may or may not be delivered to
1458%% the "tcp-application" before the socket closed message is recived
1459check_error("Invalid state") -> ok;
1460check_error("Connection closed") -> ok;
1461check_error("Selection of key exchange algorithm failed"++_) -> ok;
1462check_error("No host key available") -> ok;
1463check_error(Error) -> ct:fail(Error).
1464
1465basic_test(Config) ->
1466    ClientOpts = proplists:get_value(client_opts, Config),
1467    ServerOpts = proplists:get_value(server_opts, Config),
1468
1469    {Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts),
1470    {ok, CM} = ssh:connect(Host, Port, ClientOpts),
1471    ok = ssh:close(CM),
1472    ssh:stop_daemon(Pid).
1473
1474do_shell(IO, _Shell) ->
1475    new_do_shell(IO, [new_prompt,
1476                      {type,"1+1."},
1477                      {expect,"2"},
1478                      new_prompt,
1479                      {type,"exit()."}
1480                     ]).
1481
1482%%--------------------------------------------------------------------
1483wait_for_erlang_first_line(Config) ->
1484    receive
1485	{'EXIT', _, _} = Exit ->
1486            ct:log("~p:~p ~p", [?MODULE,?LINE,Exit]),
1487	    {fail,no_ssh_connection};
1488	<<"Eshell ",_/binary>> = _ErlShellStart ->
1489	    ct:log("Erlang shell start: ~p~n", [_ErlShellStart]),
1490	    Config;
1491	Other ->
1492	    ct:log("Unexpected answer from ssh server: ~p",[Other]),
1493	    {fail,unexpected_answer}
1494    after 10000 ->
1495	    ct:log("No answer from ssh-server"),
1496	    {fail,timeout}
1497    end.
1498
1499
1500
1501new_do_shell(IO, List) -> new_do_shell(IO, 0, List).
1502
1503new_do_shell(IO, N, [new_prompt|More]) ->
1504    new_do_shell(IO, N+1, More);
1505
1506new_do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
1507    Pfx = prompt_prefix(),
1508    PfxSize = size(Pfx),
1509    receive
1510	_X = <<"\r\n">> ->
1511	    ct:log("Skip newline ~p",[_X]),
1512	    new_do_shell(IO, N, Ops);
1513
1514	<<P1,"> ">> when (P1-$0)==N ->
1515	    new_do_shell_prompt(IO, N, Order, Arg, More);
1516	<<"(",Pfx:PfxSize/binary,")",P1,"> ">> when (P1-$0)==N ->
1517	    new_do_shell_prompt(IO, N, Order, Arg, More);
1518	<<"('",Pfx:PfxSize/binary,"')",P1,"> ">> when (P1-$0)==N ->
1519	    new_do_shell_prompt(IO, N, Order, Arg, More);
1520
1521	<<P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1522	    new_do_shell_prompt(IO, N, Order, Arg, More);
1523	<<"(",Pfx:PfxSize/binary,")",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1524	    new_do_shell_prompt(IO, N, Order, Arg, More);
1525	<<"('",Pfx:PfxSize/binary,"')",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
1526	    new_do_shell_prompt(IO, N, Order, Arg, More);
1527
1528	<<P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1529	    new_do_shell_prompt(IO, N, Order, Arg, More);
1530	<<"(",Pfx:PfxSize/binary,")",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1531	    new_do_shell_prompt(IO, N, Order, Arg, More);
1532	<<"('",Pfx:PfxSize/binary,"')",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
1533	    new_do_shell_prompt(IO, N, Order, Arg, More);
1534
1535	Err when element(1,Err)==error ->
1536	    ct:fail("new_do_shell error: ~p~n",[Err]);
1537
1538	RecBin when Order==expect ; Order==expect_echo ->
1539	    ct:log("received ~p",[RecBin]),
1540	    RecStr = string:strip(unicode:characters_to_list(RecBin)),
1541	    ExpStr = string:strip(Arg),
1542	    case lists:prefix(ExpStr, RecStr) of
1543		true when Order==expect ->
1544		    ct:log("Matched ~ts",[RecStr]),
1545		    new_do_shell(IO, N, More);
1546		true when Order==expect_echo ->
1547		    ct:log("Matched echo ~ts",[RecStr]),
1548		    new_do_shell(IO, N, More);
1549		false ->
1550		    ct:fail("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr])
1551	    end
1552    after 30000 ->
1553	    ct:log("Message queue of ~p:~n~p",
1554		   [self(), erlang:process_info(self(), messages)]),
1555	    case Order of
1556		expect -> ct:fail("timeout, expected ~p",[string:strip(Arg)]);
1557		type ->  ct:fail("timeout, no prompt")
1558	    end
1559    end;
1560
1561new_do_shell(_, _, []) ->
1562    ok.
1563
1564prompt_prefix() ->
1565    case node() of
1566	nonode@nohost -> <<>>;
1567	Node -> list_to_binary(
1568                  atom_to_list(Node))
1569    end.
1570
1571
1572new_do_shell_prompt(IO, N, type, Str, More) ->
1573    ct:log("Matched prompt ~p to trigger sending of next line to server",[N]),
1574    IO ! {input, self(), Str++"\r\n"},
1575    ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
1576    new_do_shell(IO, N, More);
1577new_do_shell_prompt(IO, N, Op, Str, More) ->
1578    ct:log("Matched prompt ~p",[N]),
1579    new_do_shell(IO, N, [{Op,Str}|More]).
1580
1581