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