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