1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2008-2018. 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_options_SUITE). 24 25%%% This test suite tests different options for the ssh functions 26 27 28-include_lib("common_test/include/ct.hrl"). 29-include_lib("kernel/include/file.hrl"). 30-include("ssh_test_lib.hrl"). 31 32%%% Test cases 33-export([connectfun_disconnectfun_client/1, 34 disconnectfun_option_client/1, 35 disconnectfun_option_server/1, 36 id_string_no_opt_client/1, 37 id_string_no_opt_server/1, 38 id_string_own_string_client/1, 39 id_string_own_string_client_trail_space/1, 40 id_string_own_string_server/1, 41 id_string_own_string_server_trail_space/1, 42 id_string_random_client/1, 43 id_string_random_server/1, 44 max_sessions_sftp_start_channel_parallel/1, 45 max_sessions_sftp_start_channel_sequential/1, 46 max_sessions_ssh_connect_parallel/1, 47 max_sessions_ssh_connect_sequential/1, 48 server_password_option/1, 49 server_userpassword_option/1, 50 server_pwdfun_option/1, 51 server_pwdfun_4_option/1, 52 server_keyboard_interactive/1, 53 ssh_connect_arg4_timeout/1, 54 ssh_connect_negtimeout_parallel/1, 55 ssh_connect_negtimeout_sequential/1, 56 ssh_connect_nonegtimeout_connected_parallel/1, 57 ssh_connect_nonegtimeout_connected_sequential/1, 58 ssh_connect_timeout/1, connect/4, 59 ssh_daemon_minimal_remote_max_packet_size_option/1, 60 ssh_msg_debug_fun_option_client/1, 61 ssh_msg_debug_fun_option_server/1, 62 system_dir_option/1, 63 unexpectedfun_option_client/1, 64 unexpectedfun_option_server/1, 65 user_dir_option/1, 66 connectfun_disconnectfun_server/1, 67 hostkey_fingerprint_check/1, 68 hostkey_fingerprint_check_md5/1, 69 hostkey_fingerprint_check_sha/1, 70 hostkey_fingerprint_check_sha256/1, 71 hostkey_fingerprint_check_sha384/1, 72 hostkey_fingerprint_check_sha512/1, 73 hostkey_fingerprint_check_list/1, 74 save_accepted_host_option/1 75 ]). 76 77%%% Common test callbacks 78-export([suite/0, all/0, groups/0, 79 init_per_suite/1, end_per_suite/1, 80 init_per_group/2, end_per_group/2, 81 init_per_testcase/2, end_per_testcase/2 82 ]). 83 84 85-define(NEWLINE, <<"\r\n">>). 86 87%%-------------------------------------------------------------------- 88%% Common Test interface functions ----------------------------------- 89%%-------------------------------------------------------------------- 90 91suite() -> 92 [{ct_hooks,[ts_install_cth]}, 93 {timetrap,{seconds,30}}]. 94 95all() -> 96 [connectfun_disconnectfun_server, 97 connectfun_disconnectfun_client, 98 server_password_option, 99 server_userpassword_option, 100 server_pwdfun_option, 101 server_pwdfun_4_option, 102 server_keyboard_interactive, 103 {group, dir_options}, 104 ssh_connect_timeout, 105 ssh_connect_arg4_timeout, 106 ssh_daemon_minimal_remote_max_packet_size_option, 107 ssh_msg_debug_fun_option_client, 108 ssh_msg_debug_fun_option_server, 109 disconnectfun_option_server, 110 disconnectfun_option_client, 111 unexpectedfun_option_server, 112 unexpectedfun_option_client, 113 hostkey_fingerprint_check, 114 hostkey_fingerprint_check_md5, 115 hostkey_fingerprint_check_sha, 116 hostkey_fingerprint_check_sha256, 117 hostkey_fingerprint_check_sha384, 118 hostkey_fingerprint_check_sha512, 119 hostkey_fingerprint_check_list, 120 id_string_no_opt_client, 121 id_string_own_string_client, 122 id_string_own_string_client_trail_space, 123 id_string_random_client, 124 id_string_no_opt_server, 125 id_string_own_string_server, 126 id_string_own_string_server_trail_space, 127 id_string_random_server, 128 save_accepted_host_option, 129 {group, hardening_tests} 130 ]. 131 132groups() -> 133 [{hardening_tests, [], [ssh_connect_nonegtimeout_connected_parallel, 134 ssh_connect_nonegtimeout_connected_sequential, 135 ssh_connect_negtimeout_parallel, 136 ssh_connect_negtimeout_sequential, 137 max_sessions_ssh_connect_parallel, 138 max_sessions_ssh_connect_sequential, 139 max_sessions_sftp_start_channel_parallel, 140 max_sessions_sftp_start_channel_sequential 141 ]}, 142 {dir_options, [], [user_dir_option, 143 system_dir_option]} 144 ]. 145 146 147%%-------------------------------------------------------------------- 148init_per_suite(Config) -> 149 ?CHECK_CRYPTO(Config). 150 151end_per_suite(_Config) -> 152 ssh:stop(). 153 154%%-------------------------------------------------------------------- 155init_per_group(hardening_tests, Config) -> 156 DataDir = proplists:get_value(data_dir, Config), 157 PrivDir = proplists:get_value(priv_dir, Config), 158 ssh_test_lib:setup_dsa(DataDir, PrivDir), 159 ssh_test_lib:setup_rsa(DataDir, PrivDir), 160 Config; 161init_per_group(dir_options, Config) -> 162 PrivDir = proplists:get_value(priv_dir, Config), 163 %% Make unreadable dir: 164 Dir_unreadable = filename:join(PrivDir, "unread"), 165 ok = file:make_dir(Dir_unreadable), 166 {ok,F1} = file:read_file_info(Dir_unreadable), 167 ok = file:write_file_info(Dir_unreadable, 168 F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}), 169 %% Make readable file: 170 File_readable = filename:join(PrivDir, "file"), 171 ok = file:write_file(File_readable, <<>>), 172 173 %% Check: 174 case {file:read_file_info(Dir_unreadable), 175 file:read_file_info(File_readable)} of 176 {{ok, Id=#file_info{type=directory, access=Md}}, 177 {ok, If=#file_info{type=regular, access=Mf}}} -> 178 AccessOK = 179 case {Md, Mf} of 180 {read, _} -> false; 181 {read_write, _} -> false; 182 {_, read} -> true; 183 {_, read_write} -> true; 184 _ -> false 185 end, 186 187 case AccessOK of 188 true -> 189 %% Save: 190 [{unreadable_dir, Dir_unreadable}, 191 {readable_file, File_readable} 192 | Config]; 193 false -> 194 ct:log("File#file_info : ~p~n" 195 "Dir#file_info : ~p",[If,Id]), 196 {skip, "File or dir mode settings failed"} 197 end; 198 199 NotDirFile -> 200 ct:log("{Dir,File} -> ~p",[NotDirFile]), 201 {skip, "File/Dir creation failed"} 202 end; 203init_per_group(_, Config) -> 204 Config. 205 206end_per_group(_, Config) -> 207 Config. 208%%-------------------------------------------------------------------- 209init_per_testcase(_TestCase, Config) -> 210 ssh:start(), 211 %% Create a clean user_dir 212 UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), 213 ssh_test_lib:del_dirs(UserDir), 214 file:make_dir(UserDir), 215 [{user_dir,UserDir}|Config]. 216 217end_per_testcase(_TestCase, Config) -> 218 ssh:stop(), 219 ok. 220 221%%-------------------------------------------------------------------- 222%% Test Cases -------------------------------------------------------- 223%%-------------------------------------------------------------------- 224 225%%% validate to server that uses the 'password' option 226server_password_option(Config) when is_list(Config) -> 227 UserDir = proplists:get_value(user_dir, Config), 228 SysDir = proplists:get_value(data_dir, Config), 229 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 230 {user_dir, UserDir}, 231 {password, "morot"}]), 232 233 ConnectionRef = 234 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 235 {user, "foo"}, 236 {password, "morot"}, 237 {user_interaction, false}, 238 {user_dir, UserDir}]), 239 240 Reason = "Unable to connect using the available authentication methods", 241 242 {error, Reason} = 243 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 244 {user, "vego"}, 245 {password, "foo"}, 246 {user_interaction, false}, 247 {user_dir, UserDir}]), 248 249 ct:log("Test of wrong password: Error msg: ~p ~n", [Reason]), 250 251 ssh:close(ConnectionRef), 252 ssh:stop_daemon(Pid). 253 254%%-------------------------------------------------------------------- 255 256%%% validate to server that uses the 'password' option 257server_userpassword_option(Config) when is_list(Config) -> 258 UserDir = proplists:get_value(user_dir, Config), 259 SysDir = proplists:get_value(data_dir, Config), 260 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 261 {user_dir, UserDir}, 262 {user_passwords, [{"vego", "morot"}]}]), 263 264 ConnectionRef = 265 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 266 {user, "vego"}, 267 {password, "morot"}, 268 {user_interaction, false}, 269 {user_dir, UserDir}]), 270 ssh:close(ConnectionRef), 271 272 Reason = "Unable to connect using the available authentication methods", 273 274 {error, Reason} = 275 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 276 {user, "foo"}, 277 {password, "morot"}, 278 {user_interaction, false}, 279 {user_dir, UserDir}]), 280 {error, Reason} = 281 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 282 {user, "vego"}, 283 {password, "foo"}, 284 {user_interaction, false}, 285 {user_dir, UserDir}]), 286 ssh:stop_daemon(Pid). 287 288%%-------------------------------------------------------------------- 289%%% validate to server that uses the 'pwdfun' option 290server_pwdfun_option(Config) -> 291 UserDir = proplists:get_value(user_dir, Config), 292 SysDir = proplists:get_value(data_dir, Config), 293 CHKPWD = fun("foo",Pwd) -> Pwd=="bar"; 294 (_,_) -> false 295 end, 296 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 297 {user_dir, UserDir}, 298 {pwdfun,CHKPWD}]), 299 ConnectionRef = 300 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 301 {user, "foo"}, 302 {password, "bar"}, 303 {user_interaction, false}, 304 {user_dir, UserDir}]), 305 ssh:close(ConnectionRef), 306 307 Reason = "Unable to connect using the available authentication methods", 308 309 {error, Reason} = 310 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 311 {user, "foo"}, 312 {password, "morot"}, 313 {user_interaction, false}, 314 {user_dir, UserDir}]), 315 {error, Reason} = 316 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 317 {user, "vego"}, 318 {password, "foo"}, 319 {user_interaction, false}, 320 {user_dir, UserDir}]), 321 ssh:stop_daemon(Pid). 322 323 324%%-------------------------------------------------------------------- 325%%% validate to server that uses the 'pwdfun/4' option 326server_pwdfun_4_option(Config) -> 327 UserDir = proplists:get_value(user_dir, Config), 328 SysDir = proplists:get_value(data_dir, Config), 329 PWDFUN = fun("foo",Pwd,{_,_},undefined) -> Pwd=="bar"; 330 ("fie",Pwd,{_,_},undefined) -> {Pwd=="bar",new_state}; 331 ("bandit",_,_,_) -> disconnect; 332 (_,_,_,_) -> false 333 end, 334 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 335 {user_dir, UserDir}, 336 {pwdfun,PWDFUN}]), 337 ConnectionRef1 = 338 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 339 {user, "foo"}, 340 {password, "bar"}, 341 {user_interaction, false}, 342 {user_dir, UserDir}]), 343 ssh:close(ConnectionRef1), 344 345 ConnectionRef2 = 346 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 347 {user, "fie"}, 348 {password, "bar"}, 349 {user_interaction, false}, 350 {user_dir, UserDir}]), 351 ssh:close(ConnectionRef2), 352 353 Reason = "Unable to connect using the available authentication methods", 354 355 {error, Reason} = 356 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 357 {user, "foo"}, 358 {password, "morot"}, 359 {user_interaction, false}, 360 {user_dir, UserDir}]), 361 {error, Reason} = 362 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 363 {user, "fie"}, 364 {password, "morot"}, 365 {user_interaction, false}, 366 {user_dir, UserDir}]), 367 {error, Reason} = 368 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 369 {user, "vego"}, 370 {password, "foo"}, 371 {user_interaction, false}, 372 {user_dir, UserDir}]), 373 374 {error, Reason} = 375 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 376 {user, "bandit"}, 377 {password, "pwd breaking"}, 378 {user_interaction, false}, 379 {user_dir, UserDir}]), 380 ssh:stop_daemon(Pid). 381 382 383%%-------------------------------------------------------------------- 384server_keyboard_interactive(Config) -> 385 UserDir = proplists:get_value(user_dir, Config), 386 SysDir = proplists:get_value(data_dir, Config), 387 %% Test that the state works 388 Parent = self(), 389 PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true; 390 (_,P,_,S=undefined) -> Parent!{P,S},{false,1}; 391 (_,P,_,S) -> Parent!{P,S}, {false,S+1} 392 end, 393 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 394 {user_dir, UserDir}, 395 {auth_methods,"keyboard-interactive"}, 396 {pwdfun,PWDFUN}]), 397 398 %% Try with passwords "incorrect", "Bad again" and finally "bar" 399 KIFFUN = fun(_Name, _Instr, _PromptInfos) -> 400 K={k,self()}, 401 Answer = 402 case get(K) of 403 undefined -> 404 put(K,1), 405 ["incorrect"]; 406 2 -> 407 put(K,3), 408 ["bar"]; 409 S-> 410 put(K,S+1), 411 ["Bad again"] 412 end, 413 ct:log("keyboard_interact_fun:~n" 414 " Name = ~p~n" 415 " Instruction = ~p~n" 416 " Prompts = ~p~n" 417 "~nAnswer:~n ~p~n", 418 [_Name, _Instr, _PromptInfos, Answer]), 419 420 Answer 421 end, 422 423 ConnectionRef2 = 424 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 425 {user, "foo"}, 426 {keyboard_interact_fun, KIFFUN}, 427 {user_dir, UserDir}]), 428 ssh:close(ConnectionRef2), 429 ssh:stop_daemon(Pid), 430 431 lists:foreach(fun(Expect) -> 432 receive 433 Expect -> ok; 434 Other -> ct:fail("Expect: ~p~nReceived ~p",[Expect,Other]) 435 after 436 2000 -> ct:fail("Timeout expecting ~p",[Expect]) 437 end 438 end, [{"incorrect",undefined}, 439 {"Bad again",1}, 440 {"bar",2}]). 441 442%%-------------------------------------------------------------------- 443system_dir_option(Config) -> 444 DirUnread = proplists:get_value(unreadable_dir,Config), 445 FileRead = proplists:get_value(readable_file,Config), 446 447 case ssh_test_lib:daemon([{system_dir, DirUnread}]) of 448 {error,{eoptions,{{system_dir,DirUnread},eacces}}} -> 449 ok; 450 {Pid1,_Host1,Port1} when is_pid(Pid1),is_integer(Port1) -> 451 ssh:stop_daemon(Pid1), 452 ct:fail("Didn't detect that dir is unreadable", []) 453 end, 454 455 case ssh_test_lib:daemon([{system_dir, FileRead}]) of 456 {error,{eoptions,{{system_dir,FileRead},enotdir}}} -> 457 ok; 458 {Pid2,_Host2,Port2} when is_pid(Pid2),is_integer(Port2) -> 459 ssh:stop_daemon(Pid2), 460 ct:fail("Didn't detect that option is a plain file", []) 461 end. 462 463 464user_dir_option(Config) -> 465 DirUnread = proplists:get_value(unreadable_dir,Config), 466 FileRead = proplists:get_value(readable_file,Config), 467 %% Any port will do (beware, implementation knowledge!): 468 Port = 65535, 469 470 case ssh:connect("localhost", Port, [{user_dir, DirUnread}]) of 471 {error,{eoptions,{{user_dir,DirUnread},eacces}}} -> 472 ok; 473 {error,econnrefused} -> 474 ct:fail("Didn't detect that dir is unreadable", []) 475 end, 476 477 case ssh:connect("localhost", Port, [{user_dir, FileRead}]) of 478 {error,{eoptions,{{user_dir,FileRead},enotdir}}} -> 479 ok; 480 {error,econnrefused} -> 481 ct:fail("Didn't detect that option is a plain file", []) 482 end. 483 484%%-------------------------------------------------------------------- 485%%% validate client that uses the 'ssh_msg_debug_fun' option 486ssh_msg_debug_fun_option_client(Config) -> 487 UserDir = proplists:get_value(user_dir, Config), 488 SysDir = proplists:get_value(data_dir, Config), 489 490 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 491 {user_dir, UserDir}, 492 {password, "morot"}, 493 {failfun, fun ssh_test_lib:failfun/2}]), 494 Parent = self(), 495 DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, 496 497 ConnectionRef = 498 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 499 {user, "foo"}, 500 {password, "morot"}, 501 {user_dir, UserDir}, 502 {user_interaction, false}, 503 {ssh_msg_debug_fun,DbgFun}]), 504 %% Beware, implementation knowledge: 505 gen_statem:cast(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), 506 receive 507 {msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} -> 508 ct:log("Got expected dbg msg ~p",[X]), 509 ssh:stop_daemon(Pid); 510 {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> 511 ct:log("Got dbg msg but bad ConnectionRef (~p expected) ~p",[ConnectionRef,X]), 512 ssh:stop_daemon(Pid), 513 {fail, "Bad ConnectionRef received"}; 514 {msg_dbg,X} -> 515 ct:log("Got bad dbg msg ~p",[X]), 516 ssh:stop_daemon(Pid), 517 {fail,"Bad msg received"} 518 after 1000 -> 519 ssh:stop_daemon(Pid), 520 {fail,timeout} 521 end. 522 523%%-------------------------------------------------------------------- 524connectfun_disconnectfun_server(Config) -> 525 UserDir = proplists:get_value(user_dir, Config), 526 SysDir = proplists:get_value(data_dir, Config), 527 528 Parent = self(), 529 Ref = make_ref(), 530 ConnFun = fun(_,_,_) -> Parent ! {connect,Ref} end, 531 DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, 532 533 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 534 {user_dir, UserDir}, 535 {password, "morot"}, 536 {failfun, fun ssh_test_lib:failfun/2}, 537 {disconnectfun, DiscFun}, 538 {connectfun, ConnFun}]), 539 ConnectionRef = 540 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 541 {user, "foo"}, 542 {password, "morot"}, 543 {user_dir, UserDir}, 544 {user_interaction, false}]), 545 receive 546 {connect,Ref} -> 547 ssh:close(ConnectionRef), 548 receive 549 {disconnect,Ref,R} -> 550 ct:log("Disconnect result: ~p",[R]), 551 ssh:stop_daemon(Pid) 552 after 10000 -> 553 receive 554 X -> ct:log("received ~p",[X]) 555 after 0 -> ok 556 end, 557 {fail, "No disconnectfun action"} 558 end 559 after 10000 -> 560 receive 561 X -> ct:log("received ~p",[X]) 562 after 0 -> ok 563 end, 564 {fail, "No connectfun action"} 565 end. 566 567%%-------------------------------------------------------------------- 568connectfun_disconnectfun_client(Config) -> 569 UserDir = proplists:get_value(user_dir, Config), 570 SysDir = proplists:get_value(data_dir, Config), 571 572 Parent = self(), 573 Ref = make_ref(), 574 DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, 575 576 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 577 {user_dir, UserDir}, 578 {password, "morot"}, 579 {failfun, fun ssh_test_lib:failfun/2}]), 580 _ConnectionRef = 581 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 582 {user, "foo"}, 583 {password, "morot"}, 584 {user_dir, UserDir}, 585 {disconnectfun, DiscFun}, 586 {user_interaction, false}]), 587 ssh:stop_daemon(Pid), 588 receive 589 {disconnect,Ref,R} -> 590 ct:log("Disconnect result: ~p",[R]) 591 after 2000 -> 592 {fail, "No disconnectfun action"} 593 end. 594 595%%-------------------------------------------------------------------- 596%%% validate client that uses the 'ssh_msg_debug_fun' option 597ssh_msg_debug_fun_option_server(Config) -> 598 UserDir = proplists:get_value(user_dir, Config), 599 SysDir = proplists:get_value(data_dir, Config), 600 601 Parent = self(), 602 DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, 603 ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, 604 605 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 606 {user_dir, UserDir}, 607 {password, "morot"}, 608 {failfun, fun ssh_test_lib:failfun/2}, 609 {connectfun, ConnFun}, 610 {ssh_msg_debug_fun, DbgFun}]), 611 _ConnectionRef = 612 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 613 {user, "foo"}, 614 {password, "morot"}, 615 {user_dir, UserDir}, 616 {user_interaction, false}]), 617 receive 618 {connection_pid,Server} -> 619 %% Beware, implementation knowledge: 620 gen_statem:cast(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), 621 receive 622 {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> 623 ct:log("Got expected dbg msg ~p",[X]), 624 ssh:stop_daemon(Pid); 625 {msg_dbg,X} -> 626 ct:log("Got bad dbg msg ~p",[X]), 627 ssh:stop_daemon(Pid), 628 {fail,"Bad msg received"} 629 after 3000 -> 630 ssh:stop_daemon(Pid), 631 {fail,timeout2} 632 end 633 after 3000 -> 634 ssh:stop_daemon(Pid), 635 {fail,timeout1} 636 end. 637 638%%-------------------------------------------------------------------- 639disconnectfun_option_server(Config) -> 640 UserDir = proplists:get_value(user_dir, Config), 641 SysDir = proplists:get_value(data_dir, Config), 642 643 Parent = self(), 644 DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, 645 646 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 647 {user_dir, UserDir}, 648 {password, "morot"}, 649 {failfun, fun ssh_test_lib:failfun/2}, 650 {disconnectfun, DisConnFun}]), 651 ConnectionRef = 652 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 653 {user, "foo"}, 654 {password, "morot"}, 655 {user_dir, UserDir}, 656 {user_interaction, false}]), 657 ssh:close(ConnectionRef), 658 receive 659 {disconnect,Reason} -> 660 ct:log("Server detected disconnect: ~p",[Reason]), 661 ssh:stop_daemon(Pid), 662 ok 663 after 5000 -> 664 receive 665 X -> ct:log("received ~p",[X]) 666 after 0 -> ok 667 end, 668 {fail,"Timeout waiting for disconnect"} 669 end. 670 671%%-------------------------------------------------------------------- 672disconnectfun_option_client(Config) -> 673 UserDir = proplists:get_value(user_dir, Config), 674 SysDir = proplists:get_value(data_dir, Config), 675 676 Parent = self(), 677 DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, 678 679 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 680 {user_dir, UserDir}, 681 {password, "morot"}, 682 {failfun, fun ssh_test_lib:failfun/2}]), 683 _ConnectionRef = 684 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 685 {user, "foo"}, 686 {password, "morot"}, 687 {user_dir, UserDir}, 688 {user_interaction, false}, 689 {disconnectfun, DisConnFun}]), 690 ssh:stop_daemon(Pid), 691 receive 692 {disconnect,Reason} -> 693 ct:log("Client detected disconnect: ~p",[Reason]), 694 ok 695 after 3000 -> 696 receive 697 X -> ct:log("received ~p",[X]) 698 after 0 -> ok 699 end, 700 {fail,"Timeout waiting for disconnect"} 701 end. 702 703%%-------------------------------------------------------------------- 704unexpectedfun_option_server(Config) -> 705 UserDir = proplists:get_value(user_dir, Config), 706 SysDir = proplists:get_value(data_dir, Config), 707 708 Parent = self(), 709 ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, 710 UnexpFun = fun(Msg,Peer) -> 711 Parent ! {unexpected,Msg,Peer,self()}, 712 skip 713 end, 714 715 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 716 {user_dir, UserDir}, 717 {password, "morot"}, 718 {failfun, fun ssh_test_lib:failfun/2}, 719 {connectfun, ConnFun}, 720 {unexpectedfun, UnexpFun}]), 721 _ConnectionRef = 722 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 723 {user, "foo"}, 724 {password, "morot"}, 725 {user_dir, UserDir}, 726 {user_interaction, false}]), 727 receive 728 {connection_pid,Server} -> 729 %% Beware, implementation knowledge: 730 Server ! unexpected_message, 731 receive 732 {unexpected, unexpected_message, {{_,_,_,_},_}, _} -> ok; 733 {unexpected, unexpected_message, Peer, _} -> ct:fail("Bad peer ~p",[Peer]); 734 M = {unexpected, _, _, _} -> ct:fail("Bad msg ~p",[M]) 735 after 3000 -> 736 ssh:stop_daemon(Pid), 737 {fail,timeout2} 738 end 739 after 3000 -> 740 ssh:stop_daemon(Pid), 741 {fail,timeout1} 742 end. 743 744%%-------------------------------------------------------------------- 745unexpectedfun_option_client(Config) -> 746 UserDir = proplists:get_value(user_dir, Config), 747 SysDir = proplists:get_value(data_dir, Config), 748 749 Parent = self(), 750 UnexpFun = fun(Msg,Peer) -> 751 Parent ! {unexpected,Msg,Peer,self()}, 752 skip 753 end, 754 755 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 756 {user_dir, UserDir}, 757 {password, "morot"}, 758 {failfun, fun ssh_test_lib:failfun/2}]), 759 ConnectionRef = 760 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 761 {user, "foo"}, 762 {password, "morot"}, 763 {user_dir, UserDir}, 764 {user_interaction, false}, 765 {unexpectedfun, UnexpFun}]), 766 %% Beware, implementation knowledge: 767 ConnectionRef ! unexpected_message, 768 769 receive 770 {unexpected, unexpected_message, {{_,_,_,_},_}, ConnectionRef} -> 771 ok; 772 {unexpected, unexpected_message, Peer, ConnectionRef} -> 773 ct:fail("Bad peer ~p",[Peer]); 774 M = {unexpected, _, _, _} -> 775 ct:fail("Bad msg ~p",[M]) 776 after 3000 -> 777 ssh:stop_daemon(Pid), 778 {fail,timeout} 779 end. 780 781%%-------------------------------------------------------------------- 782hostkey_fingerprint_check(Config) -> 783 do_hostkey_fingerprint_check(Config, old). 784 785hostkey_fingerprint_check_md5(Config) -> 786 do_hostkey_fingerprint_check(Config, md5). 787 788hostkey_fingerprint_check_sha(Config) -> 789 do_hostkey_fingerprint_check(Config, sha). 790 791hostkey_fingerprint_check_sha256(Config) -> 792 do_hostkey_fingerprint_check(Config, sha256). 793 794hostkey_fingerprint_check_sha384(Config) -> 795 do_hostkey_fingerprint_check(Config, sha384). 796 797hostkey_fingerprint_check_sha512(Config) -> 798 do_hostkey_fingerprint_check(Config, sha512). 799 800hostkey_fingerprint_check_list(Config) -> 801 do_hostkey_fingerprint_check(Config, [sha,md5,sha256]). 802 803%%%---- 804do_hostkey_fingerprint_check(Config, HashAlg) -> 805 case supported_hash(HashAlg) of 806 true -> 807 really_do_hostkey_fingerprint_check(Config, HashAlg); 808 false -> 809 {skip,{unsupported_hash,HashAlg}} 810 end. 811 812supported_hash(old) -> true; 813supported_hash(HashAlg) -> 814 Hs = if is_atom(HashAlg) -> [HashAlg]; 815 is_list(HashAlg) -> HashAlg 816 end, 817 [] == (Hs -- proplists:get_value(hashs, crypto:supports(), [])). 818 819 820really_do_hostkey_fingerprint_check(Config, HashAlg) -> 821 UserDir = proplists:get_value(user_dir, Config), 822 SysDir = proplists:get_value(data_dir, Config), 823 824 %% All host key fingerprints. Trust that public_key has checked the ssh_hostkey_fingerprint 825 %% function since that function is used by the ssh client... 826 FPs0 = [case HashAlg of 827 old -> public_key:ssh_hostkey_fingerprint(Key); 828 _ -> public_key:ssh_hostkey_fingerprint(HashAlg, Key) 829 end 830 || FileCandidate <- begin 831 {ok,KeyFileCands} = file:list_dir(SysDir), 832 KeyFileCands 833 end, 834 nomatch =/= re:run(FileCandidate, ".*\\.pub", []), 835 {Key,_Cmnts} <- begin 836 {ok,Bin} = file:read_file(filename:join(SysDir, FileCandidate)), 837 try public_key:ssh_decode(Bin, public_key) 838 catch 839 _:_ -> [] 840 end 841 end], 842 FPs = if is_atom(HashAlg) -> FPs0; 843 is_list(HashAlg) -> lists:concat(FPs0) 844 end, 845 ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]), 846 847 %% Start daemon with the public keys that we got fingerprints from 848 {Pid, Host0, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 849 {user_dir, UserDir}, 850 {password, "morot"}]), 851 Host = ssh_test_lib:ntoa(Host0), 852 FP_check_fun = fun(PeerName, FP) -> 853 ct:log("PeerName = ~p, FP = ~p",[PeerName,FP]), 854 HostCheck = ssh_test_lib:match_ip(Host, PeerName), 855 FPCheck = 856 if is_atom(HashAlg) -> lists:member(FP, FPs); 857 is_list(HashAlg) -> lists:all(fun(FP1) -> lists:member(FP1,FPs) end, 858 FP) 859 end, 860 ct:log("check ~p == ~p (~p) and ~n~p~n in ~p (~p)~n", 861 [PeerName,Host,HostCheck,FP,FPs,FPCheck]), 862 HostCheck and FPCheck 863 end, 864 865 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, 866 case HashAlg of 867 old -> FP_check_fun; 868 _ -> {HashAlg, FP_check_fun} 869 end}, 870 {user, "foo"}, 871 {password, "morot"}, 872 {user_dir, UserDir}, 873 {save_accepted_host, false}, % Ensure no 'known_hosts' disturbs 874 {user_interaction, false}]), 875 ssh:stop_daemon(Pid). 876 877%%-------------------------------------------------------------------- 878%%% Test connect_timeout option in ssh:connect/4 879ssh_connect_timeout(_Config) -> 880 ConnTimeout = 2000, 881 {error,{faked_transport,connect,TimeoutToTransport}} = 882 ssh:connect("localhost", 12345, 883 [{transport,{tcp,?MODULE,tcp_closed}}, 884 {connect_timeout,ConnTimeout}], 885 1000), 886 case TimeoutToTransport of 887 ConnTimeout -> ok; 888 Other -> 889 ct:log("connect_timeout is ~p but transport received ~p",[ConnTimeout,Other]), 890 {fail,"ssh:connect/4 wrong connect_timeout received in transport"} 891 end. 892 893%% Plugin function for the test above 894connect(_Host, _Port, _Opts, Timeout) -> 895 {error, {faked_transport,connect,Timeout}}. 896 897%%-------------------------------------------------------------------- 898%%% Test fourth argument in ssh:connect/4 899ssh_connect_arg4_timeout(_Config) -> 900 Timeout = 1000, 901 Parent = self(), 902 %% start the server 903 Server = spawn(fun() -> 904 {ok,Sl} = gen_tcp:listen(0,[]), 905 {ok,{_,Port}} = inet:sockname(Sl), 906 Parent ! {port,self(),Port}, 907 Rsa = gen_tcp:accept(Sl), 908 ct:log("Server gen_tcp:accept got ~p",[Rsa]), 909 receive after 2*Timeout -> ok end %% let client timeout first 910 end), 911 912 %% Get listening port 913 Port = receive 914 {port,Server,ServerPort} -> ServerPort 915 after 916 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 917 end, 918 919 %% try to connect with a timeout, but "supervise" it 920 Client = spawn(fun() -> 921 T0 = erlang:monotonic_time(), 922 Rc = ssh:connect("localhost",Port,[],Timeout), 923 ct:log("Client ssh:connect got ~p",[Rc]), 924 Parent ! {done,self(),Rc,T0} 925 end), 926 927 %% Wait for client reaction on the connection try: 928 receive 929 {done, Client, {error,timeout}, T0} -> 930 Msp = ms_passed(T0), 931 exit(Server,hasta_la_vista___baby), 932 Low = 0.9*Timeout, 933 High = 2.5*Timeout, 934 ct:log("Timeout limits: ~.4f - ~.4f ms, timeout " 935 "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]), 936 if 937 Low<Msp, Msp<High -> ok; 938 true -> {fail, "timeout not within limits"} 939 end; 940 941 {done, Client, {error,Other}, _T0} -> 942 ct:log("Error message \"~p\" from the client is unexpected.",[{error,Other}]), 943 {fail, "Unexpected error message"}; 944 945 {done, Client, {ok,_Ref}, _T0} -> 946 {fail,"ssh-connected ???"} 947 after 948 5000 -> 949 exit(Server,hasta_la_vista___baby), 950 exit(Client,hasta_la_vista___baby), 951 {fail, "Didn't timeout"} 952 end. 953 954%% Help function, elapsed milliseconds since T0 955ms_passed(T0) -> 956 %% OTP 18 957 erlang:convert_time_unit(erlang:monotonic_time() - T0, 958 native, 959 micro_seconds) / 1000. 960 961%%-------------------------------------------------------------------- 962ssh_daemon_minimal_remote_max_packet_size_option(Config) -> 963 SystemDir = proplists:get_value(data_dir, Config), 964 UserDir = proplists:get_value(user_dir, Config), 965 966 {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, 967 {user_dir, UserDir}, 968 {user_passwords, [{"vego", "morot"}]}, 969 {failfun, fun ssh_test_lib:failfun/2}, 970 {minimal_remote_max_packet_size, 14}]), 971 Conn = 972 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 973 {user_dir, UserDir}, 974 {user_interaction, false}, 975 {user, "vego"}, 976 {password, "morot"}]), 977 978 %% Try the limits of the minimal_remote_max_packet_size: 979 {ok, _ChannelId} = ssh_connection:session_channel(Conn, 100, 14, infinity), 980 {open_error,_,"Maximum packet size below 14 not supported",_} = 981 ssh_connection:session_channel(Conn, 100, 13, infinity), 982 983 ssh:close(Conn), 984 ssh:stop_daemon(Server). 985 986%%-------------------------------------------------------------------- 987%% This test try every algorithm by connecting to an Erlang server 988id_string_no_opt_client(Config) -> 989 {Server, _Host, Port} = fake_daemon(Config), 990 {error,_} = ssh:connect("localhost", Port, [], 1000), 991 receive 992 {id,Server,"SSH-2.0-Erlang/"++Vsn} -> 993 true = expected_ssh_vsn(Vsn); 994 {id,Server,Other} -> 995 ct:fail("Unexpected id: ~s.",[Other]) 996 after 5000 -> 997 {fail,timeout} 998 end. 999 1000%%-------------------------------------------------------------------- 1001id_string_own_string_client(Config) -> 1002 {Server, _Host, Port} = fake_daemon(Config), 1003 {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}], 1000), 1004 receive 1005 {id,Server,"SSH-2.0-Pelle\r\n"} -> 1006 ok; 1007 {id,Server,Other} -> 1008 ct:fail("Unexpected id: ~s.",[Other]) 1009 after 5000 -> 1010 {fail,timeout} 1011 end. 1012 1013%%-------------------------------------------------------------------- 1014id_string_own_string_client_trail_space(Config) -> 1015 {Server, _Host, Port} = fake_daemon(Config), 1016 {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle "}], 1000), 1017 receive 1018 {id,Server,"SSH-2.0-Pelle \r\n"} -> 1019 ok; 1020 {id,Server,Other} -> 1021 ct:fail("Unexpected id: ~s.",[Other]) 1022 after 5000 -> 1023 {fail,timeout} 1024 end. 1025 1026%%-------------------------------------------------------------------- 1027id_string_random_client(Config) -> 1028 {Server, _Host, Port} = fake_daemon(Config), 1029 {error,_} = ssh:connect("localhost", Port, [{id_string,random}], 1000), 1030 receive 1031 {id,Server,Id="SSH-2.0-Erlang"++_} -> 1032 ct:fail("Unexpected id: ~s.",[Id]); 1033 {id,Server,Rnd="SSH-2.0-"++_} -> 1034 ct:log("Got correct ~s",[Rnd]); 1035 {id,Server,Id} -> 1036 ct:fail("Unexpected id: ~s.",[Id]) 1037 after 5000 -> 1038 {fail,timeout} 1039 end. 1040 1041%%-------------------------------------------------------------------- 1042id_string_no_opt_server(Config) -> 1043 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, []), 1044 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1045 {ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000), 1046 true = expected_ssh_vsn(Vsn). 1047 1048%%-------------------------------------------------------------------- 1049id_string_own_string_server(Config) -> 1050 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,"Olle"}]), 1051 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1052 {ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000). 1053 1054%%-------------------------------------------------------------------- 1055id_string_own_string_server_trail_space(Config) -> 1056 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,"Olle "}]), 1057 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1058 {ok,"SSH-2.0-Olle \r\n"} = gen_tcp:recv(S1, 0, 2000). 1059 1060%%-------------------------------------------------------------------- 1061id_string_random_server(Config) -> 1062 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,random}]), 1063 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1064 {ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000), 1065 case Rnd of 1066 "Erlang"++_ -> ct:log("Id=~p",[Rnd]), 1067 {fail,got_default_id}; 1068 "Olle\r\n" -> {fail,got_previous_tests_value}; 1069 _ -> ct:log("Got ~s.",[Rnd]) 1070 end. 1071 1072%%-------------------------------------------------------------------- 1073ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true). 1074ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false). 1075 1076ssh_connect_negtimeout(Config, Parallel) -> 1077 process_flag(trap_exit, true), 1078 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1079 UserDir = proplists:get_value(priv_dir, Config), 1080 NegTimeOut = 2000, % ms 1081 ct:log("Parallel: ~p",[Parallel]), 1082 1083 {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, 1084 {parallel_login, Parallel}, 1085 {negotiation_timeout, NegTimeOut}, 1086 {failfun, fun ssh_test_lib:failfun/2}]), 1087 1088 {ok,Socket} = ssh_test_lib:gen_tcp_connect(Host, Port, []), 1089 1090 Factor = 2, 1091 ct:log("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), 1092 ct:sleep(round(Factor * NegTimeOut)), 1093 1094 case inet:sockname(Socket) of 1095 {ok,_} -> 1096 %% Give it another chance... 1097 ct:log("Sleep more...",[]), 1098 ct:sleep(round(Factor * NegTimeOut)), 1099 case inet:sockname(Socket) of 1100 {ok,_} -> ct:fail("Socket not closed"); 1101 {error,_} -> ok 1102 end; 1103 {error,_} -> ok 1104 end. 1105 1106%%-------------------------------------------------------------------- 1107%%% Test that ssh connection does not timeout if the connection is established (parallel) 1108ssh_connect_nonegtimeout_connected_parallel(Config) -> 1109 ssh_connect_nonegtimeout_connected(Config, true). 1110 1111%%% Test that ssh connection does not timeout if the connection is established (non-parallel) 1112ssh_connect_nonegtimeout_connected_sequential(Config) -> 1113 ssh_connect_nonegtimeout_connected(Config, false). 1114 1115 1116ssh_connect_nonegtimeout_connected(Config, Parallel) -> 1117 process_flag(trap_exit, true), 1118 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1119 UserDir = proplists:get_value(priv_dir, Config), 1120 NegTimeOut = 2000, % ms 1121 ct:log("Parallel: ~p",[Parallel]), 1122 1123 {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, 1124 {parallel_login, Parallel}, 1125 {negotiation_timeout, NegTimeOut}, 1126 {failfun, fun ssh_test_lib:failfun/2}]), 1127 ct:log("~p Listen ~p:~p",[_Pid,_Host,Port]), 1128 ct:sleep(500), 1129 1130 IO = ssh_test_lib:start_io_server(), 1131 Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]), 1132 receive 1133 Error = {'EXIT', _, _} -> 1134 ct:log("~p",[Error]), 1135 ct:fail(no_ssh_connection); 1136 ErlShellStart -> 1137 ct:log("---Erlang shell start: ~p~n", [ErlShellStart]), 1138 one_shell_op(IO, NegTimeOut), 1139 one_shell_op(IO, NegTimeOut), 1140 1141 Factor = 2, 1142 ct:log("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), 1143 ct:sleep(round(Factor * NegTimeOut)), 1144 1145 one_shell_op(IO, NegTimeOut) 1146 after 1147 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1148 end, 1149 exit(Shell, kill). 1150 1151 1152one_shell_op(IO, TimeOut) -> 1153 ct:log("One shell op: Waiting for prompter"), 1154 receive 1155 ErlPrompt0 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt0]) 1156 after TimeOut -> ct:fail("Timeout waiting for promter") 1157 end, 1158 1159 IO ! {input, self(), "2*3*7.\r\n"}, 1160 receive 1161 Echo0 -> ct:log("Echo: ~p ~n", [Echo0]) 1162 after TimeOut -> ct:fail("Timeout waiting for echo") 1163 end, 1164 1165 receive 1166 ?NEWLINE -> ct:log("NEWLINE received", []) 1167 after TimeOut -> 1168 receive Any1 -> ct:log("Bad NEWLINE: ~p",[Any1]) 1169 after 0 -> ct:fail("Timeout waiting for NEWLINE") 1170 end 1171 end, 1172 1173 receive 1174 Result0 -> ct:log("Result: ~p~n", [Result0]) 1175 after TimeOut -> ct:fail("Timeout waiting for result") 1176 end. 1177 1178%%-------------------------------------------------------------------- 1179max_sessions_ssh_connect_parallel(Config) -> 1180 max_sessions(Config, true, connect_fun(ssh__connect,Config)). 1181max_sessions_ssh_connect_sequential(Config) -> 1182 max_sessions(Config, false, connect_fun(ssh__connect,Config)). 1183 1184max_sessions_sftp_start_channel_parallel(Config) -> 1185 max_sessions(Config, true, connect_fun(ssh_sftp__start_channel, Config)). 1186max_sessions_sftp_start_channel_sequential(Config) -> 1187 max_sessions(Config, false, connect_fun(ssh_sftp__start_channel, Config)). 1188 1189 1190%%%---- helpers: 1191connect_fun(ssh__connect, Config) -> 1192 fun(Host,Port) -> 1193 ssh_test_lib:connect(Host, Port, 1194 [{silently_accept_hosts, true}, 1195 {user_dir, proplists:get_value(priv_dir,Config)}, 1196 {user_interaction, false}, 1197 {user, "carni"}, 1198 {password, "meat"} 1199 ]) 1200 %% ssh_test_lib returns R when ssh:connect returns {ok,R} 1201 end; 1202connect_fun(ssh_sftp__start_channel, _Config) -> 1203 fun(Host,Port) -> 1204 {ok,_Pid,ConnRef} = 1205 ssh_sftp:start_channel(Host, Port, 1206 [{silently_accept_hosts, true}, 1207 {user, "carni"}, 1208 {password, "meat"} 1209 ]), 1210 ConnRef 1211 end. 1212 1213 1214max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> 1215 Connect = fun(Host,Port) -> 1216 R = Connect0(Host,Port), 1217 ct:log("Connect(~p,~p) -> ~p",[Host,Port,R]), 1218 R 1219 end, 1220 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1221 UserDir = proplists:get_value(priv_dir, Config), 1222 MaxSessions = 5, 1223 {Pid, Host, Port} = ssh_test_lib:daemon([ 1224 {system_dir, SystemDir}, 1225 {user_dir, UserDir}, 1226 {user_passwords, [{"carni", "meat"}]}, 1227 {parallel_login, ParallelLogin}, 1228 {max_sessions, MaxSessions} 1229 ]), 1230 ct:log("~p Listen ~p:~p for max ~p sessions",[Pid,Host,Port,MaxSessions]), 1231 try [Connect(Host,Port) || _ <- lists:seq(1,MaxSessions)] 1232 of 1233 Connections -> 1234 %% Step 1 ok: could set up max_sessions connections 1235 ct:log("Connections up: ~p",[Connections]), 1236 [_|_] = Connections, 1237 1238 %% Now try one more than alowed: 1239 ct:pal("Info Report expected here (if not disabled) ...",[]), 1240 try Connect(Host,Port) 1241 of 1242 _ConnectionRef1 -> 1243 ssh:stop_daemon(Pid), 1244 {fail,"Too many connections accepted"} 1245 catch 1246 error:{badmatch,{error,"Connection closed"}} -> 1247 ct:log("Step 2 ok: could not set up too many connections. Good.",[]), 1248 %% Now stop one connection and try to open one more 1249 ok = ssh:close(hd(Connections)), 1250 try_to_connect(Connect, Host, Port, Pid) 1251 end 1252 catch 1253 error:{badmatch,{error,"Connection closed"}} -> 1254 ssh:stop_daemon(Pid), 1255 {fail,"Too few connections accepted"} 1256 end. 1257 1258 1259try_to_connect(Connect, Host, Port, Pid) -> 1260 {ok,Tref} = timer:send_after(30000, timeout_no_connection), % give the supervisors some time... 1261 try_to_connect(Connect, Host, Port, Pid, Tref, 1). % will take max 3300 ms after 11 tries 1262 1263try_to_connect(Connect, Host, Port, Pid, Tref, N) -> 1264 try Connect(Host,Port) 1265 of 1266 _ConnectionRef1 -> 1267 timer:cancel(Tref), 1268 ct:log("Step 3 ok: could set up one more connection after killing one. Thats good.",[]), 1269 ssh:stop_daemon(Pid), 1270 receive % flush. 1271 timeout_no_connection -> ok 1272 after 0 -> ok 1273 end 1274 catch 1275 error:{badmatch,{error,"Connection closed"}} -> 1276 %% Could not set up one more connection. Try again until timeout. 1277 receive 1278 timeout_no_connection -> 1279 ssh:stop_daemon(Pid), 1280 {fail,"Does not decrease # active sessions"} 1281 after N*50 -> % retry after this time 1282 try_to_connect(Connect, Host, Port, Pid, Tref, N+1) 1283 end 1284 end. 1285 1286%%-------------------------------------------------------------------- 1287save_accepted_host_option(Config) -> 1288 UserDir = proplists:get_value(user_dir, Config), 1289 KnownHosts = filename:join(UserDir, "known_hosts"), 1290 SysDir = proplists:get_value(data_dir, Config), 1291 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 1292 {user_dir, UserDir}, 1293 {user_passwords, [{"vego", "morot"}]} 1294 ]), 1295 {error,enoent} = file:read_file(KnownHosts), 1296 1297 {ok,_C1} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 1298 {user, "vego"}, 1299 {password, "morot"}, 1300 {user_interaction, false}, 1301 {save_accepted_host, false}, 1302 {user_dir, UserDir}]), 1303 {error,enoent} = file:read_file(KnownHosts), 1304 1305 {ok,_C2} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 1306 {user, "vego"}, 1307 {password, "morot"}, 1308 {user_interaction, false}, 1309 {user_dir, UserDir}]), 1310 {ok,_} = file:read_file(KnownHosts), 1311 ssh:stop_daemon(Pid). 1312 1313%%-------------------------------------------------------------------- 1314%% Internal functions ------------------------------------------------ 1315%%-------------------------------------------------------------------- 1316 1317expected_ssh_vsn(Str) -> 1318 try 1319 {ok,L} = application:get_all_key(ssh), 1320 proplists:get_value(vsn,L,"")++"\r\n" 1321 of 1322 Str -> true; 1323 "\r\n" -> true; 1324 _ -> false 1325 catch 1326 _:_ -> true %% ssh not started so we dont't know 1327 end. 1328 1329 1330fake_daemon(_Config) -> 1331 Parent = self(), 1332 %% start the server 1333 Server = spawn(fun() -> 1334 {ok,Sl} = gen_tcp:listen(0,[{packet,line}]), 1335 {ok,{Host,Port}} = inet:sockname(Sl), 1336 ct:log("fake_daemon listening on ~p:~p~n",[Host,Port]), 1337 Parent ! {sockname,self(),Host,Port}, 1338 Rsa = gen_tcp:accept(Sl), 1339 ct:log("Server gen_tcp:accept got ~p",[Rsa]), 1340 {ok,S} = Rsa, 1341 receive 1342 {tcp, S, Id} -> Parent ! {id,self(),Id} 1343 after 1344 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1345 end 1346 end), 1347 %% Get listening host and port 1348 receive 1349 {sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort} 1350 after 1351 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1352 end. 1353