diff options
Diffstat (limited to 'lib/ssh/test/ssh_basic_SUITE.erl')
-rw-r--r-- | lib/ssh/test/ssh_basic_SUITE.erl | 1714 |
1 files changed, 527 insertions, 1187 deletions
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 2b3fadbbf4..6c4c215b3d 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -27,11 +27,52 @@ -include_lib("kernel/include/file.hrl"). %% Note: This directive should only be used in test suites. --compile(export_all). +%%-compile(export_all). + +%%% Test cases +-export([ + app_test/1, + appup_test/1, + cli/1, + close/1, + daemon_already_started/1, + daemon_opt_fd/1, + multi_daemon_opt_fd/1, + double_close/1, + exec/1, + exec_compressed/1, + exec_key_differs1/1, + exec_key_differs2/1, + exec_key_differs3/1, + exec_key_differs_fail/1, + idle_time/1, + inet6_option/1, + inet_option/1, + internal_error/1, + known_hosts/1, + misc_ssh_options/1, + openssh_zlib_basic_test/1, + packet_size_zero/1, + pass_phrase/1, + peername_sockname/1, + send/1, + shell/1, + shell_no_unicode/1, + shell_unicode_string/1, + ssh_info_print/1, + key_callback/1, + key_callback_options/1 + ]). + +%%% Common test callbacks +-export([suite/0, all/0, groups/0, + init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2 + ]). -define(NEWLINE, <<"\r\n">>). --define(REKEY_DATA_TMO, 65000). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- @@ -44,63 +85,46 @@ all() -> appup_test, {group, dsa_key}, {group, rsa_key}, + {group, ecdsa_sha2_nistp256_key}, + {group, ecdsa_sha2_nistp384_key}, + {group, ecdsa_sha2_nistp521_key}, {group, dsa_pass_key}, {group, rsa_pass_key}, + {group, host_user_key_differs}, + {group, key_cb}, {group, internal_error}, - connectfun_disconnectfun_server, - connectfun_disconnectfun_client, - {group, renegotiate}, daemon_already_started, - server_password_option, - server_userpassword_option, - {group, dir_options}, double_close, - ssh_connect_timeout, - ssh_connect_arg4_timeout, + daemon_opt_fd, + multi_daemon_opt_fd, packet_size_zero, - ssh_daemon_minimal_remote_max_packet_size_option, - ssh_msg_debug_fun_option_client, - ssh_msg_debug_fun_option_server, - disconnectfun_option_server, - disconnectfun_option_client, - unexpectedfun_option_server, - unexpectedfun_option_client, - preferred_algorithms, - id_string_no_opt_client, - id_string_own_string_client, - id_string_random_client, - id_string_no_opt_server, - id_string_own_string_server, - id_string_random_server, - {group, hardening_tests}, ssh_info_print ]. groups() -> [{dsa_key, [], basic_tests()}, {rsa_key, [], basic_tests()}, + {ecdsa_sha2_nistp256_key, [], basic_tests()}, + {ecdsa_sha2_nistp384_key, [], basic_tests()}, + {ecdsa_sha2_nistp521_key, [], basic_tests()}, + {host_user_key_differs, [], [exec_key_differs1, + exec_key_differs2, + exec_key_differs3, + exec_key_differs_fail]}, {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, - {internal_error, [], [internal_error]}, - {renegotiate, [], [rekey, rekey_limit, renegotiate1, renegotiate2]}, - {hardening_tests, [], [ssh_connect_nonegtimeout_connected_parallel, - ssh_connect_nonegtimeout_connected_sequential, - ssh_connect_negtimeout_parallel, - ssh_connect_negtimeout_sequential, - max_sessions_ssh_connect_parallel, - max_sessions_ssh_connect_sequential, - max_sessions_sftp_start_channel_parallel, - max_sessions_sftp_start_channel_sequential - ]}, - {dir_options, [], [user_dir_option, - system_dir_option]} + {key_cb, [], [key_callback, key_callback_options]}, + {internal_error, [], [internal_error]} ]. basic_tests() -> [send, close, peername_sockname, - exec, exec_compressed, shell, cli, known_hosts, - idle_time, openssh_zlib_basic_test, misc_ssh_options, inet_option]. + exec, exec_compressed, + shell, shell_no_unicode, shell_unicode_string, + cli, known_hosts, + idle_time, openssh_zlib_basic_test, + misc_ssh_options, inet_option, inet6_option]. %%-------------------------------------------------------------------- @@ -116,8 +140,6 @@ end_per_suite(_Config) -> ssh:stop(), crypto:stop(). %%-------------------------------------------------------------------- -init_per_group(hardening_tests, Config) -> - init_per_group(dsa_key, Config); init_per_group(dsa_key, Config) -> DataDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -128,6 +150,39 @@ init_per_group(rsa_key, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:setup_rsa(DataDir, PrivDir), Config; +init_per_group(ecdsa_sha2_nistp256_key, Config) -> + case lists:member('ecdsa-sha2-nistp256', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("256", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; +init_per_group(ecdsa_sha2_nistp384_key, Config) -> + case lists:member('ecdsa-sha2-nistp384', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("384", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; +init_per_group(ecdsa_sha2_nistp521_key, Config) -> + case lists:member('ecdsa-sha2-nistp521', + ssh_transport:default_algorithms(public_key)) of + true -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_ecdsa("521", DataDir, PrivDir), + Config; + false -> + {skip, unsupported_pub_key} + end; init_per_group(rsa_pass_key, Config) -> DataDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -138,6 +193,26 @@ init_per_group(dsa_pass_key, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:setup_dsa_pass_pharse(DataDir, PrivDir, "Password"), [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config]; +init_per_group(host_user_key_differs, Config) -> + Data = ?config(data_dir, Config), + Sys = filename:join(?config(priv_dir, Config), system_rsa), + SysUsr = filename:join(Sys, user), + Usr = filename:join(?config(priv_dir, Config), user_ecdsa_256), + file:make_dir(Sys), + file:make_dir(SysUsr), + file:make_dir(Usr), + file:copy(filename:join(Data, "ssh_host_rsa_key"), filename:join(Sys, "ssh_host_rsa_key")), + file:copy(filename:join(Data, "ssh_host_rsa_key.pub"), filename:join(Sys, "ssh_host_rsa_key.pub")), + file:copy(filename:join(Data, "id_ecdsa256"), filename:join(Usr, "id_ecdsa")), + file:copy(filename:join(Data, "id_ecdsa256.pub"), filename:join(Usr, "id_ecdsa.pub")), + ssh_test_lib:setup_ecdsa_auth_keys("256", Usr, SysUsr), + ssh_test_lib:setup_rsa_known_host(Sys, Usr), + Config; +init_per_group(key_cb, Config) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:setup_dsa(DataDir, PrivDir), + Config; init_per_group(internal_error, Config) -> DataDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -189,8 +264,6 @@ init_per_group(dir_options, Config) -> init_per_group(_, Config) -> Config. -end_per_group(hardening_tests, Config) -> - end_per_group(dsa_key, Config); end_per_group(dsa_key, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:clean_dsa(PrivDir), @@ -207,6 +280,10 @@ end_per_group(rsa_pass_key, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:clean_rsa(PrivDir), Config; +end_per_group(key_cb, Config) -> + PrivDir = ?config(priv_dir, Config), + ssh_test_lib:clean_dsa(PrivDir), + Config; end_per_group(internal_error, Config) -> PrivDir = ?config(priv_dir, Config), ssh_test_lib:clean_dsa(PrivDir), @@ -215,6 +292,33 @@ end_per_group(internal_error, Config) -> end_per_group(_, Config) -> Config. %%-------------------------------------------------------------------- +init_per_testcase(TC, Config) when TC==shell_no_unicode ; + TC==shell_unicode_string -> + PrivDir = ?config(priv_dir, Config), + UserDir = ?config(priv_dir, Config), + SysDir = ?config(data_dir, Config), + ssh:start(), + Sftpd = {_Pid, _Host, Port} = + ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, PrivDir}, + {user_passwords, [{"foo", "bar"}]}]), + ct:sleep(500), + IO = ssh_test_lib:start_io_server(), + Shell = ssh_test_lib:start_shell(Port, IO, UserDir, + [{silently_accept_hosts, true}, + {user,"foo"},{password,"bar"}]), + ct:log("IO=~p, Shell=~p, self()=~p",[IO,Shell,self()]), + ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p", + [file:native_name_encoding(),io:getopts()]), + wait_for_erlang_first_line([{io,IO}, {shell,Shell}, {sftpd, Sftpd} | Config]); + +init_per_testcase(inet6_option, Config) -> + case ssh_test_lib:has_inet6_address() of + true -> + init_per_testcase('__default__', Config); + false -> + {skip,"No ipv6 interface address"} + end; init_per_testcase(_TestCase, Config) -> ssh:start(), Config. @@ -224,6 +328,15 @@ end_per_testcase(TestCase, Config) when TestCase == server_password_option; UserDir = filename:join(?config(priv_dir, Config), nopubkey), ssh_test_lib:del_dirs(UserDir), end_per_testcase(Config); +end_per_testcase(TC, Config) when TC==shell_no_unicode ; + TC==shell_unicode_string -> + case ?config(sftpd, Config) of + {Pid, _, _} -> + ssh:stop_daemon(Pid), + ssh:stop(); + _ -> + ssh:stop() + end; end_per_testcase(_TestCase, Config) -> end_per_testcase(Config). end_per_testcase(_Config) -> @@ -233,21 +346,18 @@ end_per_testcase(_Config) -> %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -app_test() -> - [{doc, "App lication consistency test."}]. +%%% Application consistency test. app_test(Config) when is_list(Config) -> ?t:app_test(ssh), ok. %%-------------------------------------------------------------------- -appup_test() -> - [{doc, "Appup file consistency test."}]. +%%% Appup file consistency test. appup_test(Config) when is_list(Config) -> ok = ?t:appup_test(ssh). %%-------------------------------------------------------------------- -misc_ssh_options() -> - [{doc, "Test that we can set some misc options not tested elsewhere, " - "some options not yet present are not decided if we should support or " - "if they need thier own test case."}]. +%%% Test that we can set some misc options not tested elsewhere +%%% some options not yet present are not decided if we should support or +%%% if they need thier own test case. misc_ssh_options(Config) when is_list(Config) -> SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), @@ -261,8 +371,7 @@ misc_ssh_options(Config) when is_list(Config) -> basic_test([{client_opts, CMiscOpt1}, {server_opts, SMiscOpt1}]). %%-------------------------------------------------------------------- -inet_option() -> - [{doc, "Test configuring IPv4"}]. +%%% Test configuring IPv4 inet_option(Config) when is_list(Config) -> SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), @@ -278,8 +387,7 @@ inet_option(Config) when is_list(Config) -> {server_opts, [{inet, inet} | ServerOpts]}]). %%-------------------------------------------------------------------- -inet6_option() -> - [{doc, "Test configuring IPv6"}]. +%%% Test configuring IPv6 inet6_option(Config) when is_list(Config) -> SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), @@ -295,8 +403,7 @@ inet6_option(Config) when is_list(Config) -> {server_opts, [{inet, inet6} | ServerOpts]}]). %%-------------------------------------------------------------------- -exec() -> - [{doc, "Test api function ssh_connection:exec"}]. +%%% Test api function ssh_connection:exec exec(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -337,37 +444,41 @@ exec(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- -exec_compressed() -> - [{doc, "Test that compression option works"}]. +%%% Test that compression option works exec_compressed(Config) when is_list(Config) -> - process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + case ssh_test_lib:ssh_supports(zlib, compression) of + false -> + {skip, "zlib compression is not supported"}; - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, - {preferred_algorithms,[{compression, [zlib]}]}, - {failfun, fun ssh_test_lib:failfun/2}]), + true -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, + {preferred_algorithms,[{compression, [zlib]}]}, + {failfun, fun ssh_test_lib:failfun/2}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}]), - {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), - success = ssh_connection:exec(ConnectionRef, ChannelId, - "1+1.", infinity), - Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}}, - case ssh_test_lib:receive_exec_result(Data) of - expected -> - ok; - Other -> - ct:fail(Other) - end, - ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), - ssh:stop_daemon(Pid). + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}]), + {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + success = ssh_connection:exec(ConnectionRef, ChannelId, + "1+1.", infinity), + Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"2\n">>}}, + case ssh_test_lib:receive_exec_result(Data) of + expected -> + ok; + Other -> + ct:fail(Other) + end, + ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId), + ssh:stop_daemon(Pid) + end. %%-------------------------------------------------------------------- -idle_time() -> - [{doc, "Idle timeout test"}]. +%%% Idle timeout test idle_time(Config) -> SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), @@ -387,203 +498,105 @@ idle_time(Config) -> {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000) end, ssh:stop_daemon(Pid). + %%-------------------------------------------------------------------- -rekey() -> - [{doc, "Idle timeout test"}]. -rekey(Config) -> - SystemDir = ?config(data_dir, Config), +%%% Test that ssh:shell/2 works +shell(Config) when is_list(Config) -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), + + {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + ct:sleep(500), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2}, - {user_passwords, - [{"simon", "says"}]}, - {rekey_limit, 0}]), - - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user, "simon"}, - {password, "says"}, - {user_interaction, false}, - {rekey_limit, 0}]), + IO = ssh_test_lib:start_io_server(), + Shell = ssh_test_lib:start_shell(Port, IO, UserDir), receive - after ?REKEY_DATA_TMO -> - %%By this time rekeying would have been done - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid) + {'EXIT', _, _} -> + ct:fail(no_ssh_connection); + ErlShellStart -> + ct:log("Erlang shell start: ~p~n", [ErlShellStart]), + do_shell(IO, Shell) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. -%%-------------------------------------------------------------------- -rekey_limit() -> - [{doc, "Test rekeying by data volume"}]. -rekey_limit(Config) -> - SystemDir = ?config(data_dir, Config), - UserDir = ?config(priv_dir, Config), - DataFile = filename:join(UserDir, "rekey.data"), - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, - [{"simon", "says"}]}]), - {ok, SftpPid, ConnectionRef} = - ssh_sftp:start_channel(Host, Port, [{system_dir, SystemDir}, - {user_dir, UserDir}, - {user, "simon"}, - {password, "says"}, - {rekey_limit, 2500}, - {user_interaction, false}, - {silently_accept_hosts, true}]), - - Kex1 = get_kex_init(ConnectionRef), - - timer:sleep(?REKEY_DATA_TMO), - Kex1 = get_kex_init(ConnectionRef), - - Data = lists:duplicate(9000,1), - ok = ssh_sftp:write_file(SftpPid, DataFile, Data), - - timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), - - false = (Kex2 == Kex1), - - timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), - - ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"), - - timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), - - false = (Kex2 == Kex1), - - timer:sleep(?REKEY_DATA_TMO), - Kex2 = get_kex_init(ConnectionRef), - - - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- -renegotiate1() -> - [{doc, "Test rekeying with simulataneous send request"}]. -renegotiate1(Config) -> - SystemDir = ?config(data_dir, Config), - UserDir = ?config(priv_dir, Config), - DataFile = filename:join(UserDir, "renegotiate1.data"), - - {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, - [{"simon", "says"}]}]), - RPort = ssh_test_lib:inet_port(), - - {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort), - - {ok, SftpPid, ConnectionRef} = - ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir}, - {user_dir, UserDir}, - {user, "simon"}, - {password, "says"}, - {user_interaction, false}, - {silently_accept_hosts, true}]), - - Kex1 = get_kex_init(ConnectionRef), - - {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), - - ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), - - ssh_relay:hold(RelayPid, rx, 20, 1000), - ssh_connection_handler:renegotiate(ConnectionRef), - spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), - - timer:sleep(2000), - - Kex2 = get_kex_init(ConnectionRef), - - false = (Kex2 == Kex1), - ssh_relay:stop(RelayPid), - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - %%-------------------------------------------------------------------- -renegotiate2() -> - [{doc, "Test rekeying with inflight messages from peer"}]. -renegotiate2(Config) -> - SystemDir = ?config(data_dir, Config), - UserDir = ?config(priv_dir, Config), - DataFile = filename:join(UserDir, "renegotiate1.data"), - - {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, - [{"simon", "says"}]}]), - RPort = ssh_test_lib:inet_port(), +%%% Test that we could user different types of host pubkey and user pubkey +exec_key_differs1(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp256']). - {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort), +exec_key_differs2(Config) -> exec_key_differs(Config, ['ssh-dss','ecdsa-sha2-nistp256']). - {ok, SftpPid, ConnectionRef} = - ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir}, - {user_dir, UserDir}, - {user, "simon"}, - {password, "says"}, - {user_interaction, false}, - {silently_accept_hosts, true}]), - - Kex1 = get_kex_init(ConnectionRef), - - {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]), +exec_key_differs3(Config) -> exec_key_differs(Config, ['ecdsa-sha2-nistp384','ecdsa-sha2-nistp256']). - ok = ssh_sftp:write(SftpPid, Handle, "hi\n"), - ssh_relay:hold(RelayPid, rx, 20, infinity), - spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end), - %% need a small pause here to ensure ssh_sftp:write is executed - ct:sleep(10), - ssh_connection_handler:renegotiate(ConnectionRef), - ssh_relay:release(RelayPid, rx), - timer:sleep(2000), +exec_key_differs(Config, UserPKAlgs) -> + case lists:usort(['ssh-rsa'|UserPKAlgs]) + -- ssh_transport:supported_algorithms(public_key) + of + [] -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system_rsa), + SystemUserDir = filename:join(SystemDir, user), + UserDir = filename:join(?config(priv_dir, Config), user_ecdsa_256), + + {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, SystemUserDir}, + {preferred_algorithms, + [{public_key,['ssh-rsa']}]}]), + ct:sleep(500), - Kex2 = get_kex_init(ConnectionRef), + IO = ssh_test_lib:start_io_server(), + Shell = ssh_test_lib:start_shell(Port, IO, UserDir, + [{preferred_algorithms,[{public_key,['ssh-rsa']}]}, + {pref_public_key_algs,UserPKAlgs} + ]), - false = (Kex2 == Kex1), - ssh_relay:stop(RelayPid), - ssh_sftp:stop_channel(SftpPid), - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). + receive + {'EXIT', _, _} -> + ct:fail(no_ssh_connection); + ErlShellStart -> + ct:log("Erlang shell start: ~p~n", [ErlShellStart]), + do_shell(IO, Shell) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) + end; + UnsupportedPubKeys -> + {skip, io_lib:format("~p unsupported",[UnsupportedPubKeys])} + end. + %%-------------------------------------------------------------------- -shell() -> - [{doc, "Test that ssh:shell/2 works"}]. -shell(Config) when is_list(Config) -> +exec_key_differs_fail(Config) when is_list(Config) -> process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + SystemDir = filename:join(?config(priv_dir, Config), system_rsa), + SystemUserDir = filename:join(SystemDir, user), + UserDir = filename:join(?config(priv_dir, Config), user_ecdsa_256), - {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2}]), + {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, SystemUserDir}, + {preferred_algorithms, + [{public_key,['ssh-rsa']}]}]), ct:sleep(500), IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir), + ssh_test_lib:start_shell(Port, IO, UserDir, + [{preferred_algorithms,[{public_key,['ssh-rsa']}]}, + {pref_public_key_algs,['ssh-dss']}]), receive {'EXIT', _, _} -> - ct:fail(no_ssh_connection); + ok; ErlShellStart -> - ct:pal("Erlang shell start: ~p~n", [ErlShellStart]), - do_shell(IO, Shell) + ct:log("Erlang shell start: ~p~n", [ErlShellStart]), + ct:fail(connection_not_rejected) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- -cli() -> - [{doc, ""}]. cli(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -609,17 +622,20 @@ cli(Config) when is_list(Config) -> {ssh_cm, ConnectionRef, {data,0,0, <<"\r\nYou are accessing a dummy, type \"q\" to exit\r\n\n">>}} -> ok = ssh_connection:send(ConnectionRef, ChannelId, <<"q">>) + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive {ssh_cm, ConnectionRef,{closed, ChannelId}} -> ok + after + 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- -daemon_already_started() -> - [{doc, "Test that get correct error message if you try to start a daemon", - "on an adress that already runs a daemon see also seq10667"}]. +%%% Test that get correct error message if you try to start a daemon +%%% on an adress that already runs a daemon see also seq10667 daemon_already_started(Config) when is_list(Config) -> SystemDir = ?config(data_dir, Config), UserDir = ?config(priv_dir, Config), @@ -634,427 +650,7 @@ daemon_already_started(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- -server_password_option() -> - [{doc, "validate to server that uses the 'password' option"}]. -server_password_option(Config) when is_list(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}]), - - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), - - Reason = "Unable to connect using the available authentication methods", - - {error, Reason} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "foo"}, - {user_interaction, false}, - {user_dir, UserDir}]), - - ct:pal("Test of wrong password: Error msg: ~p ~n", [Reason]), - - ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- - -server_userpassword_option() -> - [{doc, "validate to server that uses the 'password' option"}]. -server_userpassword_option(Config) when is_list(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, PrivDir}, - {user_passwords, [{"vego", "morot"}]}]), - - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), - ssh:close(ConnectionRef), - - Reason = "Unable to connect using the available authentication methods", - - {error, Reason} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_interaction, false}, - {user_dir, UserDir}]), - {error, Reason} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "vego"}, - {password, "foo"}, - {user_interaction, false}, - {user_dir, UserDir}]), - ssh:stop_daemon(Pid). - -%%-------------------------------------------------------------------- -system_dir_option(Config) -> - DirUnread = proplists:get_value(unreadable_dir,Config), - FileRead = proplists:get_value(readable_file,Config), - - case ssh_test_lib:daemon([{system_dir, DirUnread}]) of - {error,{eoptions,{{system_dir,DirUnread},eacces}}} -> - ok; - {Pid1,_Host1,Port1} when is_pid(Pid1),is_integer(Port1) -> - ssh:stop_daemon(Pid1), - ct:fail("Didn't detect that dir is unreadable", []) - end, - - case ssh_test_lib:daemon([{system_dir, FileRead}]) of - {error,{eoptions,{{system_dir,FileRead},enotdir}}} -> - ok; - {Pid2,_Host2,Port2} when is_pid(Pid2),is_integer(Port2) -> - ssh:stop_daemon(Pid2), - ct:fail("Didn't detect that option is a plain file", []) - end. - - -user_dir_option(Config) -> - DirUnread = proplists:get_value(unreadable_dir,Config), - FileRead = proplists:get_value(readable_file,Config), - %% Any port will do (beware, implementation knowledge!): - Port = 65535, - - case ssh:connect("localhost", Port, [{user_dir, DirUnread}]) of - {error,{eoptions,{{user_dir,DirUnread},eacces}}} -> - ok; - {error,econnrefused} -> - ct:fail("Didn't detect that dir is unreadable", []) - end, - - case ssh:connect("localhost", Port, [{user_dir, FileRead}]) of - {error,{eoptions,{{user_dir,FileRead},enotdir}}} -> - ok; - {error,econnrefused} -> - ct:fail("Didn't detect that option is a plain file", []) - end. - -%%-------------------------------------------------------------------- -ssh_msg_debug_fun_option_client() -> - [{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}]. -ssh_msg_debug_fun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}]), - Parent = self(), - DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, - - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}, - {ssh_msg_debug_fun,DbgFun}]), - %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(ConnectionRef,{ssh_msg_debug,false,<<"Hello">>,<<>>}), - receive - {msg_dbg,X={ConnectionRef,false,<<"Hello">>,<<>>}} -> - ct:log("Got expected dbg msg ~p",[X]), - ssh:stop_daemon(Pid); - {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> - ct:log("Got dbg msg but bad ConnectionRef (~p expected) ~p",[ConnectionRef,X]), - ssh:stop_daemon(Pid), - {fail, "Bad ConnectionRef received"}; - {msg_dbg,X} -> - ct:log("Got bad dbg msg ~p",[X]), - ssh:stop_daemon(Pid), - {fail,"Bad msg received"} - after 1000 -> - ssh:stop_daemon(Pid), - {fail,timeout} - end. - -%%-------------------------------------------------------------------- -connectfun_disconnectfun_server(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - Ref = make_ref(), - ConnFun = fun(_,_,_) -> Parent ! {connect,Ref} end, - DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}, - {disconnectfun, DiscFun}, - {connectfun, ConnFun}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}]), - receive - {connect,Ref} -> - ssh:close(ConnectionRef), - receive - {disconnect,Ref,R} -> - ct:log("Disconnect result: ~p",[R]), - ssh:stop_daemon(Pid) - after 2000 -> - {fail, "No disconnectfun action"} - end - after 2000 -> - {fail, "No connectfun action"} - end. - -%%-------------------------------------------------------------------- -connectfun_disconnectfun_client(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - Ref = make_ref(), - DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}]), - _ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {disconnectfun, DiscFun}, - {user_interaction, false}]), - ssh:stop_daemon(Pid), - receive - {disconnect,Ref,R} -> - ct:log("Disconnect result: ~p",[R]) - after 2000 -> - {fail, "No disconnectfun action"} - end. - -%%-------------------------------------------------------------------- -ssh_msg_debug_fun_option_server() -> - [{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}]. -ssh_msg_debug_fun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - DbgFun = fun(ConnRef,Displ,Msg,Lang) -> Parent ! {msg_dbg,{ConnRef,Displ,Msg,Lang}} end, - ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}, - {connectfun, ConnFun}, - {ssh_msg_debug_fun, DbgFun}]), - _ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}]), - receive - {connection_pid,Server} -> - %% Beware, implementation knowledge: - gen_fsm:send_all_state_event(Server,{ssh_msg_debug,false,<<"Hello">>,<<>>}), - receive - {msg_dbg,X={_,false,<<"Hello">>,<<>>}} -> - ct:log("Got expected dbg msg ~p",[X]), - ssh:stop_daemon(Pid); - {msg_dbg,X} -> - ct:log("Got bad dbg msg ~p",[X]), - ssh:stop_daemon(Pid), - {fail,"Bad msg received"} - after 3000 -> - ssh:stop_daemon(Pid), - {fail,timeout2} - end - after 3000 -> - ssh:stop_daemon(Pid), - {fail,timeout1} - end. - -%%-------------------------------------------------------------------- -disconnectfun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}, - {disconnectfun, DisConnFun}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}]), - ssh:close(ConnectionRef), - receive - {disconnect,Reason} -> - ct:log("Server detected disconnect: ~p",[Reason]), - ssh:stop_daemon(Pid), - ok - after 3000 -> - receive - X -> ct:log("received ~p",[X]) - after 0 -> ok - end, - {fail,"Timeout waiting for disconnect"} - end. - -%%-------------------------------------------------------------------- -disconnectfun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}]), - _ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}, - {disconnectfun, DisConnFun}]), - ssh:stop_daemon(Pid), - receive - {disconnect,Reason} -> - ct:log("Client detected disconnect: ~p",[Reason]), - ok - after 3000 -> - receive - X -> ct:log("received ~p",[X]) - after 0 -> ok - end, - {fail,"Timeout waiting for disconnect"} - end. - -%%-------------------------------------------------------------------- -unexpectedfun_option_server(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end, - UnexpFun = fun(Msg,Peer) -> - Parent ! {unexpected,Msg,Peer,self()}, - skip - end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}, - {connectfun, ConnFun}, - {unexpectedfun, UnexpFun}]), - _ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}]), - receive - {connection_pid,Server} -> - %% Beware, implementation knowledge: - Server ! unexpected_message, - receive - {unexpected, unexpected_message, {{_,_,_,_},_}, _} -> ok; - {unexpected, unexpected_message, Peer, _} -> ct:fail("Bad peer ~p",[Peer]); - M = {unexpected, _, _, _} -> ct:fail("Bad msg ~p",[M]) - after 3000 -> - ssh:stop_daemon(Pid), - {fail,timeout2} - end - after 3000 -> - ssh:stop_daemon(Pid), - {fail,timeout1} - end. - -%%-------------------------------------------------------------------- -unexpectedfun_option_client(Config) -> - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - SysDir = ?config(data_dir, Config), - - Parent = self(), - UnexpFun = fun(Msg,Peer) -> - Parent ! {unexpected,Msg,Peer,self()}, - skip - end, - - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir}, - {user_dir, UserDir}, - {password, "morot"}, - {failfun, fun ssh_test_lib:failfun/2}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user, "foo"}, - {password, "morot"}, - {user_dir, UserDir}, - {user_interaction, false}, - {unexpectedfun, UnexpFun}]), - %% Beware, implementation knowledge: - ConnectionRef ! unexpected_message, - - receive - {unexpected, unexpected_message, {{_,_,_,_},_}, ConnectionRef} -> - ok; - {unexpected, unexpected_message, Peer, ConnectionRef} -> - ct:fail("Bad peer ~p",[Peer]); - M = {unexpected, _, _, _} -> - ct:fail("Bad msg ~p",[M]) - after 3000 -> - ssh:stop_daemon(Pid), - {fail,timeout} - end. - -%%-------------------------------------------------------------------- -known_hosts() -> - [{doc, "check that known_hosts is updated correctly"}]. +%%% check that known_hosts is updated correctly known_hosts(Config) when is_list(Config) -> SystemDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -1080,8 +676,7 @@ known_hosts(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- -pass_phrase() -> - [{doc, "Test that we can use keyes protected by pass phrases"}]. +%%% Test that we can use keyes protected by pass phrases pass_phrase(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -1100,27 +695,75 @@ pass_phrase(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +%%% Test that we can use key callback +key_callback(Config) when is_list(Config) -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + NoPubKeyDir = filename:join(UserDir, "nopubkey"), + file:make_dir(NoPubKeyDir), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + + ConnectOpts = [{silently_accept_hosts, true}, + {user_dir, NoPubKeyDir}, + {user_interaction, false}, + {key_cb, ssh_key_cb}], -internal_error() -> - [{doc,"Test that client does not hang if disconnects due to internal error"}]. + ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts), + + {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + ssh:stop_daemon(Pid). + + +%%-------------------------------------------------------------------- +%%% Test that we can use key callback with callback options +key_callback_options(Config) when is_list(Config) -> + process_flag(trap_exit, true), + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), + + NoPubKeyDir = filename:join(UserDir, "nopubkey"), + file:make_dir(NoPubKeyDir), + + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + + {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_dsa")), + + ConnectOpts = [{silently_accept_hosts, true}, + {user_dir, NoPubKeyDir}, + {user_interaction, false}, + {key_cb, {ssh_key_cb_options, [{priv_key, PrivKey}]}}], + + ConnectionRef = ssh_test_lib:connect(Host, Port, ConnectOpts), + + {ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity), + ssh:stop_daemon(Pid). + + +%%-------------------------------------------------------------------- +%%% Test that client does not hang if disconnects due to internal error internal_error(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), UserDir = ?config(priv_dir, Config), {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2}]), + {user_dir, UserDir}, + {failfun, fun ssh_test_lib:failfun/2}]), {error, Error} = - ssh:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}]), + ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}]), check_error(Error), ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- -send() -> - [{doc, "Test ssh_connection:send/3"}]. +%%% Test ssh_connection:send/3 send(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -1140,8 +783,7 @@ send(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -peername_sockname() -> - [{doc, "Test ssh:connection_info([peername, sockname])"}]. +%%% Test ssh:connection_info([peername, sockname]) peername_sockname(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -1163,13 +805,13 @@ peername_sockname(Config) when is_list(Config) -> ssh:connection_info(ConnectionRef, [peer]), [{sockname, {HostSockClient,PortSockClient} = ClientSock}] = ssh:connection_info(ConnectionRef, [sockname]), - ct:pal("Client: ~p ~p", [ClientPeer, ClientSock]), + ct:log("Client: ~p ~p", [ClientPeer, ClientSock]), receive {ssh_cm, ConnectionRef, {data, ChannelId, _, Response}} -> {PeerNameSrv,SockNameSrv} = binary_to_term(Response), {HostPeerSrv,PortPeerSrv} = PeerNameSrv, {HostSockSrv,PortSockSrv} = SockNameSrv, - ct:pal("Server: ~p ~p", [PeerNameSrv, SockNameSrv]), + ct:log("Server: ~p ~p", [PeerNameSrv, SockNameSrv]), host_equal(HostPeerSrv, HostSockClient), PortPeerSrv = PortSockClient, host_equal(HostSockSrv, HostPeerClient), @@ -1177,7 +819,7 @@ peername_sockname(Config) when is_list(Config) -> host_equal(HostSockSrv, Host), PortSockSrv = Port after 10000 -> - throw(timeout) + ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. host_equal(H1, H2) -> @@ -1191,8 +833,7 @@ ips(Name) when is_list(Name) -> %%-------------------------------------------------------------------- -close() -> - [{doc, "Client receives close when server closes"}]. +%%% Client receives close when server closes close(Config) when is_list(Config) -> process_flag(trap_exit, true), SystemDir = filename:join(?config(priv_dir, Config), system), @@ -1212,12 +853,11 @@ close(Config) when is_list(Config) -> {ssh_cm, Client,{closed, ChannelId}} -> ok after 5000 -> - ct:fail(timeout) + ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end. %%-------------------------------------------------------------------- -double_close() -> - [{doc, "Simulate that we try to close an already closed connection"}]. +%%% Simulate that we try to close an already closed connection double_close(Config) when is_list(Config) -> SystemDir = ?config(data_dir, Config), PrivDir = ?config(priv_dir, Config), @@ -1238,89 +878,66 @@ double_close(Config) when is_list(Config) -> ok = ssh:close(CM). %%-------------------------------------------------------------------- -ssh_connect_timeout() -> - [{doc, "Test connect_timeout option in ssh:connect/4"}]. -ssh_connect_timeout(_Config) -> - ConnTimeout = 2000, - {error,{faked_transport,connect,TimeoutToTransport}} = - ssh:connect("localhost", 12345, - [{transport,{tcp,?MODULE,tcp_closed}}, - {connect_timeout,ConnTimeout}], - 1000), - case TimeoutToTransport of - ConnTimeout -> ok; - Other -> - ct:log("connect_timeout is ~p but transport received ~p",[ConnTimeout,Other]), - {fail,"ssh:connect/4 wrong connect_timeout received in transport"} - end. +daemon_opt_fd(Config) -> + SystemDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + + {ok,S1} = gen_tcp:listen(0,[]), + {ok,Fd1} = prim_inet:getfd(S1), -%% Help for the test above -connect(_Host, _Port, _Opts, Timeout) -> - {error, {faked_transport,connect,Timeout}}. + {ok,Pid1} = ssh:daemon(0, [{system_dir, SystemDir}, + {fd,Fd1}, + {user_dir, UserDir}, + {user_passwords, [{"vego", "morot"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + + {ok,{_Host1,Port1}} = inet:sockname(S1), + {ok, C1} = ssh:connect("localhost", Port1, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user, "vego"}, + {password, "morot"}, + {user_interaction, false}]), + exit(C1, {shutdown, normal}), + ssh:stop_daemon(Pid1), + gen_tcp:close(S1). %%-------------------------------------------------------------------- -ssh_connect_arg4_timeout() -> - [{doc, "Test fourth argument in ssh:connect/4"}]. -ssh_connect_arg4_timeout(_Config) -> - Timeout = 1000, - Parent = self(), - %% start the server - Server = spawn(fun() -> - {ok,Sl} = gen_tcp:listen(0,[]), - {ok,{_,Port}} = inet:sockname(Sl), - Parent ! {port,self(),Port}, - Rsa = gen_tcp:accept(Sl), - ct:log("Server gen_tcp:accept got ~p",[Rsa]), - receive after 2*Timeout -> ok end %% let client timeout first - end), - - %% Get listening port - Port = receive - {port,Server,ServerPort} -> ServerPort - end, - - %% try to connect with a timeout, but "supervise" it - Client = spawn(fun() -> - T0 = erlang:monotonic_time(), - Rc = ssh:connect("localhost",Port,[],Timeout), - ct:log("Client ssh:connect got ~p",[Rc]), - Parent ! {done,self(),Rc,T0} - end), - - %% Wait for client reaction on the connection try: - receive - {done, Client, {error,timeout}, T0} -> - Msp = ms_passed(T0), - exit(Server,hasta_la_vista___baby), - Low = 0.9*Timeout, - High = 1.1*Timeout, - ct:log("Timeout limits: ~.4f - ~.4f ms, timeout " - "was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]), - if - Low<Msp, Msp<High -> ok; - true -> {fail, "timeout not within limits"} - end; +multi_daemon_opt_fd(Config) -> + SystemDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), - {done, Client, {error,Other}, _T0} -> - ct:log("Error message \"~p\" from the client is unexpected.",[{error,Other}]), - {fail, "Unexpected error message"}; - - {done, Client, {ok,_Ref}, _T0} -> - {fail,"ssh-connected ???"} - after - 5000 -> - exit(Server,hasta_la_vista___baby), - exit(Client,hasta_la_vista___baby), - {fail, "Didn't timeout"} - end. + Test = + fun() -> + {ok,S} = gen_tcp:listen(0,[]), + {ok,Fd} = prim_inet:getfd(S), + + {ok,Pid} = ssh:daemon(0, [{system_dir, SystemDir}, + {fd,Fd}, + {user_dir, UserDir}, + {user_passwords, [{"vego", "morot"}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + + {ok,{_Host,Port}} = inet:sockname(S), + {ok, C} = ssh:connect("localhost", Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user, "vego"}, + {password, "morot"}, + {user_interaction, false}]), + {S,Pid,C} + end, + + Tests = [Test(),Test(),Test(),Test(),Test(),Test()], -%% Help function, elapsed milliseconds since T0 -ms_passed(T0) -> - %% OTP 18 - erlang:convert_time_unit(erlang:monotonic_time() - T0, - native, - micro_seconds) / 1000. + [begin + gen_tcp:close(S), + ssh:stop_daemon(Pid), + exit(C, {shutdown, normal}) + end || {S,Pid,C} <- Tests]. %%-------------------------------------------------------------------- packet_size_zero(Config) -> @@ -1347,371 +964,53 @@ packet_size_zero(Config) -> receive {ssh_cm,Conn,{data,Chan,_Type,_Msg1}} = M -> - ct:pal("Got ~p",[M]), + ct:log("Got ~p",[M]), ct:fail(doesnt_obey_max_packet_size_0) after 5000 -> ok end. %%-------------------------------------------------------------------- -ssh_daemon_minimal_remote_max_packet_size_option(Config) -> - SystemDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - - {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, [{"vego", "morot"}]}, - {failfun, fun ssh_test_lib:failfun/2}, - {minimal_remote_max_packet_size, 14}]), - Conn = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}, - {user, "vego"}, - {password, "morot"}]), - - %% Try the limits of the minimal_remote_max_packet_size: - {ok, _ChannelId} = ssh_connection:session_channel(Conn, 100, 14, infinity), - {open_error,_,"Maximum packet size below 14 not supported",_} = - ssh_connection:session_channel(Conn, 100, 13, infinity), - - ssh:close(Conn), - ssh:stop_daemon(Server). - +shell_no_unicode(Config) -> + new_do_shell(?config(io,Config), + [new_prompt, + {type,"io:format(\"hej ~p~n\",[42])."}, + {expect,"hej 42"} + ]). + %%-------------------------------------------------------------------- -%% This test try every algorithm by connecting to an Erlang server -preferred_algorithms(Config) -> - SystemDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - - {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, [{"vego", "morot"}]}, - {failfun, fun ssh_test_lib:failfun/2}]), - Available = ssh:default_algorithms(), - Tests = [[{Tag,[Alg]}] || {Tag, SubAlgs} <- Available, - is_atom(hd(SubAlgs)), - Alg <- SubAlgs] - ++ [[{Tag,[{T1,[A1]},{T2,[A2]}]}] || {Tag, [{T1,As1},{T2,As2}]} <- Available, - A1 <- As1, - A2 <- As2], - ct:log("TESTS: ~p",[Tests]), - [connect_exec_channel(Host,Port,PrefAlgs) || PrefAlgs <- Tests], - ssh:stop_daemon(Server). - - -connect_exec_channel(_Host, Port, Algs) -> - ct:log("Try ~p",[Algs]), - ConnectionRef = ssh_test_lib:connect(Port, [{silently_accept_hosts, true}, - {user_interaction, false}, - {user, "vego"}, - {password, "morot"}, - {preferred_algorithms,Algs} - ]), - chan_exec(ConnectionRef, "2*21.", <<"42\n">>), - ssh:close(ConnectionRef). - -chan_exec(ConnectionRef, Cmnd, Expected) -> - {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), - success = ssh_connection:exec(ConnectionRef, ChannelId0,Cmnd, infinity), - Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Expected}}, - case ssh_test_lib:receive_exec_result(Data0) of - expected -> - ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0); - {unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} - = ExitStatus0} -> - ct:pal("0: Collected data ~p", [ExitStatus0]), - ssh_test_lib:receive_exec_result(Data0, - ConnectionRef, ChannelId0); - Other0 -> - ct:fail(Other0) - end. - -%%-------------------------------------------------------------------- -id_string_no_opt_client(Config) -> - {Server, _Host, Port} = fake_daemon(Config), - {error,_} = ssh:connect("localhost", Port, [], 1000), - receive - {id,Server,"SSH-2.0-Erlang/"++Vsn} -> - true = expected_ssh_vsn(Vsn); - {id,Server,Other} -> - ct:fail("Unexpected id: ~s.",[Other]) - after 5000 -> - {fail,timeout} - end. - -%%-------------------------------------------------------------------- -id_string_own_string_client(Config) -> - {Server, _Host, Port} = fake_daemon(Config), - {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}], 1000), - receive - {id,Server,"SSH-2.0-Pelle\r\n"} -> - ok; - {id,Server,Other} -> - ct:fail("Unexpected id: ~s.",[Other]) - after 5000 -> - {fail,timeout} - end. - -%%-------------------------------------------------------------------- -id_string_random_client(Config) -> - {Server, _Host, Port} = fake_daemon(Config), - {error,_} = ssh:connect("localhost", Port, [{id_string,random}], 1000), - receive - {id,Server,Id="SSH-2.0-Erlang"++_} -> - ct:fail("Unexpected id: ~s.",[Id]); - {id,Server,Rnd="SSH-2.0-"++_} -> - ct:log("Got correct ~s",[Rnd]); - {id,Server,Id} -> - ct:fail("Unexpected id: ~s.",[Id]) - after 5000 -> - {fail,timeout} - end. - -%%-------------------------------------------------------------------- -id_string_no_opt_server(Config) -> - {_Server, Host, Port} = std_daemon(Config, []), - {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]), - {ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000), - true = expected_ssh_vsn(Vsn). - -%%-------------------------------------------------------------------- -id_string_own_string_server(Config) -> - {_Server, Host, Port} = std_daemon(Config, [{id_string,"Olle"}]), - {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]), - {ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000). - -%%-------------------------------------------------------------------- -id_string_random_server(Config) -> - {_Server, Host, Port} = std_daemon(Config, [{id_string,random}]), - {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]), - {ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000), - case Rnd of - "Erlang"++_ -> ct:log("Id=~p",[Rnd]), - {fail,got_default_id}; - "Olle\r\n" -> {fail,got_previous_tests_value}; - _ -> ct:log("Got ~s.",[Rnd]) - end. - -%%-------------------------------------------------------------------- -ssh_connect_negtimeout_parallel(Config) -> ssh_connect_negtimeout(Config,true). -ssh_connect_negtimeout_sequential(Config) -> ssh_connect_negtimeout(Config,false). - -ssh_connect_negtimeout(Config, Parallel) -> - process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), - NegTimeOut = 2000, % ms - ct:log("Parallel: ~p",[Parallel]), - - {_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, - {parallel_login, Parallel}, - {negotiation_timeout, NegTimeOut}, - {failfun, fun ssh_test_lib:failfun/2}]), - - {ok,Socket} = gen_tcp:connect(Host, Port, []), - - Factor = 2, - ct:pal("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), - ct:sleep(round(Factor * NegTimeOut)), - - case inet:sockname(Socket) of - {ok,_} -> ct:fail("Socket not closed"); - {error,_} -> ok - end. - -%%-------------------------------------------------------------------- -ssh_connect_nonegtimeout_connected_parallel() -> - [{doc, "Test that ssh connection does not timeout if the connection is established (parallel)"}]. -ssh_connect_nonegtimeout_connected_parallel(Config) -> - ssh_connect_nonegtimeout_connected(Config, true). - -ssh_connect_nonegtimeout_connected_sequential() -> - [{doc, "Test that ssh connection does not timeout if the connection is established (non-parallel)"}]. -ssh_connect_nonegtimeout_connected_sequential(Config) -> - ssh_connect_nonegtimeout_connected(Config, false). - - -ssh_connect_nonegtimeout_connected(Config, Parallel) -> - process_flag(trap_exit, true), - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), - NegTimeOut = 20000, % ms - ct:log("Parallel: ~p",[Parallel]), - - {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir}, - {parallel_login, Parallel}, - {negotiation_timeout, NegTimeOut}, - {failfun, fun ssh_test_lib:failfun/2}]), - ct:pal("~p Listen ~p:~p",[_Pid,_Host,Port]), - ct:sleep(500), - - IO = ssh_test_lib:start_io_server(), - Shell = ssh_test_lib:start_shell(Port, IO, UserDir), - receive - Error = {'EXIT', _, _} -> - ct:pal("~p",[Error]), - ct:fail(no_ssh_connection); - ErlShellStart -> - ct:pal("---Erlang shell start: ~p~n", [ErlShellStart]), - one_shell_op(IO, NegTimeOut), - one_shell_op(IO, NegTimeOut), - - Factor = 2, - ct:pal("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]), - ct:sleep(round(Factor * NegTimeOut)), - - one_shell_op(IO, NegTimeOut) - end, - exit(Shell, kill). - - -one_shell_op(IO, TimeOut) -> - ct:pal("One shell op: Waiting for prompter"), - receive - ErlPrompt0 -> ct:log("Erlang prompt: ~p~n", [ErlPrompt0]) - after TimeOut -> ct:fail("Timeout waiting for promter") - end, - - IO ! {input, self(), "2*3*7.\r\n"}, - receive - Echo0 -> ct:log("Echo: ~p ~n", [Echo0]) - after TimeOut -> ct:fail("Timeout waiting for echo") - end, - - receive - ?NEWLINE -> ct:log("NEWLINE received", []) - after TimeOut -> - receive Any1 -> ct:log("Bad NEWLINE: ~p",[Any1]) - after 0 -> ct:fail("Timeout waiting for NEWLINE") - end - end, - - receive - Result0 -> ct:log("Result: ~p~n", [Result0]) - after TimeOut -> ct:fail("Timeout waiting for result") - end. +shell_unicode_string(Config) -> + new_do_shell(?config(io,Config), + [new_prompt, + {type,"io:format(\"こにちわ~ts~n\",[\"四二\"])."}, + {expect,"こにちわ四二"}, + {expect,"ok"} + ]). %%-------------------------------------------------------------------- - -openssh_zlib_basic_test() -> - [{doc, "Test basic connection with openssh_zlib"}]. +%%% Test basic connection with openssh_zlib openssh_zlib_basic_test(Config) -> - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), + case ssh_test_lib:ssh_supports(['[email protected]',none], compression) of + {false,L} -> + {skip, io_lib:format("~p compression is not supported",[L])}; - {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {preferred_algorithms,[{compression, ['[email protected]']}]}, - {failfun, fun ssh_test_lib:failfun/2}]), - ConnectionRef = - ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, - {user_dir, UserDir}, - {user_interaction, false}, - {preferred_algorithms,[{compression, ['[email protected]', - none]}]} - ]), - ok = ssh:close(ConnectionRef), - ssh:stop_daemon(Pid). + true -> + SystemDir = filename:join(?config(priv_dir, Config), system), + UserDir = ?config(priv_dir, Config), -%%-------------------------------------------------------------------- - -max_sessions_ssh_connect_parallel(Config) -> - max_sessions(Config, true, connect_fun(ssh__connect,Config)). -max_sessions_ssh_connect_sequential(Config) -> - max_sessions(Config, false, connect_fun(ssh__connect,Config)). - -max_sessions_sftp_start_channel_parallel(Config) -> - max_sessions(Config, true, connect_fun(ssh_sftp__start_channel, Config)). -max_sessions_sftp_start_channel_sequential(Config) -> - max_sessions(Config, false, connect_fun(ssh_sftp__start_channel, Config)). - - -%%%---- helpers: -connect_fun(ssh__connect, Config) -> - fun(Host,Port) -> - ssh_test_lib:connect(Host, Port, - [{silently_accept_hosts, true}, - {user_dir, ?config(priv_dir,Config)}, - {user_interaction, false}, - {user, "carni"}, - {password, "meat"} - ]) - %% ssh_test_lib returns R when ssh:connect returns {ok,R} - end; -connect_fun(ssh_sftp__start_channel, _Config) -> - fun(Host,Port) -> - {ok,_Pid,ConnRef} = - ssh_sftp:start_channel(Host, Port, - [{silently_accept_hosts, true}, - {user, "carni"}, - {password, "meat"} - ]), - ConnRef - end. - - -max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) -> - Connect = fun(Host,Port) -> - R = Connect0(Host,Port), - ct:pal("Connect(~p,~p) -> ~p",[Host,Port,R]), - R - end, - SystemDir = filename:join(?config(priv_dir, Config), system), - UserDir = ?config(priv_dir, Config), - MaxSessions = 5, - {Pid, Host, Port} = ssh_test_lib:daemon([ - {system_dir, SystemDir}, - {user_dir, UserDir}, - {user_passwords, [{"carni", "meat"}]}, - {parallel_login, ParallelLogin}, - {max_sessions, MaxSessions} - ]), - ct:pal("~p Listen ~p:~p for max ~p sessions",[Pid,Host,Port,MaxSessions]), - try [Connect(Host,Port) || _ <- lists:seq(1,MaxSessions)] - of - Connections -> - %% Step 1 ok: could set up max_sessions connections - ct:log("Connections up: ~p",[Connections]), - [_|_] = Connections, - - %% Now try one more than alowed: - ct:pal("Info Report might come here...",[]), - try Connect(Host,Port) - of - _ConnectionRef1 -> - ssh:stop_daemon(Pid), - {fail,"Too many connections accepted"} - catch - error:{badmatch,{error,"Connection closed"}} -> - %% Step 2 ok: could not set up max_sessions+1 connections - %% This is expected - %% Now stop one connection and try to open one more - ok = ssh:close(hd(Connections)), - receive after 250 -> ok end, % sleep so the supervisor has time to count down. Not nice... - try Connect(Host,Port) - of - _ConnectionRef1 -> - %% Step 3 ok: could set up one more connection after killing one - %% Thats good. - ssh:stop_daemon(Pid), - ok - catch - error:{badmatch,{error,"Connection closed"}} -> - %% Bad indeed. Could not set up one more connection even after killing - %% one existing. Very bad. - ssh:stop_daemon(Pid), - {fail,"Does not decrease # active sessions"} - end - end - catch - error:{badmatch,{error,"Connection closed"}} -> - ssh:stop_daemon(Pid), - {fail,"Too few connections accepted"} + {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, + {user_dir, UserDir}, + {preferred_algorithms,[{compression, ['[email protected]']}]}, + {failfun, fun ssh_test_lib:failfun/2}]), + ConnectionRef = + ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true}, + {user_dir, UserDir}, + {user_interaction, false}, + {preferred_algorithms,[{compression, ['[email protected]', + none]}]} + ]), + ok = ssh:close(ConnectionRef), + ssh:stop_daemon(Pid) end. %%-------------------------------------------------------------------- @@ -1785,13 +1084,14 @@ ssh_info_print(Config) -> %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- - %% Due to timing the error message may or may not be delivered to %% the "tcp-application" before the socket closed message is recived check_error("Invalid state") -> ok; check_error("Connection closed") -> ok; +check_error("Selection of key exchange algorithm failed") -> + ok; check_error(Error) -> ct:fail(Error). @@ -1807,28 +1107,38 @@ basic_test(Config) -> do_shell(IO, Shell) -> receive ErlPrompt0 -> - ct:pal("Erlang prompt: ~p~n", [ErlPrompt0]) + ct:log("Erlang prompt: ~p~n", [ErlPrompt0]) end, IO ! {input, self(), "1+1.\r\n"}, receive Echo0 -> - ct:pal("Echo: ~p ~n", [Echo0]) + ct:log("Echo: ~p ~n", [Echo0]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ?NEWLINE -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive Result0 = <<"2">> -> - ct:pal("Result: ~p~n", [Result0]) + ct:log("Result: ~p~n", [Result0]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ?NEWLINE -> ok + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, receive ErlPrompt1 -> - ct:pal("Erlang prompt: ~p~n", [ErlPrompt1]) + ct:log("Erlang prompt: ~p~n", [ErlPrompt1]) + after + 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE]) end, exit(Shell, kill). %%Does not seem to work in the testserver! @@ -1839,7 +1149,7 @@ do_shell(IO, Shell) -> %% end, %% receive %% Echo1 -> - %% ct:pal("Echo: ~p ~n", [Echo1]) + %% ct:log("Echo: ~p ~n", [Echo1]) %% end, %% receive %% ?NEWLINE -> @@ -1847,7 +1157,7 @@ do_shell(IO, Shell) -> %% end, %% receive %% Result1 -> - %% ct:pal("Result: ~p~n", [Result1]) + %% ct:log("Result: ~p~n", [Result1]) %% end, %% receive %% {'EXIT', Shell, killed} -> @@ -1855,60 +1165,90 @@ do_shell(IO, Shell) -> %% end. -std_daemon(Config, ExtraOpts) -> - SystemDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), - UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth - file:make_dir(UserDir), - {_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir}, - {user_dir, UserDir}, - {failfun, fun ssh_test_lib:failfun/2} | ExtraOpts]). - -expected_ssh_vsn(Str) -> - try - {ok,L} = application:get_all_key(ssh), - proplists:get_value(vsn,L,"")++"\r\n" - of - Str -> true; - "\r\n" -> true; - _ -> false - catch - _:_ -> true %% ssh not started so we dont't know +%%-------------------------------------------------------------------- +wait_for_erlang_first_line(Config) -> + receive + {'EXIT', _, _} -> + {fail,no_ssh_connection}; + <<"Eshell ",_/binary>> = _ErlShellStart -> + ct:log("Erlang shell start: ~p~n", [_ErlShellStart]), + Config; + Other -> + ct:log("Unexpected answer from ssh server: ~p",[Other]), + {fail,unexpected_answer} + after 10000 -> + ct:log("No answer from ssh-server"), + {fail,timeout} end. - -fake_daemon(_Config) -> - Parent = self(), - %% start the server - Server = spawn(fun() -> - {ok,Sl} = gen_tcp:listen(0,[{packet,line}]), - {ok,{Host,Port}} = inet:sockname(Sl), - ct:log("fake_daemon listening on ~p:~p~n",[Host,Port]), - Parent ! {sockname,self(),Host,Port}, - Rsa = gen_tcp:accept(Sl), - ct:log("Server gen_tcp:accept got ~p",[Rsa]), - {ok,S} = Rsa, - receive - {tcp, S, Id} -> Parent ! {id,self(),Id} - end - end), - %% Get listening host and port + + +new_do_shell(IO, List) -> new_do_shell(IO, 0, List). + +new_do_shell(IO, N, [new_prompt|More]) -> + new_do_shell(IO, N+1, More); + +new_do_shell(IO, N, Ops=[{Order,Arg}|More]) -> + Pfx = prompt_prefix(), + PfxSize = size(Pfx), receive - {sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort} - end. + _X = <<"\r\n">> -> + ct:log("Skip newline ~p",[_X]), + new_do_shell(IO, N, Ops); + + <<Pfx:PfxSize/binary,P1,"> ">> when (P1-$0)==N -> + new_do_shell_prompt(IO, N, Order, Arg, More); + + <<Pfx:PfxSize/binary,P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N -> + new_do_shell_prompt(IO, N, Order, Arg, More); + + <<Pfx:PfxSize/binary,P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N -> + new_do_shell_prompt(IO, N, Order, Arg, More); + + Err when element(1,Err)==error -> + ct:fail("new_do_shell error: ~p~n",[Err]); + + RecBin when Order==expect ; Order==expect_echo -> + ct:log("received ~p",[RecBin]), + RecStr = string:strip(unicode:characters_to_list(RecBin)), + ExpStr = string:strip(Arg), + case lists:prefix(ExpStr, RecStr) of + true when Order==expect -> + ct:log("Matched ~ts",[RecStr]), + new_do_shell(IO, N, More); + true when Order==expect_echo -> + ct:log("Matched echo ~ts",[RecStr]), + new_do_shell(IO, N, More); + false -> + ct:fail("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr]) + end + after 30000 -> + ct:log("Meassage queue of ~p:~n~p", + [self(), erlang:process_info(self(), messages)]), + case Order of + expect -> ct:fail("timeout, expected ~p",[string:strip(Arg)]); + type -> ct:fail("timeout, no prompt") + end + end; -%% get_kex_init - helper function to get key_exchange_init_msg -get_kex_init(Conn) -> - %% First, validate the key exchange is complete (StateName == connected) - {connected,S} = sys:get_state(Conn), - %% Next, walk through the elements of the #state record looking - %% for the #ssh_msg_kexinit record. This method is robust against - %% changes to either record. The KEXINIT message contains a cookie - %% unique to each invocation of the key exchange procedure (RFC4253) - SL = tuple_to_list(S), - case lists:keyfind(ssh_msg_kexinit, 1, SL) of - false -> - throw(not_found); - KexInit -> - KexInit +new_do_shell(_, _, []) -> + ok. + +prompt_prefix() -> + case node() of + nonode@nohost -> <<>>; + Node -> list_to_binary( + lists:concat(["(",Node,")"])) end. + + +new_do_shell_prompt(IO, N, type, Str, More) -> + ct:log("Matched prompt ~p to trigger sending of next line to server",[N]), + IO ! {input, self(), Str++"\r\n"}, + ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]), + new_do_shell(IO, N, [{expect_echo,Str}|More]); % expect echo of the sent line +new_do_shell_prompt(IO, N, Op, Str, More) -> + ct:log("Matched prompt ~p",[N]), + new_do_shell(IO, N, [{Op,Str}|More]). + +%%-------------------------------------------------------------------- |