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_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([ 34 auth_method_kb_interactive_data_tuple/1, 35 auth_method_kb_interactive_data_fun3/1, 36 auth_method_kb_interactive_data_fun4/1, 37 connectfun_disconnectfun_client/1, 38 disconnectfun_option_client/1, 39 disconnectfun_option_server/1, 40 id_string_no_opt_client/1, 41 id_string_no_opt_server/1, 42 id_string_own_string_client/1, 43 id_string_own_string_client_trail_space/1, 44 id_string_own_string_server/1, 45 id_string_own_string_server_trail_space/1, 46 id_string_random_client/1, 47 id_string_random_server/1, 48 max_sessions_sftp_start_channel_parallel/1, 49 max_sessions_sftp_start_channel_sequential/1, 50 max_sessions_ssh_connect_parallel/1, 51 max_sessions_ssh_connect_sequential/1, 52 max_sessions_drops_tcp_connects/1, 53 max_sessions_drops_tcp_connects/0, 54 server_password_option/1, 55 server_userpassword_option/1, 56 server_pwdfun_option/1, 57 server_pwdfun_4_option/1, 58 server_keyboard_interactive/1, 59 server_keyboard_interactive_extra_msg/1, 60 ssh_connect_arg4_timeout/1, 61 ssh_connect_negtimeout_parallel/1, 62 ssh_connect_negtimeout_sequential/1, 63 ssh_connect_nonegtimeout_connected_parallel/1, 64 ssh_connect_nonegtimeout_connected_sequential/1, 65 ssh_connect_timeout/1, connect/4, 66 ssh_daemon_minimal_remote_max_packet_size_option/1, 67 ssh_msg_debug_fun_option_client/1, 68 ssh_msg_debug_fun_option_server/1, 69 system_dir_option/1, 70 unexpectedfun_option_client/1, 71 unexpectedfun_option_server/1, 72 user_dir_option/1, 73 user_dir_fun_option/1, 74 connectfun_disconnectfun_server/1, 75 hostkey_fingerprint_check/1, 76 hostkey_fingerprint_check_md5/1, 77 hostkey_fingerprint_check_sha/1, 78 hostkey_fingerprint_check_sha256/1, 79 hostkey_fingerprint_check_sha384/1, 80 hostkey_fingerprint_check_sha512/1, 81 hostkey_fingerprint_check_list/1, 82 save_accepted_host_option/1, 83 raw_option/1, 84 config_file/1, 85 config_file_modify_algorithms_order/1 86 ]). 87 88%%% Common test callbacks 89-export([suite/0, all/0, groups/0, 90 init_per_suite/1, end_per_suite/1, 91 init_per_group/2, end_per_group/2, 92 init_per_testcase/2, end_per_testcase/2 93 ]). 94 95-define(NEWLINE, <<"\r\n">>). 96 97%%-------------------------------------------------------------------- 98%% Common Test interface functions ----------------------------------- 99%%-------------------------------------------------------------------- 100 101suite() -> 102 [{ct_hooks,[ts_install_cth]}, 103 {timetrap,{seconds,60}}]. 104 105all() -> 106 [connectfun_disconnectfun_server, 107 connectfun_disconnectfun_client, 108 server_password_option, 109 server_userpassword_option, 110 server_pwdfun_option, 111 server_pwdfun_4_option, 112 server_keyboard_interactive, 113 server_keyboard_interactive_extra_msg, 114 auth_method_kb_interactive_data_tuple, 115 auth_method_kb_interactive_data_fun3, 116 auth_method_kb_interactive_data_fun4, 117 {group, dir_options}, 118 ssh_connect_timeout, 119 ssh_connect_arg4_timeout, 120 ssh_daemon_minimal_remote_max_packet_size_option, 121 ssh_msg_debug_fun_option_client, 122 ssh_msg_debug_fun_option_server, 123 disconnectfun_option_server, 124 disconnectfun_option_client, 125 unexpectedfun_option_server, 126 unexpectedfun_option_client, 127 hostkey_fingerprint_check, 128 hostkey_fingerprint_check_md5, 129 hostkey_fingerprint_check_sha, 130 hostkey_fingerprint_check_sha256, 131 hostkey_fingerprint_check_sha384, 132 hostkey_fingerprint_check_sha512, 133 hostkey_fingerprint_check_list, 134 id_string_no_opt_client, 135 id_string_own_string_client, 136 id_string_own_string_client_trail_space, 137 id_string_random_client, 138 id_string_no_opt_server, 139 id_string_own_string_server, 140 id_string_own_string_server_trail_space, 141 id_string_random_server, 142 save_accepted_host_option, 143 raw_option, 144 config_file, 145 config_file_modify_algorithms_order, 146 {group, hardening_tests} 147 ]. 148 149groups() -> 150 [{hardening_tests, [], [ssh_connect_nonegtimeout_connected_parallel, 151 ssh_connect_nonegtimeout_connected_sequential, 152 ssh_connect_negtimeout_parallel, 153 ssh_connect_negtimeout_sequential, 154 max_sessions_ssh_connect_parallel, 155 max_sessions_ssh_connect_sequential, 156 max_sessions_sftp_start_channel_parallel, 157 max_sessions_sftp_start_channel_sequential, 158 max_sessions_drops_tcp_connects 159 ]}, 160 {dir_options, [], [user_dir_option, 161 user_dir_fun_option, 162 system_dir_option]} 163 ]. 164 165 166%%-------------------------------------------------------------------- 167init_per_suite(Config) -> 168 ?CHECK_CRYPTO(Config). 169 170end_per_suite(_Config) -> 171 ssh:stop(). 172 173%%-------------------------------------------------------------------- 174init_per_group(hardening_tests, Config) -> 175 ct:log("Pub keys setup for: ~p", 176 [ssh_test_lib:setup_all_user_host_keys(Config)]), 177 Config; 178init_per_group(dir_options, Config) -> 179 PrivDir = proplists:get_value(priv_dir, Config), 180 %% Make unreadable dir: 181 Dir_unreadable = filename:join(PrivDir, "unread"), 182 ok = file:make_dir(Dir_unreadable), 183 {ok,F1} = file:read_file_info(Dir_unreadable), 184 ok = file:write_file_info(Dir_unreadable, 185 F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}), 186 %% Make readable file: 187 File_readable = filename:join(PrivDir, "file"), 188 ok = file:write_file(File_readable, <<>>), 189 190 %% Check: 191 case {file:read_file_info(Dir_unreadable), 192 file:read_file_info(File_readable)} of 193 {{ok, Id=#file_info{type=directory, access=Md}}, 194 {ok, If=#file_info{type=regular, access=Mf}}} -> 195 AccessOK = 196 case {Md, Mf} of 197 {read, _} -> false; 198 {read_write, _} -> false; 199 {_, read} -> true; 200 {_, read_write} -> true; 201 _ -> false 202 end, 203 204 case AccessOK of 205 true -> 206 %% Save: 207 [{unreadable_dir, Dir_unreadable}, 208 {readable_file, File_readable} 209 | Config]; 210 false -> 211 ct:log("File#file_info : ~p~n" 212 "Dir#file_info : ~p",[If,Id]), 213 {skip, "File or dir mode settings failed"} 214 end; 215 216 NotDirFile -> 217 ct:log("{Dir,File} -> ~p",[NotDirFile]), 218 {skip, "File/Dir creation failed"} 219 end; 220init_per_group(_, Config) -> 221 Config. 222 223end_per_group(_, Config) -> 224 Config. 225%%-------------------------------------------------------------------- 226init_per_testcase(_TestCase, Config) -> 227 ssh:start(), 228 %% Create a clean user_dir 229 UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey), 230 ssh_test_lib:del_dirs(UserDir), 231 file:make_dir(UserDir), 232 [{user_dir,UserDir}|Config]. 233 234end_per_testcase(_TestCase, _Config) -> 235 ssh:stop(), 236 ok. 237 238%%-------------------------------------------------------------------- 239%% Test Cases -------------------------------------------------------- 240%%-------------------------------------------------------------------- 241 242%%% validate to server that uses the 'password' option 243server_password_option(Config) when is_list(Config) -> 244 UserDir = proplists:get_value(user_dir, Config), 245 SysDir = proplists:get_value(data_dir, Config), 246 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 247 {user_dir, UserDir}, 248 {password, "morot"}]), 249 250 ConnectionRef = 251 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 252 {user, "foo"}, 253 {password, "morot"}, 254 {user_interaction, false}, 255 {user_dir, UserDir}]), 256 257 Reason = "Unable to connect using the available authentication methods", 258 259 {error, Reason} = 260 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 261 {save_accepted_host, false}, 262 {user, "vego"}, 263 {password, "foo"}, 264 {user_interaction, false}, 265 {user_dir, UserDir}]), 266 267 ct:log("Test of wrong password: Error msg: ~p ~n", [Reason]), 268 269 ssh:close(ConnectionRef), 270 ssh:stop_daemon(Pid). 271 272%%-------------------------------------------------------------------- 273 274%%% validate to server that uses the 'password' option 275server_userpassword_option(Config) when is_list(Config) -> 276 UserDir = proplists:get_value(user_dir, Config), 277 SysDir = proplists:get_value(data_dir, Config), 278 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 279 {user_dir, UserDir}, 280 {user_passwords, [{"vego", "morot"}]}]), 281 282 ConnectionRef = 283 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 284 {user, "vego"}, 285 {password, "morot"}, 286 {user_interaction, false}, 287 {user_dir, UserDir}]), 288 ssh:close(ConnectionRef), 289 290 Reason = "Unable to connect using the available authentication methods", 291 292 {error, Reason} = 293 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 294 {save_accepted_host, false}, 295 {user, "foo"}, 296 {password, "morot"}, 297 {user_interaction, false}, 298 {user_dir, UserDir}]), 299 {error, Reason} = 300 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 301 {save_accepted_host, false}, 302 {user, "vego"}, 303 {password, "foo"}, 304 {user_interaction, false}, 305 {user_dir, UserDir}]), 306 ssh:stop_daemon(Pid). 307 308%%-------------------------------------------------------------------- 309%%% validate to server that uses the 'pwdfun' option 310server_pwdfun_option(Config) -> 311 UserDir = proplists:get_value(user_dir, Config), 312 SysDir = proplists:get_value(data_dir, Config), 313 CHKPWD = fun("foo",Pwd) -> Pwd=="bar"; 314 (_,_) -> false 315 end, 316 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 317 {user_dir, UserDir}, 318 {pwdfun,CHKPWD}]), 319 ConnectionRef = 320 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 321 {user, "foo"}, 322 {password, "bar"}, 323 {user_interaction, false}, 324 {user_dir, UserDir}]), 325 ssh:close(ConnectionRef), 326 327 Reason = "Unable to connect using the available authentication methods", 328 329 {error, Reason} = 330 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 331 {save_accepted_host, false}, 332 {user, "foo"}, 333 {password, "morot"}, 334 {user_interaction, false}, 335 {user_dir, UserDir}]), 336 {error, Reason} = 337 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 338 {save_accepted_host, false}, 339 {user, "vego"}, 340 {password, "foo"}, 341 {user_interaction, false}, 342 {user_dir, UserDir}]), 343 ssh:stop_daemon(Pid). 344 345 346%%-------------------------------------------------------------------- 347%%% validate to server that uses the 'pwdfun/4' option 348server_pwdfun_4_option(Config) -> 349 UserDir = proplists:get_value(user_dir, Config), 350 SysDir = proplists:get_value(data_dir, Config), 351 PWDFUN = fun("foo",Pwd,{_,_},undefined) -> Pwd=="bar"; 352 ("fie",Pwd,{_,_},undefined) -> {Pwd=="bar",new_state}; 353 ("bandit",_,_,_) -> disconnect; 354 (_,_,_,_) -> false 355 end, 356 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 357 {user_dir, UserDir}, 358 {pwdfun,PWDFUN}]), 359 ConnectionRef1 = 360 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 361 {user, "foo"}, 362 {password, "bar"}, 363 {user_interaction, false}, 364 {user_dir, UserDir}]), 365 ssh:close(ConnectionRef1), 366 367 ConnectionRef2 = 368 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 369 {user, "fie"}, 370 {password, "bar"}, 371 {user_interaction, false}, 372 {user_dir, UserDir}]), 373 ssh:close(ConnectionRef2), 374 375 Reason = "Unable to connect using the available authentication methods", 376 377 {error, Reason} = 378 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 379 {save_accepted_host, false}, 380 {user, "foo"}, 381 {password, "morot"}, 382 {user_interaction, false}, 383 {user_dir, UserDir}]), 384 {error, Reason} = 385 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 386 {save_accepted_host, false}, 387 {user, "fie"}, 388 {password, "morot"}, 389 {user_interaction, false}, 390 {user_dir, UserDir}]), 391 {error, Reason} = 392 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 393 {user, "vego"}, 394 {password, "foo"}, 395 {user_interaction, false}, 396 {user_dir, UserDir}]), 397 398 {error, Reason} = 399 ssh:connect(Host, Port, [{silently_accept_hosts, true}, 400 {save_accepted_host, false}, 401 {user, "bandit"}, 402 {password, "pwd breaking"}, 403 {user_interaction, false}, 404 {user_dir, UserDir}]), 405 ssh:stop_daemon(Pid). 406 407 408%%-------------------------------------------------------------------- 409server_keyboard_interactive(Config) -> 410 UserDir = proplists:get_value(user_dir, Config), 411 SysDir = proplists:get_value(data_dir, Config), 412 %% Test that the state works 413 Parent = self(), 414 PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true; 415 (_,P,_,S=undefined) -> Parent!{P,S},{false,1}; 416 (_,P,_,S) -> Parent!{P,S}, {false,S+1} 417 end, 418 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 419 {user_dir, UserDir}, 420 {auth_methods,"keyboard-interactive"}, 421 {pwdfun,PWDFUN}]), 422 423 %% Try with passwords "incorrect", "Bad again" and finally "bar" 424 KIFFUN = fun(_Name, _Instr, _PromptInfos) -> 425 K={k,self()}, 426 Answer = 427 case get(K) of 428 undefined -> 429 put(K,1), 430 ["incorrect"]; 431 2 -> 432 put(K,3), 433 ["bar"]; 434 S-> 435 put(K,S+1), 436 ["Bad again"] 437 end, 438 ct:log("keyboard_interact_fun:~n" 439 " Name = ~p~n" 440 " Instruction = ~p~n" 441 " Prompts = ~p~n" 442 "~nAnswer:~n ~p~n", 443 [_Name, _Instr, _PromptInfos, Answer]), 444 445 Answer 446 end, 447 448 ConnectionRef2 = 449 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 450 {user, "foo"}, 451 {keyboard_interact_fun, KIFFUN}, 452 {user_dir, UserDir}]), 453 ssh:close(ConnectionRef2), 454 ssh:stop_daemon(Pid), 455 456 lists:foreach(fun(Expect) -> 457 receive 458 Expect -> ok; 459 Other -> ct:fail("Expect: ~p~nReceived ~p",[Expect,Other]) 460 after 461 2000 -> ct:fail("Timeout expecting ~p",[Expect]) 462 end 463 end, [{"incorrect",undefined}, 464 {"Bad again",1}, 465 {"bar",2}]). 466 467%%-------------------------------------------------------------------- 468server_keyboard_interactive_extra_msg(Config) -> 469 UserDir = proplists:get_value(user_dir, Config), 470 SysDir = proplists:get_value(data_dir, Config), 471 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 472 {user_dir, UserDir}, 473 {auth_methods,"keyboard-interactive"}, 474 {tstflg, [{one_empty,true}]}, 475 {user_passwords, [{"foo","bar"}]} 476 ]), 477 478 ConnectionRef = 479 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 480 {user, "foo"}, 481 {password, "bar"}, 482 {user_dir, UserDir}]), 483 ssh:close(ConnectionRef), 484 ssh:stop_daemon(Pid). 485 486%%-------------------------------------------------------------------- 487auth_method_kb_interactive_data_tuple(Config) -> 488 T = {"abc1", "def1", "ghi1: ", true}, 489 amkid(Config, T, T). 490 491auth_method_kb_interactive_data_fun3(Config) -> 492 T = {"abc2", "def2", "ghi2: ", true}, 493 amkid(Config, T, 494 fun(_Peer, _User, _Service) -> T end 495 ). 496 497auth_method_kb_interactive_data_fun4(Config) -> 498 T = {"abc3", "def3", "ghi3: ", true}, 499 amkid(Config, T, 500 fun(_Peer, _User, _Service, _State) -> T end 501 ). 502 503amkid(Config, {ExpectName,ExpectInstr,ExpectPrompts,ExpectEcho}, OptVal) -> 504 UserDir = proplists:get_value(user_dir, Config), 505 SysDir = proplists:get_value(data_dir, Config), 506 %% Test that the state works 507 Parent = self(), 508 PWDFUN = fun("foo",P="bar",_,S) -> Parent!{P,S},true; 509 (_,P,_,S=undefined) -> Parent!{P,S},{false,1}; 510 (_,P,_,S) -> Parent!{P,S}, {false,S+1} 511 end, 512 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 513 {user_dir, UserDir}, 514 {auth_methods,"keyboard-interactive"}, 515 {pwdfun,PWDFUN}, 516 {auth_method_kb_interactive_data,OptVal} 517 ]), 518 519 KIFFUN = fun(Name, Instr, PromptInfos) -> 520 K={k,self()}, 521 Answer = 522 case get(K) of 523 undefined -> 524 put(K,1), 525 ["incorrect"]; 526 2 -> 527 put(K,3), 528 ["bar"]; 529 S-> 530 put(K,S+1), 531 ["Bad again"] 532 end, 533 ct:log("keyboard_interact_fun:~n" 534 " Name = ~p~n" 535 " Instruction = ~p~n" 536 " Prompts = ~p~n" 537 "~nAnswer:~n ~p~n", 538 [Name, Instr, PromptInfos, Answer]), 539 case {binary_to_list(Name), 540 binary_to_list(Instr), 541 [{binary_to_list(PI),Echo} || {PI,Echo} <- PromptInfos] 542 } of 543 {ExpectName, ExpectInstr, [{ExpectPrompts,ExpectEcho}]} -> 544 ct:log("Match!", []), 545 Answer; 546 _ -> 547 ct:log("Not match!~n" 548 " ExpectName = ~p~n" 549 " ExpectInstruction = ~p~n" 550 " ExpectPrompts = ~p~n", 551 [ExpectName, ExpectInstr, [{ExpectPrompts,ExpectEcho}]]), 552 ct:fail("no_match") 553 end 554 end, 555 ssh_dbg:start(), ssh_dbg:on(authentication), %% Test dbg code 556 ConnectionRef2 = 557 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 558 {user, "foo"}, 559 {keyboard_interact_fun, KIFFUN}, 560 {user_dir, UserDir}]), 561 ssh_dbg:stop(), 562 ssh:close(ConnectionRef2), 563 ssh:stop_daemon(Pid), 564 565 lists:foreach(fun(Expect) -> 566 receive 567 Expect -> ok; 568 Other -> ct:fail("Expect: ~p~nReceived ~p",[Expect,Other]) 569 after 570 2000 -> ct:fail("Timeout expecting ~p",[Expect]) 571 end 572 end, [{"incorrect",undefined}, 573 {"Bad again",1}, 574 {"bar",2}]). 575 576%%-------------------------------------------------------------------- 577system_dir_option(Config) -> 578 DirUnread = proplists:get_value(unreadable_dir,Config), 579 FileRead = proplists:get_value(readable_file,Config), 580 581 case ssh_test_lib:daemon([{system_dir, DirUnread}]) of 582 {error,{eoptions,{{system_dir,DirUnread},eacces}}} -> 583 ok; 584 {Pid1,_Host1,Port1} when is_pid(Pid1),is_integer(Port1) -> 585 ssh:stop_daemon(Pid1), 586 ct:fail("Didn't detect that dir is unreadable", []) 587 end, 588 589 case ssh_test_lib:daemon([{system_dir, FileRead}]) of 590 {error,{eoptions,{{system_dir,FileRead},enotdir}}} -> 591 ok; 592 {Pid2,_Host2,Port2} when is_pid(Pid2),is_integer(Port2) -> 593 ssh:stop_daemon(Pid2), 594 ct:fail("Didn't detect that option is a plain file", []) 595 end. 596 597%%-------------------------------------------------------------------- 598user_dir_option(Config) -> 599 DirUnread = proplists:get_value(unreadable_dir,Config), 600 FileRead = proplists:get_value(readable_file,Config), 601 %% Any port will do (beware, implementation knowledge!): 602 Port = 65535, 603 604 case ssh:connect("localhost", Port, [{user_dir, DirUnread}, 605 {save_accepted_host, false}]) of 606 {error,{eoptions,{{user_dir,DirUnread},eacces}}} -> 607 ok; 608 {error,econnrefused} -> 609 ct:fail("Didn't detect that dir is unreadable", []) 610 end, 611 612 case ssh:connect("localhost", Port, [{user_dir, FileRead}, 613 {save_accepted_host, false}]) of 614 {error,{eoptions,{{user_dir,FileRead},enotdir}}} -> 615 ok; 616 {error,econnrefused} -> 617 ct:fail("Didn't detect that option is a plain file", []) 618 end. 619 620%%-------------------------------------------------------------------- 621user_dir_fun_option(Config) -> 622 DataDir = proplists:get_value(data_dir, Config), 623 PrivDir = proplists:get_value(priv_dir, Config), 624 SysDir = filename:join(PrivDir,"system"), 625 ssh_test_lib:setup_all_host_keys(DataDir, SysDir), 626 UserDir = filename:join(PrivDir,"user"), 627 ssh_test_lib:setup_all_user_keys(DataDir, UserDir), 628 629 Parent = self(), 630 Ref = make_ref(), 631 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 632 {user_dir_fun, fun(User) -> 633 ct:log("user_dir_fun called ~p",[User]), 634 Parent ! {user,Ref,User}, 635 UserDir 636 end}, 637 {failfun, fun ssh_test_lib:failfun/2}]), 638 _ConnectionRef = 639 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 640 {user, "foo"}, 641 {user_dir, UserDir}, 642 {auth_methods,"publickey"}, 643 {user_interaction, false}]), 644 receive 645 {user,Ref,"foo"} -> 646 ssh:stop_daemon(Pid), 647 ok; 648 {user,Ref,What} -> 649 ssh:stop_daemon(Pid), 650 ct:log("Got ~p",[What]), 651 {fail, bad_userid} 652 after 2000 -> 653 ssh:stop_daemon(Pid), 654 {fail,timeout_in_receive} 655 end. 656 657 658%%-------------------------------------------------------------------- 659%%% validate client that uses the 'ssh_msg_debug_fun' option 660ssh_msg_debug_fun_option_client(Config) -> 661 UserDir = proplists:get_value(user_dir, Config), 662 SysDir = proplists:get_value(data_dir, Config), 663 664 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 665 {user_dir, UserDir}, 666 {password, "morot"}, 667 {failfun, fun ssh_test_lib:failfun/2}]), 668 Parent = self(), 669 DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, 670 671 ConnectionRef = 672 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 673 {user, "foo"}, 674 {password, "morot"}, 675 {user_dir, UserDir}, 676 {user_interaction, false}, 677 {ssh_msg_debug_fun,DbgFun}]), 678 %% Beware, implementation knowledge: 679 gen_statem:cast(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), 680 receive 681 {msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} -> 682 ct:log("Got expected dbg msg ~p",[X]), 683 ssh:stop_daemon(Pid); 684 {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> 685 ct:log("Got dbg msg but bad ConnectionRef (~p expected) ~p",[ConnectionRef,X]), 686 ssh:stop_daemon(Pid), 687 {fail, "Bad ConnectionRef received"}; 688 {msg_dbg,X} -> 689 ct:log("Got bad dbg msg ~p",[X]), 690 ssh:stop_daemon(Pid), 691 {fail,"Bad msg received"} 692 after 1000 -> 693 ssh:stop_daemon(Pid), 694 {fail,timeout} 695 end. 696 697%%-------------------------------------------------------------------- 698connectfun_disconnectfun_server(Config) -> 699 UserDir = proplists:get_value(user_dir, Config), 700 SysDir = proplists:get_value(data_dir, Config), 701 702 Parent = self(), 703 Ref = make_ref(), 704 ConnFun = fun(_,_,_) -> Parent ! {connect,Ref} end, 705 DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, 706 707 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 708 {user_dir, UserDir}, 709 {password, "morot"}, 710 {failfun, fun ssh_test_lib:failfun/2}, 711 {disconnectfun, DiscFun}, 712 {connectfun, ConnFun}]), 713 ConnectionRef = 714 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 715 {user, "foo"}, 716 {password, "morot"}, 717 {user_dir, UserDir}, 718 {user_interaction, false}]), 719 receive 720 {connect,Ref} -> 721 ssh:close(ConnectionRef), 722 receive 723 {disconnect,Ref,R} -> 724 ct:log("Disconnect result: ~p",[R]), 725 ssh:stop_daemon(Pid) 726 after 10000 -> 727 receive 728 X -> ct:log("received ~p",[X]) 729 after 0 -> ok 730 end, 731 {fail, "No disconnectfun action"} 732 end 733 after 10000 -> 734 receive 735 X -> ct:log("received ~p",[X]) 736 after 0 -> ok 737 end, 738 {fail, "No connectfun action"} 739 end. 740 741%%-------------------------------------------------------------------- 742connectfun_disconnectfun_client(Config) -> 743 UserDir = proplists:get_value(user_dir, Config), 744 SysDir = proplists:get_value(data_dir, Config), 745 746 Parent = self(), 747 Ref = make_ref(), 748 DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, 749 750 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 751 {user_dir, UserDir}, 752 {password, "morot"}, 753 {failfun, fun ssh_test_lib:failfun/2}]), 754 _ConnectionRef = 755 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 756 {user, "foo"}, 757 {password, "morot"}, 758 {user_dir, UserDir}, 759 {disconnectfun, DiscFun}, 760 {user_interaction, false}]), 761 ssh:stop_daemon(Pid), 762 receive 763 {disconnect,Ref,R} -> 764 ct:log("Disconnect result: ~p",[R]) 765 after 2000 -> 766 {fail, "No disconnectfun action"} 767 end. 768 769%%-------------------------------------------------------------------- 770%%% validate client that uses the 'ssh_msg_debug_fun' option 771ssh_msg_debug_fun_option_server(Config) -> 772 UserDir = proplists:get_value(user_dir, Config), 773 SysDir = proplists:get_value(data_dir, Config), 774 775 Parent = self(), 776 DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, 777 ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, 778 779 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 780 {user_dir, UserDir}, 781 {password, "morot"}, 782 {failfun, fun ssh_test_lib:failfun/2}, 783 {connectfun, ConnFun}, 784 {ssh_msg_debug_fun, DbgFun}]), 785 _ConnectionRef = 786 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 787 {user, "foo"}, 788 {password, "morot"}, 789 {user_dir, UserDir}, 790 {user_interaction, false}]), 791 receive 792 {connection_pid,Server} -> 793 %% Beware, implementation knowledge: 794 gen_statem:cast(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), 795 receive 796 {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> 797 ct:log("Got expected dbg msg ~p",[X]), 798 ssh:stop_daemon(Pid); 799 {msg_dbg,X} -> 800 ct:log("Got bad dbg msg ~p",[X]), 801 ssh:stop_daemon(Pid), 802 {fail,"Bad msg received"} 803 after 3000 -> 804 ssh:stop_daemon(Pid), 805 {fail,timeout2} 806 end 807 after 3000 -> 808 ssh:stop_daemon(Pid), 809 {fail,timeout1} 810 end. 811 812%%-------------------------------------------------------------------- 813disconnectfun_option_server(Config) -> 814 UserDir = proplists:get_value(user_dir, Config), 815 SysDir = proplists:get_value(data_dir, Config), 816 817 Parent = self(), 818 DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, 819 820 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 821 {user_dir, UserDir}, 822 {password, "morot"}, 823 {failfun, fun ssh_test_lib:failfun/2}, 824 {disconnectfun, DisConnFun}]), 825 ConnectionRef = 826 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 827 {user, "foo"}, 828 {password, "morot"}, 829 {user_dir, UserDir}, 830 {user_interaction, false}]), 831 ssh:close(ConnectionRef), 832 receive 833 {disconnect,Reason} -> 834 ct:log("Server detected disconnect: ~p",[Reason]), 835 ssh:stop_daemon(Pid), 836 ok 837 after 5000 -> 838 receive 839 X -> ct:log("received ~p",[X]) 840 after 0 -> ok 841 end, 842 {fail,"Timeout waiting for disconnect"} 843 end. 844 845%%-------------------------------------------------------------------- 846disconnectfun_option_client(Config) -> 847 UserDir = proplists:get_value(user_dir, Config), 848 SysDir = proplists:get_value(data_dir, Config), 849 850 Parent = self(), 851 DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, 852 853 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 854 {user_dir, UserDir}, 855 {password, "morot"}, 856 {failfun, fun ssh_test_lib:failfun/2}]), 857 _ConnectionRef = 858 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 859 {user, "foo"}, 860 {password, "morot"}, 861 {user_dir, UserDir}, 862 {user_interaction, false}, 863 {disconnectfun, DisConnFun}]), 864 ssh:stop_daemon(Pid), 865 receive 866 {disconnect,Reason} -> 867 ct:log("Client detected disconnect: ~p",[Reason]), 868 ok 869 after 3000 -> 870 receive 871 X -> ct:log("received ~p",[X]) 872 after 0 -> ok 873 end, 874 {fail,"Timeout waiting for disconnect"} 875 end. 876 877%%-------------------------------------------------------------------- 878unexpectedfun_option_server(Config) -> 879 UserDir = proplists:get_value(user_dir, Config), 880 SysDir = proplists:get_value(data_dir, Config), 881 882 Parent = self(), 883 ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, 884 UnexpFun = fun(Msg,Peer) -> 885 Parent ! {unexpected,Msg,Peer,self()}, 886 skip 887 end, 888 889 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 890 {user_dir, UserDir}, 891 {password, "morot"}, 892 {failfun, fun ssh_test_lib:failfun/2}, 893 {connectfun, ConnFun}, 894 {unexpectedfun, UnexpFun}]), 895 _ConnectionRef = 896 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 897 {user, "foo"}, 898 {password, "morot"}, 899 {user_dir, UserDir}, 900 {user_interaction, false}]), 901 receive 902 {connection_pid,Server} -> 903 %% Beware, implementation knowledge: 904 Server ! unexpected_message, 905 receive 906 {unexpected, unexpected_message, {{_,_,_,_},_}, _} -> ok; 907 {unexpected, unexpected_message, Peer, _} -> ct:fail("Bad peer ~p",[Peer]); 908 M = {unexpected, _, _, _} -> ct:fail("Bad msg ~p",[M]) 909 after 3000 -> 910 ssh:stop_daemon(Pid), 911 {fail,timeout2} 912 end 913 after 3000 -> 914 ssh:stop_daemon(Pid), 915 {fail,timeout1} 916 end. 917 918%%-------------------------------------------------------------------- 919unexpectedfun_option_client(Config) -> 920 UserDir = proplists:get_value(user_dir, Config), 921 SysDir = proplists:get_value(data_dir, Config), 922 923 Parent = self(), 924 UnexpFun = fun(Msg,Peer) -> 925 Parent ! {unexpected,Msg,Peer,self()}, 926 skip 927 end, 928 929 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 930 {user_dir, UserDir}, 931 {password, "morot"}, 932 {failfun, fun ssh_test_lib:failfun/2}]), 933 ConnectionRef = 934 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 935 {user, "foo"}, 936 {password, "morot"}, 937 {user_dir, UserDir}, 938 {user_interaction, false}, 939 {unexpectedfun, UnexpFun}]), 940 %% Beware, implementation knowledge: 941 ConnectionRef ! unexpected_message, 942 943 receive 944 {unexpected, unexpected_message, {{_,_,_,_},_}, ConnectionRef} -> 945 ok; 946 {unexpected, unexpected_message, Peer, ConnectionRef} -> 947 ct:fail("Bad peer ~p",[Peer]); 948 M = {unexpected, _, _, _} -> 949 ct:fail("Bad msg ~p",[M]) 950 after 3000 -> 951 ssh:stop_daemon(Pid), 952 {fail,timeout} 953 end. 954 955%%-------------------------------------------------------------------- 956hostkey_fingerprint_check(Config) -> 957 do_hostkey_fingerprint_check(Config, old). 958 959hostkey_fingerprint_check_md5(Config) -> 960 do_hostkey_fingerprint_check(Config, md5). 961 962hostkey_fingerprint_check_sha(Config) -> 963 do_hostkey_fingerprint_check(Config, sha). 964 965hostkey_fingerprint_check_sha256(Config) -> 966 do_hostkey_fingerprint_check(Config, sha256). 967 968hostkey_fingerprint_check_sha384(Config) -> 969 do_hostkey_fingerprint_check(Config, sha384). 970 971hostkey_fingerprint_check_sha512(Config) -> 972 do_hostkey_fingerprint_check(Config, sha512). 973 974hostkey_fingerprint_check_list(Config) -> 975 do_hostkey_fingerprint_check(Config, [sha,md5,sha256]). 976 977%%%---- 978do_hostkey_fingerprint_check(Config, HashAlg) -> 979 case supported_hash(HashAlg) of 980 true -> 981 really_do_hostkey_fingerprint_check(Config, HashAlg); 982 false when HashAlg == old -> 983 {skip,{unsupported_hash,md5}};% Happen to know that ssh:hostkey_fingerprint/1 uses md5... 984 false -> 985 {skip,{unsupported_hash,HashAlg}} 986 end. 987 988supported_hash(old) -> 989 supported_hash(md5); % Happen to know that ssh:hostkey_fingerprint/1 uses md5... 990supported_hash(HashAlg) -> 991 Hs = if is_atom(HashAlg) -> [HashAlg]; 992 is_list(HashAlg) -> HashAlg 993 end, 994 [] == (Hs -- proplists:get_value(hashs, crypto:supports(), [])). 995 996 997really_do_hostkey_fingerprint_check(Config, HashAlg) -> 998 UserDir = proplists:get_value(user_dir, Config), 999 SysDir = proplists:get_value(data_dir, Config), 1000 1001 %% All host key fingerprints. Trust that public_key has checked the hostkey_fingerprint 1002 %% function since that function is used by the ssh client... 1003 FPs0 = [case HashAlg of 1004 old -> ssh:hostkey_fingerprint(Key); 1005 _ -> ssh:hostkey_fingerprint(HashAlg, Key) 1006 end 1007 || FileCandidate <- begin 1008 {ok,KeyFileCands} = file:list_dir(SysDir), 1009 KeyFileCands 1010 end, 1011 nomatch =/= re:run(FileCandidate, ".*\\.pub", []), 1012 {Key,_Cmnts} <- begin 1013 {ok,Bin} = file:read_file(filename:join(SysDir, FileCandidate)), 1014 try ssh_file:decode(Bin, public_key) 1015 catch 1016 _:_ -> [] 1017 end 1018 end], 1019 FPs = if is_atom(HashAlg) -> FPs0; 1020 is_list(HashAlg) -> lists:concat(FPs0) 1021 end, 1022 ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]), 1023 1024 %% Start daemon with the public keys that we got fingerprints from 1025 {Pid, Host0, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 1026 {user_dir, UserDir}, 1027 {password, "morot"}]), 1028 Host = ssh_test_lib:ntoa(Host0), 1029 FP_check_fun = fun(PeerName, FP) -> 1030 ct:log("PeerName = ~p, FP = ~p",[PeerName,FP]), 1031 HostCheck = ssh_test_lib:match_ip(Host, PeerName), 1032 FPCheck = 1033 if is_atom(HashAlg) -> lists:member(FP, FPs); 1034 is_list(HashAlg) -> lists:all(fun(FP1) -> lists:member(FP1,FPs) end, 1035 FP) 1036 end, 1037 ct:log("check ~p == ~p (~p) and ~n~p~n in ~p (~p)~n", 1038 [PeerName,Host,HostCheck,FP,FPs,FPCheck]), 1039 HostCheck and FPCheck 1040 end, 1041 1042 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, 1043 case HashAlg of 1044 old -> FP_check_fun; 1045 _ -> {HashAlg, FP_check_fun} 1046 end}, 1047 {user, "foo"}, 1048 {password, "morot"}, 1049 {user_dir, UserDir}, 1050 {save_accepted_host, false}, % Ensure no 'known_hosts' disturbs 1051 {user_interaction, false}]), 1052 ssh:stop_daemon(Pid). 1053 1054%%-------------------------------------------------------------------- 1055%%% Test connect_timeout option in ssh:connect/4 1056ssh_connect_timeout(_Config) -> 1057 ConnTimeout = 2000, 1058 {error,{faked_transport,connect,TimeoutToTransport}} = 1059 ssh:connect("localhost", 12345, 1060 [{transport,{tcp,?MODULE,tcp_closed}}, 1061 {save_accepted_host, false}, 1062 {connect_timeout,ConnTimeout}], 1063 1000), 1064 case TimeoutToTransport of 1065 ConnTimeout -> ok; 1066 Other -> 1067 ct:log("connect_timeout is ~p but transport received ~p",[ConnTimeout,Other]), 1068 {fail,"ssh:connect/4 wrong connect_timeout received in transport"} 1069 end. 1070 1071%% Plugin function for the test above 1072connect(_Host, _Port, _Opts, Timeout) -> 1073 {error, {faked_transport,connect,Timeout}}. 1074 1075%%-------------------------------------------------------------------- 1076%%% Test fourth argument in ssh:connect/4 1077ssh_connect_arg4_timeout(_Config) -> 1078 Timeout = 1000, 1079 Parent = self(), 1080 %% start the server 1081 Server = spawn(fun() -> 1082 {ok,Sl} = gen_tcp:listen(0,[]), 1083 {ok,{_,Port}} = inet:sockname(Sl), 1084 Parent ! {port,self(),Port}, 1085 Rsa = gen_tcp:accept(Sl), 1086 ct:log("Server gen_tcp:accept got ~p",[Rsa]), 1087 receive after 2*Timeout -> ok end %% let client timeout first 1088 end), 1089 1090 %% Get listening port 1091 Port = receive 1092 {port,Server,ServerPort} -> ServerPort 1093 after 1094 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1095 end, 1096 1097 %% try to connect with a timeout, but "supervise" it 1098 Client = spawn(fun() -> 1099 T0 = erlang:monotonic_time(), 1100 Rc = ssh:connect("localhost",Port,[{save_accepted_host, false}],Timeout), 1101 ct:log("Client ssh:connect got ~p",[Rc]), 1102 Parent ! {done,self(),Rc,T0} 1103 end), 1104 1105 %% Wait for client reaction on the connection try: 1106 receive 1107 {done, Client, {error,timeout}, T0} -> 1108 Msp = ms_passed(T0), 1109 exit(Server,hasta_la_vista___baby), 1110 Low = 0.9*Timeout, 1111 High = 4.0*Timeout, 1112 ct:log("Timeout limits: ~.4f - ~.4f ms, timeout " 1113 "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]), 1114 if 1115 Low<Msp, Msp<High -> ok; 1116 true -> {fail, "timeout not within limits"} 1117 end; 1118 1119 {done, Client, {error,Other}, _T0} -> 1120 ct:log("Error message \"~p\" from the client is unexpected.",[{error,Other}]), 1121 {fail, "Unexpected error message"}; 1122 1123 {done, Client, {ok,_Ref}, _T0} -> 1124 {fail,"ssh-connected ???"} 1125 after 1126 5000 -> 1127 exit(Server,hasta_la_vista___baby), 1128 exit(Client,hasta_la_vista___baby), 1129 {fail, "Didn't timeout"} 1130 end. 1131 1132%% Help function, elapsed milliseconds since T0 1133ms_passed(T0) -> 1134 %% OTP 18 1135 erlang:convert_time_unit(erlang:monotonic_time() - T0, 1136 native, 1137 micro_seconds) / 1000. 1138 1139%%-------------------------------------------------------------------- 1140ssh_daemon_minimal_remote_max_packet_size_option(Config) -> 1141 SystemDir = proplists:get_value(data_dir, Config), 1142 UserDir = proplists:get_value(user_dir, Config), 1143 1144 {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, 1145 {user_dir, UserDir}, 1146 {user_passwords, [{"vego", "morot"}]}, 1147 {failfun, fun ssh_test_lib:failfun/2}, 1148 {minimal_remote_max_packet_size, 14}]), 1149 Conn = 1150 ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, 1151 {user_dir, UserDir}, 1152 {user_interaction, false}, 1153 {user, "vego"}, 1154 {password, "morot"}]), 1155 1156 %% Try the limits of the minimal_remote_max_packet_size: 1157 {ok, _ChannelId} = ssh_connection:session_channel(Conn, 100, 14, infinity), 1158 {open_error,_,"Maximum packet size below 14 not supported",_} = 1159 ssh_connection:session_channel(Conn, 100, 13, infinity), 1160 1161 ssh:close(Conn), 1162 ssh:stop_daemon(Server). 1163 1164%%-------------------------------------------------------------------- 1165%% This test try every algorithm by connecting to an Erlang server 1166id_string_no_opt_client(Config) -> 1167 {Server, _Host, Port} = fake_daemon(Config), 1168 {error,_} = ssh:connect("localhost", Port, [{save_accepted_host, false}], 1000), 1169 receive 1170 {id,Server,"SSH-2.0-Erlang/"++Vsn} -> 1171 true = expected_ssh_vsn(Vsn); 1172 {id,Server,Other} -> 1173 ct:fail("Unexpected id: ~s.",[Other]) 1174 after 5000 -> 1175 {fail,timeout} 1176 end. 1177 1178%%-------------------------------------------------------------------- 1179id_string_own_string_client(Config) -> 1180 {Server, _Host, Port} = fake_daemon(Config), 1181 {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}, 1182 {save_accepted_host, false} 1183 ], 1000), 1184 receive 1185 {id,Server,"SSH-2.0-Pelle\r\n"} -> 1186 ok; 1187 {id,Server,Other} -> 1188 ct:fail("Unexpected id: ~s.",[Other]) 1189 after 5000 -> 1190 {fail,timeout} 1191 end. 1192 1193%%-------------------------------------------------------------------- 1194id_string_own_string_client_trail_space(Config) -> 1195 {Server, _Host, Port} = fake_daemon(Config), 1196 {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle "}, 1197 {save_accepted_host, false}], 1000), 1198 receive 1199 {id,Server,"SSH-2.0-Pelle \r\n"} -> 1200 ok; 1201 {id,Server,Other} -> 1202 ct:fail("Unexpected id: ~s.",[Other]) 1203 after 5000 -> 1204 {fail,timeout} 1205 end. 1206 1207%%-------------------------------------------------------------------- 1208id_string_random_client(Config) -> 1209 {Server, _Host, Port} = fake_daemon(Config), 1210 {error,_} = ssh:connect("localhost", Port, [{id_string,random}, 1211 {save_accepted_host, false}], 1000), 1212 receive 1213 {id,Server,Id="SSH-2.0-Erlang"++_} -> 1214 ct:fail("Unexpected id: ~s.",[Id]); 1215 {id,Server,Rnd="SSH-2.0-"++ID} when 4=<length(ID),length(ID)=<7 -> %% Add 2 for CRLF 1216 ct:log("Got correct ~s",[Rnd]); 1217 {id,Server,Id} -> 1218 ct:fail("Unexpected id: ~s.",[Id]) 1219 after 5000 -> 1220 {fail,timeout} 1221 end. 1222 1223%%-------------------------------------------------------------------- 1224id_string_no_opt_server(Config) -> 1225 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, []), 1226 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1227 {ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000), 1228 true = expected_ssh_vsn(Vsn). 1229 1230%%-------------------------------------------------------------------- 1231id_string_own_string_server(Config) -> 1232 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,"Olle"}]), 1233 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1234 {ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000). 1235 1236%%-------------------------------------------------------------------- 1237id_string_own_string_server_trail_space(Config) -> 1238 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,"Olle "}]), 1239 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1240 {ok,"SSH-2.0-Olle \r\n"} = gen_tcp:recv(S1, 0, 2000). 1241 1242%%-------------------------------------------------------------------- 1243id_string_random_server(Config) -> 1244 %% Check undocumented format of id_string. First a bad variant: 1245 {error,{eoptions,_}} = ssh:daemon(0, [{id_string,{random,8,6}}]), 1246 %% And then a correct one: 1247 {_Server, Host, Port} = ssh_test_lib:std_daemon(Config, [{id_string,{random,6,8}}]), 1248 {ok,S1}=ssh_test_lib:gen_tcp_connect(Host,Port,[{active,false},{packet,line}]), 1249 {ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000), 1250 case Rnd of 1251 "Erlang"++_ -> ct:log("Id=~p",[Rnd]), 1252 {fail,got_default_id}; 1253 "Olle\r\n" -> {fail,got_previous_tests_value}; 1254 _ when 8=<length(Rnd),length(Rnd)=<10 -> %% Add 2 for CRLF 1255 ct:log("Got correct ~s",[Rnd]); 1256 _ -> 1257 ct:log("Got wrong sized ~s.",[Rnd]), 1258 {fail,got_wrong_size} 1259 end. 1260 1261%%-------------------------------------------------------------------- 1262ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true). 1263ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false). 1264 1265ssh_connect_negtimeout(Config, Parallel) -> 1266 process_flag(trap_exit, true), 1267 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1268 UserDir = proplists:get_value(priv_dir, Config), 1269 NegTimeOut = 2000, % ms 1270 ct:log("Parallel: ~p",[Parallel]), 1271 1272 {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, 1273 {parallel_login, Parallel}, 1274 {negotiation_timeout, NegTimeOut}, 1275 {failfun, fun ssh_test_lib:failfun/2}]), 1276 1277 {ok,Socket} = ssh_test_lib:gen_tcp_connect(Host, Port, []), 1278 1279 Factor = 2, 1280 ct:log("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), 1281 ct:sleep(round(Factor * NegTimeOut)), 1282 1283 case inet:sockname(Socket) of 1284 {ok,_} -> 1285 %% Give it another chance... 1286 ct:log("Sleep more...",[]), 1287 ct:sleep(round(Factor * NegTimeOut)), 1288 case inet:sockname(Socket) of 1289 {ok,_} -> ct:fail("Socket not closed"); 1290 {error,_} -> ok 1291 end; 1292 {error,_} -> ok 1293 end. 1294 1295%%-------------------------------------------------------------------- 1296%%% Test that ssh connection does not timeout if the connection is established (parallel) 1297ssh_connect_nonegtimeout_connected_parallel(Config) -> 1298 ssh_connect_nonegtimeout_connected(Config, true). 1299 1300%%% Test that ssh connection does not timeout if the connection is established (non-parallel) 1301ssh_connect_nonegtimeout_connected_sequential(Config) -> 1302 ssh_connect_nonegtimeout_connected(Config, false). 1303 1304 1305ssh_connect_nonegtimeout_connected(Config, Parallel) -> 1306 process_flag(trap_exit, true), 1307 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1308 UserDir = proplists:get_value(priv_dir, Config), 1309 NegTimeOut = 2000, % ms 1310 ct:log("Parallel: ~p",[Parallel]), 1311 1312 {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, 1313 {parallel_login, Parallel}, 1314 {negotiation_timeout, NegTimeOut}, 1315 {failfun, fun ssh_test_lib:failfun/2}]), 1316 ct:log("~p Listen ~p:~p",[_Pid,_Host,Port]), 1317 ct:sleep(500), 1318 1319 IO = ssh_test_lib:start_io_server(), 1320 Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]), 1321 receive 1322 Error = {'EXIT', _, _} -> 1323 ct:log("~p",[Error]), 1324 ct:fail(no_ssh_connection); 1325 ErlShellStart -> 1326 ct:log("---Erlang shell start: ~p~n", [ErlShellStart]), 1327 one_shell_op(IO, NegTimeOut), 1328 one_shell_op(IO, NegTimeOut), 1329 1330 Factor = 2, 1331 ct:log("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), 1332 ct:sleep(round(Factor * NegTimeOut)), 1333 1334 one_shell_op(IO, NegTimeOut) 1335 after 1336 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1337 end, 1338 exit(Shell, kill). 1339 1340 1341one_shell_op(IO, TimeOut) -> 1342 ct:log("One shell op: Waiting for prompter"), 1343 receive 1344 ErlPrompt0 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt0]) 1345 after TimeOut -> ct:fail("Timeout waiting for promter") 1346 end, 1347 1348 IO ! {input, self(), "2*3*7.\r\n"}, 1349 receive 1350 Result0 -> ct:log("Result: ~p~n", [Result0]) 1351 after TimeOut -> ct:fail("Timeout waiting for result") 1352 end. 1353 1354%%-------------------------------------------------------------------- 1355max_sessions_ssh_connect_parallel(Config) -> 1356 max_sessions(Config, true, connect_fun(ssh__connect,Config)). 1357max_sessions_ssh_connect_sequential(Config) -> 1358 max_sessions(Config, false, connect_fun(ssh__connect,Config)). 1359 1360max_sessions_sftp_start_channel_parallel(Config) -> 1361 max_sessions(Config, true, connect_fun(ssh_sftp__start_channel, Config)). 1362max_sessions_sftp_start_channel_sequential(Config) -> 1363 max_sessions(Config, false, connect_fun(ssh_sftp__start_channel, Config)). 1364 1365 1366%%%---- helpers: 1367connect_fun(ssh__connect, Config) -> 1368 fun(Host,Port) -> 1369 ssh_test_lib:connect(Host, Port, 1370 [{silently_accept_hosts, true}, 1371 {user_dir, proplists:get_value(priv_dir,Config)}, 1372 {user_interaction, false}, 1373 {user, "carni"}, 1374 {password, "meat"} 1375 ]) 1376 %% ssh_test_lib returns R when ssh:connect returns {ok,R} 1377 end; 1378connect_fun(ssh_sftp__start_channel, _Config) -> 1379 fun(Host,Port) -> 1380 {ok,_Pid,ConnRef} = 1381 ssh_sftp:start_channel(Host, Port, 1382 [{silently_accept_hosts, true}, 1383 {save_accepted_host, false}, 1384 {user, "carni"}, 1385 {password, "meat"} 1386 ]), 1387 ConnRef 1388 end. 1389 1390 1391max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> 1392 Connect = fun(Host,Port) -> 1393 R = Connect0(Host,Port), 1394 ct:log("Connect(~p,~p) -> ~p",[Host,Port,R]), 1395 R 1396 end, 1397 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1398 UserDir = proplists:get_value(priv_dir, Config), 1399 MaxSessions = 5, 1400 {Pid, Host, Port} = ssh_test_lib:daemon([ 1401 {system_dir, SystemDir}, 1402 {user_dir, UserDir}, 1403 {user_passwords, [{"carni", "meat"}]}, 1404 {parallel_login, ParallelLogin}, 1405 {max_sessions, MaxSessions} 1406 ]), 1407 ct:log("~p Listen ~p:~p for max ~p sessions",[Pid,Host,Port,MaxSessions]), 1408 try [Connect(Host,Port) || _ <- lists:seq(1,MaxSessions)] 1409 of 1410 Connections -> 1411 %% Step 1 ok: could set up max_sessions connections 1412 ct:log("Connections up: ~p",[Connections]), 1413 [_|_] = Connections, 1414 1415 %% N w try one more than alowed: 1416 ct:pal("Info Report expected here (if not disabled) ...",[]), 1417 try Connect(Host,Port) 1418 of 1419 _ConnectionRef1 -> 1420 ssh:stop_daemon(Pid), 1421 {fail,"Too many connections accepted"} 1422 catch 1423 error:{badmatch,{error,"Connection closed"}} -> 1424 ct:log("Step 2 ok: could not set up too many connections. Good.",[]), 1425 %% Now stop one connection and try to open one more 1426 ok = ssh:close(hd(Connections)), 1427 try_to_connect(Connect, Host, Port, Pid) 1428 end 1429 catch 1430 error:{badmatch,{error,"Connection closed"}} -> 1431 ssh:stop_daemon(Pid), 1432 {fail,"Too few connections accepted"} 1433 end. 1434 1435 1436try_to_connect(Connect, Host, Port, Pid) -> 1437 {ok,Tref} = timer:send_after(30000, timeout_no_connection), % give the supervisors some time... 1438 try_to_connect(Connect, Host, Port, Pid, Tref, 1). % will take max 3300 ms after 11 tries 1439 1440try_to_connect(Connect, Host, Port, Pid, Tref, N) -> 1441 try Connect(Host,Port) 1442 of 1443 _ConnectionRef1 -> 1444 timer:cancel(Tref), 1445 ct:log("Step 3 ok: could set up one more connection after killing one. Thats good.",[]), 1446 ssh:stop_daemon(Pid), 1447 receive % flush. 1448 timeout_no_connection -> ok 1449 after 0 -> ok 1450 end 1451 catch 1452 error:{badmatch,{error,"Connection closed"}} -> 1453 %% Could not set up one more connection. Try again until timeout. 1454 receive 1455 timeout_no_connection -> 1456 ssh:stop_daemon(Pid), 1457 {fail,"Does not decrease # active sessions"} 1458 after N*50 -> % retry after this time 1459 try_to_connect(Connect, Host, Port, Pid, Tref, N+1) 1460 end 1461 end. 1462 1463%%-------------------------------------------------------------------- 1464max_sessions_drops_tcp_connects() -> 1465 [{timetrap,{minutes,20}}]. 1466 1467max_sessions_drops_tcp_connects(Config) -> 1468 MaxSessions = 20, 1469 UseSessions = 2, % Must be =< MaxSessions 1470 FloodSessions = 1000, 1471 ParallelLogin = true, 1472 NegTimeOut = 8*1000, 1473 HelloTimeOut = 200, 1474 1475 %% Start a test daemon 1476 SystemDir = filename:join(proplists:get_value(priv_dir, Config), system), 1477 UserDir = proplists:get_value(priv_dir, Config), 1478 {Pid, Host0, Port} = 1479 ssh_test_lib:daemon([ 1480 {system_dir, SystemDir}, 1481 {user_dir, UserDir}, 1482 {user_passwords, [{"carni", "meat"}]}, 1483 {parallel_login, ParallelLogin}, 1484 {hello_timeout, HelloTimeOut}, 1485 {negotiation_timeout, NegTimeOut}, 1486 {max_sessions, MaxSessions} 1487 ]), 1488 Host = ssh_test_lib:mangle_connect_address(Host0), 1489 ct:log("~p:~p ~p Listen ~p:~p for max ~p sessions. Mangled Host = ~p", 1490 [?MODULE,?LINE,Pid,Host0,Port,MaxSessions,Host]), 1491 1492 %% Log in UseSessions connections 1493 SSHconnect = fun(N) -> 1494 R = ssh:connect(Host, Port, 1495 [{silently_accept_hosts, true}, 1496 {save_accepted_host, false}, 1497 {user_dir, proplists:get_value(priv_dir,Config)}, 1498 {user_interaction, false}, 1499 {user, "carni"}, 1500 {password, "meat"} 1501 ]), 1502 ct:log("~p:~p ~p: ssh:connect -> ~p", [?MODULE,?LINE,N,R]), 1503 R 1504 end, 1505 1506 L1 = oks([SSHconnect(N) || N <- lists:seq(1,UseSessions)]), 1507 case length(L1) of 1508 UseSessions -> 1509 %% As expected 1510 %% Try gen_tcp:connect 1511 [ct:log("~p:~p ~p: gen_tcp:connect -> ~p", 1512 [?MODULE,?LINE, N, gen_tcp:connect(Host, Port, [])]) 1513 || N <- lists:seq(UseSessions+1, MaxSessions) 1514 ], 1515 1516 ct:log("~p:~p Now try ~p gen_tcp:connect to be rejected", [?MODULE,?LINE,FloodSessions]), 1517 [ct:log("~p:~p ~p: gen_tcp:connect -> ~p", 1518 [?MODULE,?LINE, N, gen_tcp:connect(Host, Port, [])]) 1519 || N <- lists:seq(MaxSessions+1, MaxSessions+1+FloodSessions) 1520 ], 1521 1522 ct:log("~p:~p try ~p ssh:connect", [?MODULE,?LINE, MaxSessions - UseSessions]), 1523 try_ssh_connect(MaxSessions - UseSessions, NegTimeOut, SSHconnect); 1524 1525 Len1 -> 1526 {fail, Len1} 1527 end. 1528 1529try_ssh_connect(N, NegTimeOut, F) when N>0 -> 1530 case F(N) of 1531 {ok,_} -> 1532 try_ssh_connect(N-1, NegTimeOut, F); 1533 {error,_} when N==1 -> 1534 try_ssh_connect(N, NegTimeOut, F); 1535 {error,_} -> 1536 timer:sleep(NegTimeOut), 1537 try_ssh_connect(N, NegTimeOut, F) 1538 end; 1539try_ssh_connect(_N, _NegTimeOut, _F) -> 1540 done. 1541 1542 1543oks(L) -> lists:filter(fun({ok,_}) -> true; 1544 (_) -> false 1545 end, L). 1546 1547%%-------------------------------------------------------------------- 1548save_accepted_host_option(Config) -> 1549 UserDir = proplists:get_value(user_dir, Config), 1550 KnownHosts = filename:join(UserDir, "known_hosts"), 1551 SysDir = proplists:get_value(data_dir, Config), 1552 {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, 1553 {user_dir, UserDir}, 1554 {user_passwords, [{"vego", "morot"}]} 1555 ]), 1556 {error,enoent} = file:read_file(KnownHosts), 1557 1558 {ok,_C1} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 1559 {save_accepted_host, false}, 1560 {user, "vego"}, 1561 {password, "morot"}, 1562 {user_interaction, false}, 1563 {user_dir, UserDir}]), 1564 {error,enoent} = file:read_file(KnownHosts), 1565 1566 {ok,_C2} = ssh:connect(Host, Port, [{silently_accept_hosts, true}, 1567 {user, "vego"}, 1568 {password, "morot"}, 1569 {user_interaction, false}, 1570 {user_dir, UserDir}]), 1571 {ok,_} = file:read_file(KnownHosts), 1572 ssh:stop_daemon(Pid). 1573 1574%%-------------------------------------------------------------------- 1575raw_option(_Config) -> 1576 Opts = [{raw,1,2,3,4}], 1577 #{socket_options := Opts} = ssh_options:handle_options(client, Opts), 1578 #{socket_options := Opts} = ssh_options:handle_options(server, Opts). 1579 1580%%-------------------------------------------------------------------- 1581config_file(Config) -> 1582 %% First find common algs: 1583 ServerAlgs = ssh_test_lib:default_algorithms(sshd), 1584 OurAlgs = ssh_transport:supported_algorithms(), % Incl disabled but supported 1585 CommonAlgs = ssh_test_lib:intersection(ServerAlgs, OurAlgs), 1586 ct:log("ServerAlgs =~n~p~n~nOurAlgs =~n~p~n~nCommonAlgs =~n~p",[ServerAlgs,OurAlgs,CommonAlgs]), 1587 Nkex = length(proplists:get_value(kex, CommonAlgs, [])), 1588 1589 %% Adjust for very old ssh daemons that only supports ssh-rsa and ssh-dss: 1590 AdjustClient = 1591 case proplists:get_value(public_key,ServerAlgs,[]) -- ['ssh-rsa','ssh-dss'] of 1592 [] -> 1593 %% Old, let the client support them also: 1594 ct:log("Adjust the client's public_key set", []), 1595 [{public_key, ['ssh-rsa','ssh-dss']}]; 1596 [_|_] -> 1597 %% Ok, let the client be un-modified: 1598 [] 1599 end, 1600 1601 case {ServerAlgs, ssh_test_lib:some_empty(CommonAlgs)} of 1602 {[],_} -> 1603 {skip, "No server algorithms found"}; 1604 {_,true} -> 1605 {fail, "Missing common algorithms"}; 1606 _ when Nkex<3 -> 1607 {skip, "Not enough number of common kex"}; 1608 _ -> 1609 %% Then find three common kex and one common cipher: 1610 [K1a,K1b,K2a|_] = proplists:get_value(kex, CommonAlgs), 1611 [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs), 1612 1613 %% Make config file: 1614 Contents = 1615 [{ssh, [{preferred_algorithms, 1616 [{cipher, [Ch1]}, 1617 {kex, [K1a]} 1618 ] ++ AdjustClient}, 1619 {client_options, 1620 [{modify_algorithms, 1621 [{rm, [{kex, [K1a]}]}, 1622 {append, [{kex, [K1b]}]} 1623 ]} 1624 ]} 1625 ]} 1626 ], 1627 %% write the file: 1628 PrivDir = proplists:get_value(priv_dir, Config), 1629 ConfFile = filename:join(PrivDir,"c2.config"), 1630 {ok,D} = file:open(ConfFile, [write]), 1631 io:format(D, "~p.~n", [Contents]), 1632 file:close(D), 1633 {ok,Cnfs} = file:read_file(ConfFile), 1634 ct:log("c2.config:~n~s", [Cnfs]), 1635 1636 %% Start the slave node with the configuration just made: 1637 {ok,Node} = start_node(random_node_name(?MODULE), ConfFile), 1638 1639 R0 = rpc:call(Node, ssh, default_algorithms, []), 1640 ct:log("R0 = ~p",[R0]), 1641 R0 = ssh:default_algorithms(), 1642 1643 %% Start ssh on the slave. This should apply the ConfFile: 1644 rpc:call(Node, ssh, start, []), 1645 1646 R1 = rpc:call(Node, ssh, default_algorithms, []), 1647 ct:log("R1 = ~p",[R1]), 1648 [{kex,[K1a]}, 1649 {public_key,_}, 1650 {cipher,[{_,[Ch1]}, 1651 {_,[Ch1]}]} | _] = R1, 1652 1653 %% First connection. The client_options should be applied: 1654 {ok,C1} = rpc:call(Node, ssh, connect, [loopback, ?SSH_DEFAULT_PORT, 1655 [{silently_accept_hosts, true}, 1656 {save_accepted_host, false}, 1657 {user_interaction, false} 1658 ]]), 1659 ct:log("C1 = ~n~p", [C1]), 1660 {algorithms,As1} = rpc:call(Node, ssh, connection_info, [C1, algorithms]), 1661 K1b = proplists:get_value(kex, As1), 1662 Ch1 = proplists:get_value(encrypt, As1), 1663 Ch1 = proplists:get_value(decrypt, As1), 1664 {options,Os1} = rpc:call(Node, ssh, connection_info, [C1, options]), 1665 ct:log("C1 algorithms:~n~p~n~noptions:~n~p", [As1,Os1]), 1666 1667 %% Second connection, the Options take precedence: 1668 C2_Opts = [{modify_algorithms,[{rm,[{kex,[K1b]}]}, % N.B. 1669 {append, [{kex,[K2a]}]}]}, 1670 {silently_accept_hosts, true}, 1671 {save_accepted_host, false}, 1672 {user_interaction, false} 1673 ], 1674 {ok,C2} = rpc:call(Node, ssh, connect, [loopback, ?SSH_DEFAULT_PORT, C2_Opts]), 1675 {algorithms,As2} = rpc:call(Node, ssh, connection_info, [C2, algorithms]), 1676 K2a = proplists:get_value(kex, As2), 1677 Ch1 = proplists:get_value(encrypt, As2), 1678 Ch1 = proplists:get_value(decrypt, As2), 1679 {options,Os2} = rpc:call(Node, ssh, connection_info, [C2, options]), 1680 ct:log("C2 opts:~n~p~n~nalgorithms:~n~p~n~noptions:~n~p", [C2_Opts,As2,Os2]), 1681 1682 stop_node_nice(Node) 1683 end. 1684 1685%%%---------------------------------------------------------------- 1686config_file_modify_algorithms_order(Config) -> 1687 %% First find common algs: 1688 ServerAlgs = ssh_test_lib:default_algorithms(sshd), 1689 OurAlgs = ssh_transport:supported_algorithms(), % Incl disabled but supported 1690 CommonAlgs = ssh_test_lib:intersection(ServerAlgs, OurAlgs), 1691 ct:log("ServerAlgs =~n~p~n~nOurAlgs =~n~p~n~nCommonAlgs =~n~p",[ServerAlgs,OurAlgs,CommonAlgs]), 1692 Nkex = length(proplists:get_value(kex, CommonAlgs, [])), 1693 case {ServerAlgs, ssh_test_lib:some_empty(CommonAlgs)} of 1694 {[],_} -> 1695 {skip, "No server algorithms found"}; 1696 {_,true} -> 1697 {fail, "Missing common algorithms"}; 1698 _ when Nkex<3 -> 1699 {skip, "Not enough number of common kex"}; 1700 _ -> 1701 %% Then find three common kex and one common cipher: 1702 [K1,K2,K3|_] = proplists:get_value(kex, CommonAlgs), 1703 [{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs), 1704 1705 %% Make config file: 1706 Contents = 1707 [{ssh, [{preferred_algorithms, 1708 [{cipher, [Ch1]}, 1709 {kex, [K1]} 1710 ]}, 1711 {server_options, 1712 [{modify_algorithms, 1713 [{rm, [{kex, [K1]}]}, 1714 {append, [{kex, [K2]}]} 1715 ]} 1716 ]}, 1717 {client_options, 1718 [{modify_algorithms, 1719 [{rm, [{kex, [K1]}]}, 1720 {append, [{kex, [K3]}]} 1721 ]} 1722 ]} 1723 ]} 1724 ], 1725 %% write the file: 1726 PrivDir = proplists:get_value(priv_dir, Config), 1727 ConfFile = filename:join(PrivDir,"c3.config"), 1728 {ok,D} = file:open(ConfFile, [write]), 1729 io:format(D, "~p.~n", [Contents]), 1730 file:close(D), 1731 {ok,Cnfs} = file:read_file(ConfFile), 1732 ct:log("c3.config:~n~s", [Cnfs]), 1733 1734 %% Start the slave node with the configuration just made: 1735 {ok,Node} = start_node(random_node_name(?MODULE), ConfFile), 1736 1737 R0 = rpc:call(Node, ssh, default_algorithms, []), 1738 ct:log("R0 = ~p",[R0]), 1739 R0 = ssh:default_algorithms(), 1740 1741 %% Start ssh on the slave. This should apply the ConfFile: 1742 ok = rpc:call(Node, ssh, start, []), 1743 R1 = rpc:call(Node, ssh, default_algorithms, []), 1744 ct:log("R1 = ~p",[R1]), 1745 [{kex,[K1]} | _] = R1, 1746 1747 %% Start a daemon 1748 {Server, Host, Port} = rpc:call(Node, ssh_test_lib, std_daemon, [Config, []]), 1749 {ok,ServerInfo} = rpc:call(Node, ssh, daemon_info, [Server]), 1750 ct:log("ServerInfo =~n~p", [ServerInfo]), 1751 1752 %% Test that the server_options env key works: 1753 [K2] = proplists:get_value(kex, 1754 proplists:get_value(preferred_algorithms, 1755 proplists:get_value(options, ServerInfo))), 1756 1757 {badrpc, {'EXIT', {{badmatch,ExpectedError}, _}}} = 1758 %% No common kex algorithms expected. 1759 rpc:call(Node, ssh_test_lib, std_connect, [Config, Host, Port, []]), 1760 {error,"Key exchange failed"} = ExpectedError, 1761 1762 C = rpc:call(Node, ssh_test_lib, std_connect, 1763 [Config, Host, Port, 1764 [{modify_algorithms,[{append,[{kex,[K2]}]}]}]]), 1765 ConnInfo = rpc:call(Node, ssh, connection_info, [C]), 1766 ct:log("ConnInfo =~n~p", [ConnInfo]), 1767 Algs = proplists:get_value(algorithms, ConnInfo), 1768 ct:log("Algs =~n~p", [Algs]), 1769 ConnOptions = proplists:get_value(options, ConnInfo), 1770 ConnPrefAlgs = proplists:get_value(preferred_algorithms, ConnOptions), 1771 1772 %% And now, are all levels appied in right order: 1773 [K3,K2] = proplists:get_value(kex, ConnPrefAlgs), 1774 1775 stop_node_nice(Node) 1776 end. 1777 1778 1779%%-------------------------------------------------------------------- 1780%% Internal functions ------------------------------------------------ 1781%%-------------------------------------------------------------------- 1782 1783start_node(Name, ConfigFile) -> 1784 Pa = filename:dirname(code:which(?MODULE)), 1785 test_server:start_node(Name, slave, [{args, 1786 " -pa " ++ Pa ++ 1787 " -config " ++ ConfigFile}]). 1788 1789stop_node_nice(Node) when is_atom(Node) -> 1790 test_server:stop_node(Node). 1791 1792random_node_name(BaseName) -> 1793 L = integer_to_list(erlang:unique_integer([positive])), 1794 lists:concat([BaseName,"___",L]). 1795 1796%%%---- 1797 1798expected_ssh_vsn(Str) -> 1799 try 1800 {ok,L} = application:get_all_key(ssh), 1801 proplists:get_value(vsn,L,"")++"\r\n" 1802 of 1803 Str -> true; 1804 "\r\n" -> true; 1805 _ -> false 1806 catch 1807 _:_ -> true %% ssh not started so we dont't know 1808 end. 1809 1810 1811fake_daemon(_Config) -> 1812 Parent = self(), 1813 %% start the server 1814 Server = spawn(fun() -> 1815 {ok,Sl} = gen_tcp:listen(0,[{packet,line}]), 1816 {ok,{Host,Port}} = inet:sockname(Sl), 1817 ct:log("fake_daemon listening on ~p:~p~n",[Host,Port]), 1818 Parent ! {sockname,self(),Host,Port}, 1819 Rsa = gen_tcp:accept(Sl), 1820 ct:log("Server gen_tcp:accept got ~p",[Rsa]), 1821 {ok,S} = Rsa, 1822 receive 1823 {tcp, S, Id} -> Parent ! {id,self(),Id} 1824 after 1825 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1826 end 1827 end), 1828 %% Get listening host and port 1829 receive 1830 {sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort} 1831 after 1832 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) 1833 end. 1834