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-module(ssh_connection_SUITE).
23
24-include_lib("common_test/include/ct.hrl").
25-include("ssh_connect.hrl").
26-include("ssh_test_lib.hrl").
27
28
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         big_cat/1,
44         connect_sock_not_passive/1,
45         connect_sock_not_tcp/1,
46         daemon_sock_not_passive/1,
47         daemon_sock_not_tcp/1,
48         do_interrupted_send/3,
49         do_simple_exec/1,
50         encode_decode_pty_opts/1,
51         exec_disabled/1,
52         exec_erlang_term/1,
53         exec_erlang_term_non_default_shell/1,
54         exec_shell_disabled/1,
55         gracefull_invalid_long_start/1,
56         gracefull_invalid_long_start_no_nl/1,
57         gracefull_invalid_start/1,
58         gracefull_invalid_version/1,
59         kex_error/1,
60         interrupted_send/1,
61         max_channels_option/1,
62         no_sensitive_leak/1,
63         ptty_alloc/1,
64         ptty_alloc_default/1,
65         ptty_alloc_pixel/1,
66         read_write_loop/1,
67         read_write_loop1/2,
68         send_after_exit/1,
69         simple_eval/1,
70         simple_exec/1,
71         simple_exec_sock/1,
72         simple_exec_two_socks/1,
73         small_cat/1,
74         small_interrupted_send/1,
75         start_exec_direct_fun1_read_write/1,
76         start_exec_direct_fun1_read_write_advanced/1,
77         start_shell/1,
78         start_shell_pty/1,
79         start_shell_exec/1,
80         start_shell_exec_direct_fun/1,
81         start_shell_exec_direct_fun1_error/1,
82         start_shell_exec_direct_fun1_error_type/1,
83         start_shell_exec_direct_fun2/1,
84         start_shell_exec_direct_fun3/1,
85         start_shell_exec_fun/1,
86         start_shell_exec_fun2/1,
87         start_shell_exec_fun3/1,
88         start_shell_sock_daemon_exec/1,
89         start_shell_sock_daemon_exec_multi/1,
90         start_shell_sock_exec_fun/1,
91         start_subsystem_on_closed_channel/1,
92         stop_listener/1,
93         ssh_exec_echo/2 % called as an MFA
94        ]).
95
96-define(EXEC_TIMEOUT, 10000).
97
98%%--------------------------------------------------------------------
99%% Common Test interface functions -----------------------------------
100%%--------------------------------------------------------------------
101
102%% suite() ->
103%%     [{ct_hooks,[ts_install_cth]}].
104
105suite() ->
106    [{timetrap,{seconds,40}}].
107
108all() ->
109    [
110     {group, openssh},
111     small_interrupted_send,
112     interrupted_send,
113     exec_erlang_term,
114     exec_erlang_term_non_default_shell,
115     exec_disabled,
116     exec_shell_disabled,
117     start_shell,
118     start_shell_pty,
119     start_shell_exec,
120     start_shell_exec_fun,
121     start_shell_exec_fun2,
122     start_shell_exec_fun3,
123     start_shell_exec_direct_fun,
124     start_shell_exec_direct_fun2,
125     start_shell_exec_direct_fun3,
126     start_shell_exec_direct_fun1_error,
127     start_shell_exec_direct_fun1_error_type,
128     start_exec_direct_fun1_read_write,
129     start_exec_direct_fun1_read_write_advanced,
130     start_shell_sock_exec_fun,
131     start_shell_sock_daemon_exec,
132     start_shell_sock_daemon_exec_multi,
133     encode_decode_pty_opts,
134     connect_sock_not_tcp,
135     daemon_sock_not_tcp,
136     gracefull_invalid_version,
137     gracefull_invalid_start,
138     gracefull_invalid_long_start,
139     gracefull_invalid_long_start_no_nl,
140     kex_error,
141     stop_listener,
142     no_sensitive_leak,
143     start_subsystem_on_closed_channel,
144     max_channels_option
145    ].
146groups() ->
147    [{openssh, [], payload() ++ ptty() ++ sock()}].
148
149payload() ->
150    [simple_exec,
151     simple_exec_sock,
152     simple_exec_two_socks,
153     small_cat,
154     big_cat,
155     send_after_exit].
156
157ptty() ->
158    [ptty_alloc_default,
159     ptty_alloc,
160     ptty_alloc_pixel].
161
162sock() ->
163    [connect_sock_not_passive,
164     daemon_sock_not_passive
165    ].
166
167%%--------------------------------------------------------------------
168init_per_suite(Config) ->
169    ?CHECK_CRYPTO(
170       [{ptty_supported, ssh_test_lib:ptty_supported()}
171        | Config]
172      ).
173
174end_per_suite(_Config) ->
175    catch ssh:stop(),
176    ok.
177
178%%--------------------------------------------------------------------
179init_per_group(openssh, Config) ->
180    case ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []) of
181	{error,econnrefused} ->
182	    {skip,"No openssh deamon (econnrefused)"};
183	{ok, Socket} ->
184	    gen_tcp:close(Socket),
185	    ssh_test_lib:openssh_sanity_check(Config)
186    end;
187init_per_group(_, Config) ->
188    Config.
189
190end_per_group(_, Config) ->
191    Config.
192
193%%--------------------------------------------------------------------
194init_per_testcase(_TestCase, Config) ->
195    %% To make sure we start clean as it is not certain that
196    %% end_per_testcase will be run!
197    end_per_testcase(any, Config),
198    ssh:start(),
199    Config.
200
201end_per_testcase(_TestCase, _Config) ->
202    ssh:stop().
203
204%%--------------------------------------------------------------------
205%% Test Cases --------------------------------------------------------
206%%--------------------------------------------------------------------
207simple_exec(Config) when is_list(Config) ->
208    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
209    do_simple_exec(ConnectionRef).
210
211%%--------------------------------------------------------------------
212simple_exec_sock(_Config) ->
213    {ok, Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, [{active,false}]),
214    {ok, ConnectionRef} = ssh:connect(Sock, [{save_accepted_host, false},
215                                             {silently_accept_hosts, true},
216                                             {user_interaction, true}
217                                            ]),
218    do_simple_exec(ConnectionRef).
219
220%%--------------------------------------------------------------------
221simple_exec_two_socks(_Config) ->
222    Parent = self(),
223    F = fun() ->
224                spawn_link(
225                  fun() ->
226                          {ok, Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, [{active,false}]),
227                          {ok, ConnectionRef} = ssh:connect(Sock, [{save_accepted_host, false},
228                                                                   {silently_accept_hosts, true},
229                                                                   {user_interaction, true}]),
230                          Parent ! {self(),do_simple_exec(ConnectionRef)}
231                  end)
232        end,
233    Pid1 = F(),
234    Pid2 = F(),
235    receive
236        {Pid1,ok} -> ok
237    end,
238    receive
239        {Pid2,ok} -> ok
240    end.
241
242%%--------------------------------------------------------------------
243connect_sock_not_tcp(_Config) ->
244    {ok,Sock} = gen_udp:open(0, []),
245    {error, not_tcp_socket} = ssh:connect(Sock, [{save_accepted_host, false},
246                                                 {silently_accept_hosts, true},
247                                                 {user_interaction, true}]),
248    gen_udp:close(Sock).
249
250%%--------------------------------------------------------------------
251daemon_sock_not_tcp(_Config) ->
252    {ok,Sock} = gen_udp:open(0, []),
253    {error, not_tcp_socket} = ssh:daemon(Sock),
254    gen_udp:close(Sock).
255
256%%--------------------------------------------------------------------
257connect_sock_not_passive(_Config) ->
258    {ok,Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []),
259    {error, not_passive_mode} = ssh:connect(Sock, [{save_accepted_host, false},
260                                                   {silently_accept_hosts, true},
261                                                   {user_interaction, true}]),
262    gen_tcp:close(Sock).
263
264%%--------------------------------------------------------------------
265daemon_sock_not_passive(_Config) ->
266    {ok,Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []),
267    {error, not_passive_mode} = ssh:daemon(Sock),
268    gen_tcp:close(Sock).
269
270%%--------------------------------------------------------------------
271small_cat(Config) when is_list(Config) ->
272    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
273    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
274    success = ssh_connection:exec(ConnectionRef, ChannelId0,
275				  "cat", infinity),
276
277    Data = <<"I like spaghetti squash">>,
278    ok = ssh_connection:send(ConnectionRef, ChannelId0, Data),
279    ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
280
281    %% receive response to input
282    receive
283	{ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} ->
284	    ok
285    after
286	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
287    end,
288
289    %% receive close messages
290    receive
291	{ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
292	    ok
293    after
294	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
295    end,
296    receive
297	{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
298	    ok
299    after
300	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
301    end,
302    receive
303	{ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
304	    ok
305    after
306	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
307    end.
308%%--------------------------------------------------------------------
309big_cat(Config) when is_list(Config) ->
310    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
311							     {user_interaction, false}]),
312    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
313    success = ssh_connection:exec(ConnectionRef, ChannelId0,
314				  "cat", infinity),
315
316    %% build 10MB binary
317    Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
318
319    %% pre-adjust receive window so the other end doesn't block
320    ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
321
322    ct:log("sending ~p byte binary~n",[size(Data)]),
323    ok = ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000),
324    ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
325
326    %% collect echoed data until eof
327    case big_cat_rx(ConnectionRef, ChannelId0) of
328	{ok, Data} ->
329	    ok;
330	{ok, Other} ->
331	    case size(Data) =:= size(Other) of
332		true ->
333		    ct:log("received and sent data are same"
334			   "size but do not match~n",[]);
335		false ->
336		    ct:log("sent ~p but only received ~p~n",
337			   [size(Data), size(Other)])
338	    end,
339	    ct:fail(receive_data_mismatch);
340	Else ->
341	    ct:fail(Else)
342    end,
343
344    %% receive close messages (eof already consumed)
345    receive
346	{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
347	    ok
348    after
349	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
350    end,
351    receive
352	{ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
353	    ok
354    after
355	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
356    end.
357
358%%--------------------------------------------------------------------
359send_after_exit(Config) when is_list(Config) ->
360    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
361    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
362    Data = <<"I like spaghetti squash">>,
363
364    %% Shell command "false" will exit immediately
365    success = ssh_connection:exec(ConnectionRef, ChannelId0,
366				  "false", infinity),
367    receive
368	{ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
369	    ok
370    after
371	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
372    end,
373    receive
374	{ssh_cm, ConnectionRef, {exit_status, ChannelId0, ExitStatus}} when ExitStatus=/=0 ->
375	    ok
376    after
377	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
378    end,
379    receive
380	{ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
381	    ok
382    after
383	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
384    end,
385    case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of
386	{error, closed} -> ok;
387	ok ->
388	    ct:fail({expected,{error,closed}, {got, ok}});
389	{error, timeout} ->
390	    ct:fail({expected,{error,closed}, {got, {error, timeout}}});
391	Else ->
392	    ct:fail(Else)
393    end.
394
395%%--------------------------------------------------------------------
396encode_decode_pty_opts(_Config) ->
397    Tags =
398        [vintr, vquit, verase, vkill, veof, veol, veol2, vstart, vstop, vsusp, vdsusp,
399         vreprint, vwerase, vlnext, vflush, vswtch, vstatus, vdiscard, ignpar, parmrk,
400         inpck, istrip, inlcr, igncr, icrnl, iuclc, ixon, ixany, ixoff, imaxbel, isig,
401         icanon, xcase, echo, echoe, echok, echonl, noflsh, tostop, iexten, echoctl,
402         echoke, pendin, opost, olcuc, onlcr, ocrnl, onocr, onlret, cs7, cs8, parenb,
403         parodd, tty_op_ispeed, tty_op_ospeed],
404    Opts =
405        lists:zip(Tags,
406                  lists:seq(1, length(Tags))),
407
408    case ssh_connection:encode_pty_opts(Opts) of
409        Bin when is_binary(Bin) ->
410            case ssh_connection:decode_pty_opts(Bin) of
411                Opts ->
412                    ok;
413                Other ->
414                    ct:log("Expected ~p~nGot ~p~nBin = ~p",[Opts,Other,Bin]),
415                    ct:fail("Not the same",[])
416            end;
417        Other ->
418            ct:log("encode -> ~p",[Other]),
419            ct:fail("Encode failed",[])
420    end.
421
422%%--------------------------------------------------------------------
423ptty_alloc_default(Config) when is_list(Config) ->
424    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
425    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
426    Expect = case proplists:get_value(ptty_supported, Config) of
427                 true -> success;
428                 false -> failure
429             end,
430    Expect = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
431    ssh:close(ConnectionRef).
432
433%%--------------------------------------------------------------------
434ptty_alloc(Config) when is_list(Config) ->
435    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
436    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
437    Expect = case proplists:get_value(ptty_supported, Config) of
438                 true -> success;
439                 false -> failure
440             end,
441    Expect = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
442                                       [{term, os:getenv("TERM", ?DEFAULT_TERMINAL)}, {width, 70}, {height, 20}]),
443    ssh:close(ConnectionRef).
444
445
446%%--------------------------------------------------------------------
447ptty_alloc_pixel(Config) when is_list(Config) ->
448    ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
449    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
450    Expect = case proplists:get_value(ptty_supported, Config) of
451                 true -> success;
452                 false -> failure
453             end,
454    Expect = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
455                                       [{term, os:getenv("TERM", ?DEFAULT_TERMINAL)}, {pixel_widh, 630}, {pixel_hight, 470}]),
456    ssh:close(ConnectionRef).
457
458%%--------------------------------------------------------------------
459small_interrupted_send(Config) ->
460    K = 1024,
461    M = K*K,
462    do_interrupted_send(Config, 10*M, 4*K).
463interrupted_send(Config) ->
464    M = 1024*1024,
465    do_interrupted_send(Config, 10*M, 4*M).
466
467do_interrupted_send(Config, SendSize, EchoSize) ->
468    PrivDir = proplists:get_value(priv_dir, Config),
469    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
470    file:make_dir(UserDir),
471    SysDir = proplists:get_value(data_dir, Config),
472    EchoSS_spec = {ssh_echo_server, [EchoSize,[{dbg,true}]]},
473    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
474					     {user_dir, UserDir},
475					     {password, "morot"},
476					     {subsystems, [{"echo_n",EchoSS_spec}]}]),
477
478    ct:log("~p:~p connect", [?MODULE,?LINE]),
479    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
480						      {user, "foo"},
481						      {password, "morot"},
482						      {user_interaction, false},
483						      {user_dir, UserDir}]),
484    ct:log("~p:~p connected", [?MODULE,?LINE]),
485
486    %% build big binary
487    Data = << <<X:32>> || X <- lists:seq(1,SendSize div 4)>>,
488
489    %% expect remote end to send us EchoSize back
490    <<ExpectedData:EchoSize/binary, _/binary>> = Data,
491
492    %% Spawn listener. Otherwise we could get a deadlock due to filled buffers
493    Parent = self(),
494    ResultPid = spawn(
495		  fun() ->
496			  ct:log("~p:~p open channel",[?MODULE,?LINE]),
497			  {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
498			  ct:log("~p:~p start subsystem", [?MODULE,?LINE]),
499			  case ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity) of
500			      success ->
501				  Parent ! {self(), channelId, ChannelId},
502
503				  Result =
504				      try collect_data(ConnectionRef, ChannelId, EchoSize)
505				      of
506					  ExpectedData ->
507					      ct:log("~p:~p got expected data",[?MODULE,?LINE]),
508					      ok;
509					  Other ->
510					      ct:log("~p:~p unexpect: ~p", [?MODULE,?LINE,Other]),
511					      {fail,"unexpected result in listener"}
512				      catch
513					  Class:Exception ->
514					      {fail, io_lib:format("Listener exception ~p:~p",[Class,Exception])}
515				      end,
516				  Parent ! {self(), result, Result};
517			      Other ->
518				  Parent ! {self(), channelId, error, Other}
519			  end
520		  end),
521
522    receive
523	{ResultPid, channelId, error, Other} ->
524	    ct:log("~p:~p channelId error ~p", [?MODULE,?LINE,Other]),
525	    ssh:close(ConnectionRef),
526	    ssh:stop_daemon(Pid),
527	    {fail, "ssh_connection:subsystem"};
528
529	{ResultPid, channelId, ChannelId} ->
530	    ct:log("~p:~p ~p going to send ~p bytes", [?MODULE,?LINE,self(),size(Data)]),
531	    SenderPid = spawn(fun() ->
532				      Parent ! {self(),  ssh_connection:send(ConnectionRef, ChannelId, Data, 30000)}
533			      end),
534	    receive
535	    	{ResultPid, result, {fail, Fail}} ->
536		    ct:log("~p:~p Listener failed: ~p", [?MODULE,?LINE,Fail]),
537		    {fail, Fail};
538
539		{ResultPid, result, Result} ->
540		    ct:log("~p:~p Got result: ~p", [?MODULE,?LINE,Result]),
541		    ssh:close(ConnectionRef),
542		    ssh:stop_daemon(Pid),
543		    ct:log("~p:~p Check sender", [?MODULE,?LINE]),
544		    receive
545			{SenderPid, {error, closed}} ->
546			    ct:log("~p:~p {error,closed} - That's what we expect :)",[?MODULE,?LINE]),
547			    ok;
548			Msg ->
549			    ct:log("~p:~p Not expected send result: ~p",[?MODULE,?LINE,Msg]),
550			    {fail, "Not expected msg"}
551		    end;
552
553		{SenderPid, {error, closed}} ->
554		    ct:log("~p:~p {error,closed} - That's what we expect, but client channel handler has not reported yet",[?MODULE,?LINE]),
555		    receive
556			{ResultPid, result, Result} ->
557			    ct:log("~p:~p Now got the result: ~p", [?MODULE,?LINE,Result]),
558			    ssh:close(ConnectionRef),
559			    ssh:stop_daemon(Pid),
560			    ok;
561			Msg ->
562			    ct:log("~p:~p Got an unexpected msg ~p",[?MODULE,?LINE,Msg]),
563			    {fail, "Un-expected msg"}
564		    end;
565
566		Msg ->
567		    ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,Msg]),
568		    {fail, "Unexpected msg"}
569	    end
570    end.
571
572%%--------------------------------------------------------------------
573start_shell(Config) when is_list(Config) ->
574    PrivDir = proplists:get_value(priv_dir, Config),
575    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
576    file:make_dir(UserDir),
577    SysDir = proplists:get_value(data_dir, Config),
578    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
579					     {user_dir, UserDir},
580					     {password, "morot"},
581					     {shell, fun(U, H) -> start_our_shell(U, H) end} ]),
582
583    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
584						      {user, "foo"},
585						      {password, "morot"},
586						      {user_interaction, true},
587						      {user_dir, UserDir}]),
588    test_shell_is_enabled(ConnectionRef, <<"Enter command">>), % No pty alloc by erl client
589    test_exec_is_disabled(ConnectionRef),
590    ssh:close(ConnectionRef),
591    ssh:stop_daemon(Pid).
592
593%%-------------------------------------------------------------------
594start_shell_pty(Config) when is_list(Config) ->
595    PrivDir = proplists:get_value(priv_dir, Config),
596    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
597    file:make_dir(UserDir),
598    SysDir = proplists:get_value(data_dir, Config),
599    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
600					     {user_dir, UserDir},
601					     {password, "morot"},
602					     {shell, fun(U, H) -> start_our_shell(U, H) end} ]),
603
604    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
605						      {user, "foo"},
606						      {password, "morot"},
607						      {user_interaction, true},
608						      {user_dir, UserDir}]),
609    test_shell_is_enabled(ConnectionRef, <<"Enter command\r\n">>, [{pty_opts,[{onlcr,1}]}]), % alloc pty
610    test_exec_is_disabled(ConnectionRef),
611    ssh:close(ConnectionRef),
612    ssh:stop_daemon(Pid).
613
614
615%%--------------------------------------------------------------------
616start_shell_exec(Config) when is_list(Config) ->
617    PrivDir = proplists:get_value(priv_dir, Config),
618    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
619    file:make_dir(UserDir),
620    SysDir = proplists:get_value(data_dir, Config),
621    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
622					     {user_dir, UserDir},
623					     {password, "morot"},
624					     {exec, {?MODULE,ssh_exec_echo,[]}} ]),
625
626    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
627						      {user, "foo"},
628						      {password, "morot"},
629						      {user_interaction, true},
630						      {user_dir, UserDir}]),
631    test_shell_is_enabled(ConnectionRef),
632    test_exec_is_enabled(ConnectionRef,  "testing",  <<"echo testing\r\n">>),
633    ssh:close(ConnectionRef),
634    ssh:stop_daemon(Pid).
635
636%%--------------------------------------------------------------------
637exec_erlang_term(Config) when is_list(Config) ->
638    PrivDir = proplists:get_value(priv_dir, Config),
639    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
640    file:make_dir(UserDir),
641    SysDir = proplists:get_value(data_dir, Config),
642    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
643					     {user_dir, UserDir},
644					     {password, "morot"}
645                                            ]),
646
647    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
648						      {user, "foo"},
649						      {password, "morot"},
650						      {user_interaction, true},
651						      {user_dir, UserDir}]),
652    test_shell_is_enabled(ConnectionRef),
653    test_exec_is_enabled(ConnectionRef),
654    ssh:close(ConnectionRef),
655    ssh:stop_daemon(Pid).
656
657%%--------------------------------------------------------------------
658exec_erlang_term_non_default_shell(Config) when is_list(Config) ->
659    PrivDir = proplists:get_value(priv_dir, Config),
660    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
661    file:make_dir(UserDir),
662    SysDir = proplists:get_value(data_dir, Config),
663    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
664					     {user_dir, UserDir},
665					     {password, "morot"},
666                                             {shell, fun(U, H) -> start_our_shell(U, H) end}
667                                            ]),
668
669    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
670						      {user, "foo"},
671						      {password, "morot"},
672						      {user_interaction, true},
673						      {user_dir, UserDir}
674                                                     ]),
675    test_exec_is_disabled(ConnectionRef),
676    ssh:close(ConnectionRef),
677    ssh:stop_daemon(Pid).
678
679%%--------------------------------------------------------------------
680exec_disabled(Config) when is_list(Config) ->
681    PrivDir = proplists:get_value(priv_dir, Config),
682    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
683    file:make_dir(UserDir),
684    SysDir = proplists:get_value(data_dir, Config),
685    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
686					     {user_dir, UserDir},
687					     {password, "morot"},
688                                             {exec, disabled}
689                                            ]),
690    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
691						      {user, "foo"},
692						      {password, "morot"},
693						      {user_interaction, true},
694						      {user_dir, UserDir}
695                                                     ]),
696    test_shell_is_enabled(ConnectionRef),
697    test_exec_is_disabled(ConnectionRef),
698    ssh:stop_daemon(Pid).
699
700
701exec_shell_disabled(Config) when is_list(Config) ->
702    PrivDir = proplists:get_value(priv_dir, Config),
703    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
704    file:make_dir(UserDir),
705    SysDir = proplists:get_value(data_dir, Config),
706    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
707					     {user_dir, UserDir},
708					     {password, "morot"},
709                                             {shell, disabled}
710                                            ]),
711    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
712						      {user, "foo"},
713						      {password, "morot"},
714						      {user_interaction, true},
715						      {user_dir, UserDir}
716                                                     ]),
717    test_shell_is_disabled(ConnectionRef),
718    test_exec_is_enabled(ConnectionRef),
719    ssh:stop_daemon(Pid).
720
721%%--------------------------------------------------------------------
722start_shell_exec_fun(Config) ->
723    do_start_shell_exec_fun(fun(Cmd) ->
724                                    spawn(fun() ->
725                                                  io:format("echo ~s\n", [Cmd])
726                                          end)
727                            end,
728                            "testing", <<"echo testing\n">>, 0,
729                            Config).
730
731start_shell_exec_fun2(Config) ->
732    do_start_shell_exec_fun(fun(Cmd, User) ->
733                                    spawn(fun() ->
734                                                  io:format("echo ~s ~s\n",[User,Cmd])
735                                          end)
736                            end,
737                            "testing", <<"echo foo testing\n">>, 0,
738                            Config).
739
740start_shell_exec_fun3(Config) ->
741    do_start_shell_exec_fun(fun(Cmd, User, _PeerAddr) ->
742                                    spawn(fun() ->
743                                                  io:format("echo ~s ~s\n",[User,Cmd])
744                                          end)
745                            end,
746                            "testing", <<"echo foo testing\n">>, 0,
747                            Config).
748
749start_shell_exec_direct_fun(Config) ->
750    do_start_shell_exec_fun({direct, fun(Cmd) -> {ok, io_lib:format("echo ~s~n",[Cmd])} end},
751                            "testing", <<"echo testing\n">>, 0,
752                            Config).
753
754start_shell_exec_direct_fun2(Config) ->
755    do_start_shell_exec_fun({direct, fun(Cmd,User) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
756                            "testing", <<"echo foo testing">>, 0,
757                            Config).
758
759start_shell_exec_direct_fun3(Config) ->
760    do_start_shell_exec_fun({direct, fun(Cmd,User,_PeerAddr) -> {ok, io_lib:format("echo ~s ~s",[User,Cmd])} end},
761                            "testing", <<"echo foo testing">>, 0,
762                            Config).
763
764start_shell_exec_direct_fun1_error(Config) ->
765    do_start_shell_exec_fun({direct, fun(_Cmd) -> {error, {bad}} end},
766                            "testing", <<"**Error** {bad}">>, 1,
767                            Config).
768
769start_shell_exec_direct_fun1_error_type(Config) ->
770    do_start_shell_exec_fun({direct, fun(_Cmd) -> very_bad end},
771                            "testing", <<"**Error** Bad exec fun in server. Invalid return value: very_bad">>, 1,
772                            Config).
773
774start_exec_direct_fun1_read_write(Config) ->
775    PrivDir = proplists:get_value(priv_dir, Config),
776    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
777    file:make_dir(UserDir),
778    SysDir = proplists:get_value(data_dir, Config),
779    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
780					     {user_dir, UserDir},
781					     {password, "morot"},
782					     {exec, {direct,fun read_write_loop/1}}]),
783
784    C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
785                                          {user, "foo"},
786                                          {password, "morot"},
787                                          {user_interaction, true},
788                                          {user_dir, UserDir}]),
789
790    {ok, Ch} = ssh_connection:session_channel(C, infinity),
791
792    success = ssh_connection:exec(C, Ch, "> ", infinity),
793    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
794
795    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
796    ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
797    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
798
799    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
800    ok = ssh_connection:send(C, Ch, "quit.\n", 5000),
801    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{1,inputs}">>}}),
802    receive
803        {ssh_cm,C,{exit_status,Ch,0}} -> ok
804    after 5000 -> go_on
805    end,
806    receive
807        {ssh_cm,C,{eof,Ch}} -> ok
808    after 5000 -> go_on
809    end,
810    receive
811        {ssh_cm,C,{closed,Ch}} -> ok
812    after 5000 -> go_on
813    end,
814    receive
815        X -> ct:fail("remaining messages"),
816             ct:log("remaining message: ~p",[X])
817    after 0 -> go_on
818    end,
819    ssh:stop_daemon(Pid).
820
821
822start_exec_direct_fun1_read_write_advanced(Config) ->
823    PrivDir = proplists:get_value(priv_dir, Config),
824    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
825    file:make_dir(UserDir),
826    SysDir = proplists:get_value(data_dir, Config),
827    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
828					     {user_dir, UserDir},
829					     {password, "morot"},
830					     {exec, {direct,fun read_write_loop/1}}]),
831
832    C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
833                                          {user, "foo"},
834                                          {password, "morot"},
835                                          {user_interaction, true},
836                                          {user_dir, UserDir}]),
837
838    {ok, Ch} = ssh_connection:session_channel(C, infinity),
839
840    success = ssh_connection:exec(C, Ch, "> ", infinity),
841    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"Tiny read/write test\n">>}}),
842
843    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"1> ">>}}),
844    ok = ssh_connection:send(C, Ch, "hej.\n", 5000),
845    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,hej}\n">>}}),
846
847    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"2> ">>}}),
848    ok = ssh_connection:send(C, Ch, "'Hi ", 5000),
849    ok = ssh_connection:send(C, Ch, "there", 5000),
850    ok = ssh_connection:send(C, Ch, "'", 5000),
851    ok = ssh_connection:send(C, Ch, ".\n", 5000),
852    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"{simple_eval,'Hi there'}\n">>}}),
853    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,0,<<"3> ">>}}),
854    ok = ssh_connection:send(C, Ch, "bad_input.\n", 5000),
855    ssh_test_lib:receive_exec_result_or_fail({ssh_cm,C,{data,Ch,1,<<"**Error** {bad_input,3}">>}}),
856    receive
857        {ssh_cm,C,{exit_status,Ch,255}} -> ok
858    after 5000 -> go_on
859    end,
860    receive
861        {ssh_cm,C,{eof,Ch}} -> ok
862    after 5000 -> go_on
863    end,
864    receive
865        {ssh_cm,C,{closed,Ch}} -> ok
866    after 5000 -> go_on
867    end,
868    receive
869        X -> ct:fail("remaining messages"),
870             ct:log("remaining message: ~p",[X])
871    after 0 -> go_on
872    end,
873    ssh:stop_daemon(Pid).
874
875
876
877
878%% A tiny read-write loop ended by a 'quit.\n'
879read_write_loop(Prompt) ->
880    io:format("Tiny read/write test~n", []),
881    read_write_loop1(Prompt, 1).
882
883read_write_loop1(Prompt, N) ->
884    case io:read(lists:concat([N,Prompt])) of
885        {ok, quit} ->
886            {ok, {N-1, inputs}};
887        {ok, bad_input} ->
888            {error, {bad_input,N}};
889        {ok,Inp} ->
890            io:format("~p~n",[simple_eval(Inp)]),
891            read_write_loop1(Prompt, N+1)
892    end.
893
894simple_eval(Inp) -> {simple_eval,Inp}.
895
896
897do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
898    PrivDir = proplists:get_value(priv_dir, Config),
899    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
900    file:make_dir(UserDir),
901    SysDir = proplists:get_value(data_dir, Config),
902    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
903					     {user_dir, UserDir},
904					     {password, "morot"},
905					     {exec, Fun}]),
906
907    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
908						      {user, "foo"},
909						      {password, "morot"},
910						      {user_interaction, true},
911						      {user_dir, UserDir}]),
912
913    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
914
915    success = ssh_connection:exec(ConnectionRef, ChannelId0, Command, infinity),
916
917    receive
918	{ssh_cm, ConnectionRef, {data, _ChannelId, ExpectType, Expect}} ->
919	    ok
920    after 5000 ->
921            receive
922                Other ->
923                    ct:log("Received other:~n~p~nExpected: ~p~n",
924                           [Other, {ssh_cm, ConnectionRef, {data, '_ChannelId', ExpectType, Expect}} ]),
925                    ct:fail("Unexpected response")
926            after 0 ->
927                    ct:fail("Exec Timeout")
928            end
929    end,
930
931    ssh:close(ConnectionRef),
932    ssh:stop_daemon(Pid).
933
934%%--------------------------------------------------------------------
935start_shell_sock_exec_fun(Config) when is_list(Config) ->
936    PrivDir = proplists:get_value(priv_dir, Config),
937    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
938    file:make_dir(UserDir),
939    SysDir = proplists:get_value(data_dir, Config),
940    {Pid, HostD, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
941                                              {user_dir, UserDir},
942                                              {password, "morot"},
943                                              {exec, fun ssh_exec_echo/1}]),
944    Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(HostD)),
945
946    {ok, Sock} = ssh_test_lib:gen_tcp_connect(Host, Port, [{active,false}]),
947    {ok,ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
948                                            {save_accepted_host, false},
949					    {user, "foo"},
950					    {password, "morot"},
951					    {user_interaction, true},
952					    {user_dir, UserDir}]),
953
954    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
955
956    success = ssh_connection:exec(ConnectionRef, ChannelId0,
957				  "testing", infinity),
958
959    receive
960	{ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
961	    ok
962    after 5000 ->
963	    ct:fail("Exec Timeout")
964    end,
965
966    ssh:close(ConnectionRef),
967    ssh:stop_daemon(Pid).
968
969%%--------------------------------------------------------------------
970start_shell_sock_daemon_exec(Config) ->
971    PrivDir = proplists:get_value(priv_dir, Config),
972    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
973    file:make_dir(UserDir),
974    SysDir = proplists:get_value(data_dir, Config),
975
976    %% Listening tcp socket at the client side
977    {ok,Sl} = gen_tcp:listen(0, [{active,false}]),
978    {ok,{_IP,Port}} = inet:sockname(Sl),	% _IP is likely to be {0,0,0,0}. Win don't like...
979
980    %% A server tcp-contects to the listening socket and starts an ssh daemon
981    spawn_link(fun() ->
982		       {ok,Ss} = ssh_test_lib:gen_tcp_connect(Port, [{active,false}]),
983		       {ok, _Pid} = ssh:daemon(Ss, [{system_dir, SysDir},
984						    {user_dir, UserDir},
985						    {password, "morot"},
986						    {exec, fun ssh_exec_echo/1}])
987	       end),
988
989    %% The client accepts the tcp connection from the server and ssh-connects to it
990    {ok,Sc} = gen_tcp:accept(Sl),
991    {ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
992                                          {save_accepted_host, false},
993					  {user, "foo"},
994					  {password, "morot"},
995					  {user_interaction, true},
996					  {user_dir, UserDir}]),
997
998    %% And runs some commands
999    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
1000
1001    success = ssh_connection:exec(ConnectionRef, ChannelId0,
1002				  "testing", infinity),
1003
1004    receive
1005	{ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
1006	    ok
1007    after 5000 ->
1008	    ct:fail("Exec Timeout")
1009    end,
1010
1011    ssh:close(ConnectionRef).
1012
1013%%--------------------------------------------------------------------
1014start_shell_sock_daemon_exec_multi(Config) ->
1015    PrivDir = proplists:get_value(priv_dir, Config),
1016    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1017    file:make_dir(UserDir),
1018    SysDir = proplists:get_value(data_dir, Config),
1019
1020    NumConcurent = 5,
1021
1022    %% Listening tcp socket at the client side
1023    {ok,Sl} = gen_tcp:listen(0, [{active,false}]),
1024    {ok,{_IP,Port}} = inet:sockname(Sl),	% _IP is likely to be {0,0,0,0}. Win don't like...
1025
1026    DaemonOpts = [{system_dir, SysDir},
1027                  {user_dir, UserDir},
1028                  {password, "morot"},
1029                  {exec, fun ssh_exec_echo/1}],
1030
1031    %% Servers tcp-contects to the listening socket and starts an ssh daemon
1032    Pids =
1033        [spawn_link(fun() ->
1034                            {ok,Ss} = ssh_test_lib:gen_tcp_connect(Port, [{active,false}]),
1035                            {ok, _Pid} = ssh:daemon(Ss, DaemonOpts)
1036                    end)
1037         || _ <- lists:seq(1,NumConcurent)],
1038    ct:log("~p:~p: ~p daemons spawned!", [?MODULE,?LINE,length(Pids)]),
1039
1040    %% The client accepts the tcp connections from the servers and ssh-connects to it
1041    ConnectionRefs =
1042        [begin
1043             {ok,Sc} = gen_tcp:accept(Sl),
1044             {ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
1045                                                   {save_accepted_host, false},
1046                                                   {user, "foo"},
1047                                                   {password, "morot"},
1048                                                   {user_interaction, true},
1049                                                   {user_dir, UserDir}]),
1050             ConnectionRef
1051         end || _Pid <- Pids],
1052    ct:log("~p:~p: ~p connections accepted!", [?MODULE,?LINE,length(ConnectionRefs)]),
1053
1054    %% And runs some exec commands
1055    Parent = self(),
1056    ClientPids =
1057        lists:map(
1058          fun(ConnectionRef) ->
1059                  spawn_link(
1060                    fun() ->
1061                            {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
1062                            success = ssh_connection:exec(ConnectionRef, ChannelId0, "testing", infinity),
1063                            ct:log("~p:~p: exec on connection ~p", [?MODULE,?LINE,ConnectionRef]),
1064                            receive
1065                                {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
1066                                    Parent ! {answer_received,self()},
1067                                    ct:log("~p:~p: recevied result on connection ~p", [?MODULE,?LINE,ConnectionRef])
1068                            after 5000 ->
1069                                    ct:fail("Exec Timeout")
1070                            end
1071                    end)
1072          end, ConnectionRefs),
1073    ct:log("~p:~p: ~p clients spawned!", [?MODULE,?LINE,length(ClientPids)]),
1074
1075    lists:foreach(fun(P) ->
1076                          receive
1077                              {answer_received,P} -> ok
1078                          end
1079                  end, ClientPids),
1080    ct:log("~p:~p: All answers received!", [?MODULE,?LINE]),
1081
1082    lists:foreach(fun ssh:close/1, ConnectionRefs).
1083
1084%%--------------------------------------------------------------------
1085gracefull_invalid_version(Config) when is_list(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    SysDir = proplists:get_value(data_dir, Config),
1090
1091    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1092                                               {user_dir, UserDir},
1093                                               {password, "morot"}]),
1094
1095    {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
1096    ok = gen_tcp:send(S,  ["SSH-8.-1","\r\n"]),
1097    receive
1098	Verstring ->
1099	    ct:log("Server version: ~p~n", [Verstring]),
1100	    receive
1101		{tcp_closed, S} ->
1102		    ok
1103	    end
1104    after
1105	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1106    end.
1107
1108gracefull_invalid_start(Config) when is_list(Config) ->
1109    PrivDir = proplists:get_value(priv_dir, Config),
1110    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1111    file:make_dir(UserDir),
1112    SysDir = proplists:get_value(data_dir, Config),
1113    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1114                                               {user_dir, UserDir},
1115                                               {password, "morot"}]),
1116
1117    {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
1118    ok = gen_tcp:send(S,  ["foobar","\r\n"]),
1119    receive
1120	Verstring ->
1121	    ct:log("Server version: ~p~n", [Verstring]),
1122	    receive
1123		{tcp_closed, S} ->
1124		    ok
1125	    end
1126    after
1127	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1128    end.
1129
1130gracefull_invalid_long_start(Config) when is_list(Config) ->
1131    PrivDir = proplists:get_value(priv_dir, Config),
1132    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1133    file:make_dir(UserDir),
1134    SysDir = proplists:get_value(data_dir, Config),
1135    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1136                                               {user_dir, UserDir},
1137                                               {password, "morot"}]),
1138
1139    {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
1140    ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]),
1141    receive
1142	Verstring ->
1143	    ct:log("Server version: ~p~n", [Verstring]),
1144	    receive
1145		{tcp_closed, S} ->
1146		    ok
1147	    end
1148    after
1149	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1150    end.
1151
1152
1153gracefull_invalid_long_start_no_nl(Config) when is_list(Config) ->
1154    PrivDir = proplists:get_value(priv_dir, Config),
1155    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1156    file:make_dir(UserDir),
1157    SysDir = proplists:get_value(data_dir, Config),
1158    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1159                                               {user_dir, UserDir},
1160                                               {password, "morot"}]),
1161
1162    {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
1163    ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]),
1164    receive
1165	Verstring ->
1166	    ct:log("Server version: ~p~n", [Verstring]),
1167	    receive
1168		{tcp_closed, S} ->
1169		    ok
1170	    end
1171    after
1172	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1173    end.
1174
1175kex_error(Config) ->
1176    PrivDir = proplists:get_value(priv_dir, Config),
1177    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1178    file:make_dir(UserDir),
1179    SysDir = proplists:get_value(data_dir, Config),
1180    [Kex1,Kex2|_] = proplists:get_value(kex, ssh:default_algorithms()),
1181    {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1182                                              {user_dir, UserDir},
1183                                              {password, "morot"},
1184                                              {preferred_algorithms,[{kex,[Kex1]}]}
1185                                             ]),
1186    Ref = make_ref(),
1187    ok = ssh_log_h:add_fun(kex_error,
1188                           fun(#{msg:={report,#{format:=Fmt,args:=As,label:={error_logger,_}}}}, Pid) ->
1189                                   true = (erlang:process_info(Pid) =/= undefined), % remove handler if we are dead
1190                                   Pid ! {Ref, lists:flatten(io_lib:format(Fmt,As))};
1191                              (_,Pid) ->
1192                                   true = (erlang:process_info(Pid) =/= undefined), % remove handler if we are dead
1193                                   ok % Other msg
1194                           end,
1195                           self()),
1196    try
1197        ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1198                                          {user, "foo"},
1199                                          {password, "morot"},
1200                                          {user_interaction, false},
1201                                          {user_dir, UserDir},
1202                                          {preferred_algorithms,[{kex,[Kex2]}]}
1203                                         ])
1204    of
1205        _ ->
1206            ok = logger:remove_handler(kex_error),
1207            ct:fail("expected failure", [])
1208    catch
1209        error:{badmatch,{error,"Key exchange failed"}} ->
1210            %% ok
1211            receive
1212                {Ref, ErrMsgTxt} ->
1213                    ok = logger:remove_handler(kex_error),
1214                    ct:log("ErrMsgTxt = ~n~s", [ErrMsgTxt]),
1215                    Lines = lists:map(fun string:trim/1, string:tokens(ErrMsgTxt, "\n")),
1216                    OK = (lists:all(fun(S) -> lists:member(S,Lines) end,
1217                                    ["Disconnects with code = 3 [RFC4253 11.1]: Key exchange failed",
1218                                     "Details:",
1219                                     "No common key exchange algorithm,",
1220                                     "we have:",
1221                                     "peer have:"]) andalso
1222                          string:find(ErrMsgTxt, atom_to_list(Kex1)) =/= nomatch andalso
1223                          string:find(ErrMsgTxt, atom_to_list(Kex2)) =/= nomatch),
1224                    case OK of
1225                        true ->
1226                            ok;
1227                        false ->
1228                            ct:fail("unexpected error text msg", [])
1229                    end
1230            after 20000 ->
1231                    ok = logger:remove_handler(kex_error),
1232                    ct:fail("timeout", [])
1233            end;
1234
1235        error:{badmatch,{error,_}} ->
1236            ok = logger:remove_handler(kex_error),
1237            ct:fail("unexpected error msg", [])
1238    end.
1239
1240stop_listener(Config) when is_list(Config) ->
1241    PrivDir = proplists:get_value(priv_dir, Config),
1242    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1243    file:make_dir(UserDir),
1244    SysDir = proplists:get_value(data_dir, Config),
1245
1246    {Pid0, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1247					      {user_dir, UserDir},
1248					      {password, "morot"},
1249					      {exec, fun ssh_exec_echo/1}]),
1250
1251    ConnectionRef0 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1252						       {user, "foo"},
1253						       {password, "morot"},
1254						       {user_interaction, false},
1255						       {user_dir, UserDir}]),
1256
1257    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef0, infinity),
1258
1259    ssh:stop_listener(Host, Port),
1260
1261    {error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
1262                                          {save_accepted_host, false},
1263                                          {save_accepted_host, false},
1264					  {user, "foo"},
1265					  {password, "morot"},
1266					  {user_interaction, true},
1267					  {user_dir, UserDir}]),
1268    success = ssh_connection:exec(ConnectionRef0, ChannelId0,
1269				  "testing", infinity),
1270    receive
1271	{ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\n">>}} ->
1272	    ok
1273    after 5000 ->
1274	    ct:fail("Exec Timeout")
1275    end,
1276
1277    case ssh_test_lib:daemon(Port, [{system_dir, SysDir},
1278                                    {user_dir, UserDir},
1279                                    {password, "potatis"},
1280                                    {exec, fun ssh_exec_echo/1}]) of
1281	{Pid1, Host, Port} ->
1282	    ConnectionRef1 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1283							       {user, "foo"},
1284							       {password, "potatis"},
1285							       {user_interaction, true},
1286							       {user_dir, UserDir}]),
1287	    {error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
1288                                                  {save_accepted_host, false},
1289                                                  {user, "foo"},
1290                                                  {password, "morot"},
1291                                                  {user_interaction, true},
1292                                                  {user_dir, UserDir}]),
1293	    ssh:close(ConnectionRef0),
1294	    ssh:close(ConnectionRef1),
1295	    ssh:stop_daemon(Pid0),
1296	    ssh:stop_daemon(Pid1);
1297	Error ->
1298	    ssh:close(ConnectionRef0),
1299	    ssh:stop_daemon(Pid0),
1300	    ct:fail({unexpected, Error})
1301    end.
1302
1303no_sensitive_leak(Config) ->
1304    PrivDir = proplists:get_value(priv_dir, Config),
1305    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1306    file:make_dir(UserDir),
1307    SysDir = proplists:get_value(data_dir, Config),
1308
1309    %% Save old, and set new log level:
1310    #{level := Level} = logger:get_primary_config(),
1311    logger:set_primary_config(level, info),
1312    %% Define a collect fun:
1313    Ref = make_ref(),
1314    Collect =
1315        fun G(N,Nf,Nt) ->
1316                receive
1317                    {Ref, false, _} ->
1318                        G(N+1, Nf+1, Nt);
1319                    {Ref, true, R} ->
1320                        ct:log("Report leaked:~n~p", [R]),
1321                        G(N+1, Nf, Nt+1)
1322                after 100 ->
1323                        {N, Nf, Nt}
1324                end
1325        end,
1326
1327    %% Install the test handler:
1328    Hname = no_sensitive_leak,
1329    ok = ssh_log_h:add_fun(Hname,
1330                           fun(#{msg := {report,#{report := Rep}}}, Pid) ->
1331                                   true = (erlang:process_info(Pid, status) =/= undefined), % remove handler if we are dead
1332                                   Pid ! {Ref,ssh_log_h:sensitive_in_opt(Rep),Rep};
1333                              (_,Pid) ->
1334                                   true = (erlang:process_info(Pid, status) =/= undefined), % remove handler if we are dead
1335                                   ok
1336                           end,
1337                           self()),
1338
1339    {_Pid0, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1340					      {user_dir, UserDir},
1341					      {password, "morot"},
1342					      {exec, fun ssh_exec_echo/1}]),
1343
1344    _ConnectionRef0 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1345						       {user, "foo"},
1346						       {password, "morot"},
1347						       {user_interaction, true},
1348						       {user_dir, UserDir}]),
1349    %% Kill acceptor to make it restart:
1350    [true|_] =
1351        [exit(Pacc,kill) || {{ssh_system_sup,_},P1,supervisor,_} <- supervisor:which_children(sshd_sup),
1352                            {{ssh_acceptor_sup,_},P2,supervisor,_} <- supervisor:which_children(P1),
1353                            {{ssh_acceptor_sup,_},Pacc,worker,_} <- supervisor:which_children(P2)],
1354
1355    %% Remove the test handler and reset the logger level:
1356    timer:sleep(500),
1357    logger:remove_handler(Hname),
1358    logger:set_primary_config(Level),
1359
1360    case Collect(0, 0, 0) of
1361        {0, 0,   0} -> ct:fail("Logging failed, line = ~p", [?LINE]);
1362        {_, _,   0} -> ok;
1363        {_, _, Nt0} -> ct:fail("Leak in ~p cases!", [Nt0])
1364    end.
1365
1366start_subsystem_on_closed_channel(Config) ->
1367    PrivDir = proplists:get_value(priv_dir, Config),
1368    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1369    file:make_dir(UserDir),
1370    SysDir = proplists:get_value(data_dir, Config),
1371    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1372					     {user_dir, UserDir},
1373					     {password, "morot"},
1374					     {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]),
1375
1376    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1377						      {user, "foo"},
1378						      {password, "morot"},
1379						      {user_interaction, false},
1380						      {user_dir, UserDir}]),
1381
1382
1383    {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
1384    ok = ssh_connection:close(ConnectionRef, ChannelId1),
1385    {error, closed} = ssh_connection:ptty_alloc(ConnectionRef, ChannelId1, []),
1386    {error, closed} = ssh_connection:subsystem(ConnectionRef, ChannelId1, "echo_n", 5000),
1387    {error, closed} = ssh_connection:exec(ConnectionRef, ChannelId1, "testing1.\n", 5000),
1388    {error, closed} = ssh_connection:send(ConnectionRef, ChannelId1, "exit().\n", 5000),
1389
1390    %% Test that there could be a gap between close and an operation (Bugfix OTP-14939):
1391    {ok, ChannelId2} = ssh_connection:session_channel(ConnectionRef, infinity),
1392    ok = ssh_connection:close(ConnectionRef, ChannelId2),
1393    timer:sleep(2000),
1394    {error, closed} = ssh_connection:ptty_alloc(ConnectionRef, ChannelId2, []),
1395    {error, closed} = ssh_connection:subsystem(ConnectionRef, ChannelId2, "echo_n", 5000),
1396    {error, closed} = ssh_connection:exec(ConnectionRef, ChannelId2, "testing1.\n", 5000),
1397    {error, closed} = ssh_connection:send(ConnectionRef, ChannelId2, "exit().\n", 5000),
1398
1399    ssh:close(ConnectionRef),
1400    ssh:stop_daemon(Pid).
1401
1402%%--------------------------------------------------------------------
1403max_channels_option(Config) when is_list(Config) ->
1404    PrivDir = proplists:get_value(priv_dir, Config),
1405    UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
1406    file:make_dir(UserDir),
1407    SysDir = proplists:get_value(data_dir, Config),
1408    {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
1409					     {user_dir, UserDir},
1410					     {password, "morot"},
1411					     {max_channels, 3},
1412					     {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}
1413					    ]),
1414
1415    ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
1416						      {user, "foo"},
1417						      {password, "morot"},
1418						      {user_interaction, true},
1419						      {user_dir, UserDir}]),
1420
1421    %% Allocate a number of ChannelId:s to play with. (This operation is not
1422    %% counted by the max_channel option).
1423    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
1424    {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
1425    {ok, ChannelId2} = ssh_connection:session_channel(ConnectionRef, infinity),
1426    {ok, ChannelId3} = ssh_connection:session_channel(ConnectionRef, infinity),
1427    {ok, ChannelId4} = ssh_connection:session_channel(ConnectionRef, infinity),
1428    {ok, ChannelId5} = ssh_connection:session_channel(ConnectionRef, infinity),
1429    {ok, ChannelId6} = ssh_connection:session_channel(ConnectionRef, infinity),
1430    {ok, _ChannelId7} = ssh_connection:session_channel(ConnectionRef, infinity),
1431
1432    %% Now start to open the channels (this is counted my max_channels) to check that
1433    %% it gives a failure at right place
1434
1435    %%%---- Channel 1(3): shell
1436    ok = ssh_connection:shell(ConnectionRef,ChannelId0),
1437    receive
1438	{ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"Eshell",_/binary>>}} ->
1439	    ok
1440    after 5000 ->
1441	    ct:fail("CLI Timeout")
1442    end,
1443
1444    %%%---- Channel 2(3): subsystem "echo_n"
1445    success = ssh_connection:subsystem(ConnectionRef, ChannelId1, "echo_n", infinity),
1446
1447    %%%---- Channel 3(3): exec. This closes itself.
1448    success = ssh_connection:exec(ConnectionRef, ChannelId2, "testing1.\n", infinity),
1449    receive
1450	{ssh_cm, ConnectionRef, {data, ChannelId2, 0, <<"testing1",_/binary>>}} ->
1451	    ok
1452    after 5000 ->
1453	    ct:fail("Exec #1 Timeout")
1454    end,
1455
1456    %%%---- Channel 3(3): subsystem "echo_n" (Note that ChannelId2 should be closed now)
1457    ?wait_match(success, ssh_connection:subsystem(ConnectionRef, ChannelId3, "echo_n", infinity)),
1458
1459    %%%---- Channel 4(3) !: exec  This should fail
1460    failure = ssh_connection:exec(ConnectionRef, ChannelId4, "testing2.\n", infinity),
1461
1462    %%%---- close the shell (Frees one channel)
1463    ok = ssh_connection:send(ConnectionRef, ChannelId0, "exit().\n", 5000),
1464
1465    %%%---- wait for the subsystem to terminate
1466    receive
1467	{ssh_cm,ConnectionRef,{closed,ChannelId0}} -> ok
1468    after 5000 ->
1469	    ct:log("Timeout waiting for '{ssh_cm,~p,{closed,~p}}'~n"
1470		   "Message queue:~n~p",
1471		   [ConnectionRef,ChannelId0,erlang:process_info(self(),messages)]),
1472	    ct:fail("exit Timeout",[])
1473    end,
1474
1475    %%---- Try that we can open one channel instead of the closed one
1476    ?wait_match(success, ssh_connection:subsystem(ConnectionRef, ChannelId5, "echo_n", infinity)),
1477
1478    %%---- But not a fourth one...
1479    failure = ssh_connection:subsystem(ConnectionRef, ChannelId6, "echo_n", infinity),
1480
1481    ssh:close(ConnectionRef),
1482    ssh:stop_daemon(Pid).
1483
1484%%--------------------------------------------------------------------
1485%% Internal functions ------------------------------------------------
1486%%--------------------------------------------------------------------
1487
1488do_simple_exec(ConnectionRef) ->
1489    {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
1490    success = ssh_connection:exec(ConnectionRef, ChannelId0,
1491				  "echo testing", infinity),
1492    %% receive response to input
1493    receive
1494	{ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
1495	    ok
1496    after
1497	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1498    end,
1499
1500    %% receive close messages
1501    receive
1502	{ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
1503	    ok
1504    after
1505	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1506    end,
1507    receive
1508	{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
1509	    ok
1510    after
1511	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1512    end,
1513    receive
1514	{ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
1515	    ok
1516    after
1517	10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
1518    end.
1519
1520
1521%%--------------------------------------------------------------------
1522flush_msgs() ->
1523    receive
1524        _ -> flush_msgs()
1525    after
1526        500 -> ok
1527    end.
1528
1529%%--------------------------------------------------------------------
1530test_shell_is_disabled(ConnectionRef) ->
1531    test_shell_is_disabled(ConnectionRef, <<"Prohibited.">>, <<"Eshell V">>).
1532
1533test_shell_is_disabled(ConnectionRef, Expect, NotExpect) ->
1534    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1535    ok = ssh_connection:shell(ConnectionRef, ChannelId),
1536    ExpSz = size(Expect),
1537    NotExpSz = size(NotExpect),
1538    receive
1539        {ssh_cm, ConnectionRef, {data, ChannelId, 1, <<Expect:ExpSz/binary, _/binary>>}} ->
1540            flush_msgs();
1541
1542        {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<NotExpect:NotExpSz/binary, _/binary>>}} ->
1543            ct:fail("Could start disabled shell!");
1544
1545        R ->
1546            ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
1547                   [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, '0|1', Expect}} ]),
1548            ct:fail("Strange shell response")
1549
1550    after 5000 ->
1551            ct:fail("Shell Timeout")
1552    end.
1553
1554%%--------------------------------------------------------------------
1555test_exec_is_disabled(ConnectionRef) ->
1556    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1557    success = ssh_connection:exec(ConnectionRef, ChannelId, "1+2.", infinity),
1558    receive
1559        {ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ->
1560            flush_msgs();
1561        R ->
1562            ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
1563                   [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data,ChannelId,1,<<"Prohibited.">>}} ]),
1564            ct:fail("Could exec erlang term although non-erlang shell")
1565    after 5000 ->
1566            ct:fail("Exec Timeout")
1567    end.
1568
1569%%--------------------------------------------------------------------
1570test_shell_is_enabled(ConnectionRef) ->
1571    test_shell_is_enabled(ConnectionRef, <<"Eshell V">>).
1572
1573test_shell_is_enabled(ConnectionRef, Expect) ->
1574    test_shell_is_enabled(ConnectionRef, Expect, []).
1575
1576test_shell_is_enabled(ConnectionRef, Expect, PtyOpts) ->
1577    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1578    case PtyOpts of
1579        [] ->
1580            no_alloc;
1581        _ ->
1582            success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, PtyOpts)
1583    end,
1584    ok = ssh_connection:shell(ConnectionRef,ChannelId),
1585
1586    ExpSz = size(Expect),
1587    receive
1588	{ssh_cm,ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} ->
1589	    flush_msgs();
1590
1591        R ->
1592            ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
1593                   [?MODULE,?LINE,R, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ]),
1594            ct:fail("Strange shell response")
1595
1596    after 5000 ->
1597	    ct:fail("CLI Timeout")
1598    end.
1599
1600
1601test_exec_is_enabled(ConnectionRef) ->
1602    test_exec_is_enabled(ConnectionRef, "1+2.", <<"3">>).
1603
1604test_exec_is_enabled(ConnectionRef, Exec, Expect) ->
1605    {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
1606    success = ssh_connection:exec(ConnectionRef, ChannelId, Exec, infinity),
1607    ExpSz = size(Expect),
1608    receive
1609        {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} = R ->
1610            ct:log("~p:~p Got expected ~p",[?MODULE,?LINE,R]);
1611        Other ->
1612            ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
1613                   [?MODULE,?LINE, Other, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ])
1614    after 5000 ->
1615            {fail,"Exec Timeout"}
1616    end.
1617
1618%%%----------------------------------------------------------------
1619big_cat_rx(ConnectionRef, ChannelId) ->
1620    big_cat_rx(ConnectionRef, ChannelId, []).
1621
1622big_cat_rx(ConnectionRef, ChannelId, Acc) ->
1623    receive
1624	{ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
1625	    %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
1626	    %% window was pre-adjusted, don't adjust again here
1627	    big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
1628	{ssh_cm, ConnectionRef, {eof, ChannelId}} ->
1629	    {ok, iolist_to_binary(lists:reverse(Acc))}
1630    after ?EXEC_TIMEOUT ->
1631	    timeout
1632    end.
1633
1634collect_data(ConnectionRef, ChannelId, EchoSize) ->
1635    ct:log("~p:~p Listener ~p running! ConnectionRef=~p, ChannelId=~p",[?MODULE,?LINE,self(),ConnectionRef,ChannelId]),
1636    collect_data(ConnectionRef, ChannelId, EchoSize, [], 0).
1637
1638collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum) ->
1639    TO = 5000,
1640    receive
1641	{ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} when is_binary(Data) ->
1642	    ct:log("~p:~p collect_data: received ~p bytes. total ~p bytes,  want ~p more",
1643		   [?MODULE,?LINE,size(Data),Sum+size(Data),EchoSize-Sum]),
1644	    ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
1645	    collect_data(ConnectionRef, ChannelId, EchoSize, [Data | Acc], Sum+size(Data));
1646	{ssh_cm, ConnectionRef, Msg={eof, ChannelId}} ->
1647	    collect_data_report_end(Acc, Msg, EchoSize);
1648
1649	{ssh_cm, ConnectionRef, Msg={closed,ChannelId}} ->
1650	    collect_data_report_end(Acc, Msg, EchoSize);
1651
1652	Msg ->
1653	    ct:log("~p:~p collect_data: ***** unexpected message *****~n~p",[?MODULE,?LINE,Msg]),
1654	    collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
1655
1656    after TO ->
1657	    ct:log("~p:~p collect_data: ----- Nothing received for ~p seconds -----~n",[?MODULE,?LINE,TO]),
1658	    collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
1659    end.
1660
1661collect_data_report_end(Acc, Msg, EchoSize) ->
1662    try
1663	iolist_to_binary(lists:reverse(Acc))
1664    of
1665	Bin ->
1666	    ct:log("~p:~p collect_data: received ~p.~nGot in total ~p bytes,  want ~p more",
1667		   [?MODULE,?LINE,Msg,size(Bin),EchoSize,size(Bin)]),
1668	    Bin
1669    catch
1670	C:E ->
1671	    ct:log("~p:~p collect_data: received ~p.~nAcc is strange...~nException=~p:~p~nAcc=~p",
1672		   [?MODULE,?LINE,Msg,C,E,Acc]),
1673	    {error,{C,E}}
1674    end.
1675
1676%%%-------------------------------------------------------------------
1677%% This is taken from the ssh example code.
1678start_our_shell(_User, _Peer) ->
1679    spawn(fun() ->
1680		  io:format("Enter command\n")
1681		  %% Don't actually loop, just exit
1682          end).
1683
1684
1685ssh_exec_echo(Cmd) ->
1686    spawn(fun() ->
1687                  io:format("echo ~s\n", [Cmd])
1688          end).
1689
1690ssh_exec_echo(Cmd, User) ->
1691    spawn(fun() ->
1692                  io:format("echo ~s ~s\n",[User,Cmd])
1693          end).
1694