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