%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(ssh_basic_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/file.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
-define(NEWLINE, <<"\r\n">>).
-define(REKEY_DATA_TMO, 65000).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
[{ct_hooks,[ts_install_cth]}].
all() ->
[app_test,
appup_test,
{group, dsa_key},
{group, rsa_key},
{group, dsa_pass_key},
{group, rsa_pass_key},
{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,
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,
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}
].
groups() ->
[{dsa_key, [], basic_tests()},
{rsa_key, [], basic_tests()},
{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]}
].
basic_tests() ->
[send, close, peername_sockname,
exec, exec_compressed, shell, cli, known_hosts,
idle_time, openssh_zlib_basic_test, misc_ssh_options, inet_option].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
catch crypto:stop(),
case catch crypto:start() of
ok ->
Config;
_Else ->
{skip, "Crypto could not be started!"}
end.
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),
ssh_test_lib:setup_dsa(DataDir, PrivDir),
Config;
init_per_group(rsa_key, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_rsa(DataDir, PrivDir),
Config;
init_per_group(rsa_pass_key, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_rsa_pass_pharse(DataDir, PrivDir, "Password"),
[{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
init_per_group(dsa_pass_key, Config) ->
DataDir = ?config(data_dir, 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(internal_error, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:setup_dsa(DataDir, PrivDir),
file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
Config;
init_per_group(dir_options, Config) ->
PrivDir = ?config(priv_dir, Config),
%% Make unreadable dir:
Dir_unreadable = filename:join(PrivDir, "unread"),
ok = file:make_dir(Dir_unreadable),
{ok,F1} = file:read_file_info(Dir_unreadable),
ok = file:write_file_info(Dir_unreadable,
F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}),
%% Make readable file:
File_readable = filename:join(PrivDir, "file"),
ok = file:write_file(File_readable, <<>>),
%% Check:
case {file:read_file_info(Dir_unreadable),
file:read_file_info(File_readable)} of
{{ok, #file_info{type=directory, access=Md}},
{ok, #file_info{type=regular, access=Mf}}} when Md=/=read, Md=/=read_write ->
%% Save:
[{unreadable_dir, Dir_unreadable},
{readable_file, File_readable}
| Config];
X ->
ct:log("#file_info : ~p",[X]),
{skip, "File or dir mode settings failed"}
end;
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),
Config;
end_per_group(rsa_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_rsa(PrivDir),
Config;
end_per_group(dsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
Config;
end_per_group(rsa_pass_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_rsa(PrivDir),
Config;
end_per_group(internal_error, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
Config;
end_per_group(_, Config) ->
Config.
%%--------------------------------------------------------------------
init_per_testcase(_TestCase, Config) ->
ssh:start(),
Config.
end_per_testcase(TestCase, Config) when TestCase == server_password_option;
TestCase == server_userpassword_option ->
UserDir = filename:join(?config(priv_dir, Config), nopubkey),
ssh_test_lib:del_dirs(UserDir),
end_per_testcase(Config);
end_per_testcase(_TestCase, Config) ->
end_per_testcase(Config).
end_per_testcase(_Config) ->
ssh:stop(),
ok.
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
app_test() ->
[{doc, "App lication consistency test."}].
app_test(Config) when is_list(Config) ->
?t:app_test(ssh),
ok.
%%--------------------------------------------------------------------
appup_test() ->
[{doc, "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."}].
misc_ssh_options(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
CMiscOpt0 = [{connect_timeout, 1000}, {user_dir, UserDir}],
CMiscOpt1 = [{connect_timeout, infinity}, {user_dir, UserDir}],
SMiscOpt0 = [{user_dir, UserDir}, {system_dir, SystemDir}],
SMiscOpt1 = [{user_dir, UserDir}, {system_dir, SystemDir}],
basic_test([{client_opts, CMiscOpt0}, {server_opts, SMiscOpt0}]),
basic_test([{client_opts, CMiscOpt1}, {server_opts, SMiscOpt1}]).
%%--------------------------------------------------------------------
inet_option() ->
[{doc, "Test configuring IPv4"}].
inet_option(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
ClientOpts = [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}],
ServerOpts = [{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}],
basic_test([{client_opts, [{inet, inet} | ClientOpts]},
{server_opts, [{inet, inet} | ServerOpts]}]).
%%--------------------------------------------------------------------
inet6_option() ->
[{doc, "Test configuring IPv6"}].
inet6_option(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
ClientOpts = [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}],
ServerOpts = [{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}],
basic_test([{client_opts, [{inet, inet6} | ClientOpts]},
{server_opts, [{inet, inet6} | ServerOpts]}]).
%%--------------------------------------------------------------------
exec() ->
[{doc, "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),
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}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"1+1.", infinity),
Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"2\n">>}},
case ssh_test_lib:receive_exec_result(Data0) of
expected ->
ok;
Other0 ->
ct:fail(Other0)
end,
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
%% Test that it is possible to start a new channel and
%% run an other exec on the same connection.
{ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId1,
"2+2.", infinity),
Data1 = {ssh_cm, ConnectionRef, {data, ChannelId1, 0, <<"4\n">>}},
case ssh_test_lib:receive_exec_result(Data1) of
expected ->
ok;
Other1 ->
ct:fail(Other1)
end,
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
exec_compressed() ->
[{doc, "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),
{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).
%%--------------------------------------------------------------------
idle_time() ->
[{doc, "Idle timeout test"}].
idle_time(Config) ->
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}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false},
{idle_time, 2000}]),
{ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
ssh_connection:close(ConnectionRef, Id),
receive
after 10000 ->
{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),
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_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}]),
receive
after ?REKEY_DATA_TMO ->
%%By this time rekeying would have been done
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid)
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(),
{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, 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),
Kex2 = get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
ssh_relay:stop(RelayPid),
ssh_sftp:stop_channel(SftpPid),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
shell() ->
[{doc, "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),
IO = ssh_test_lib:start_io_server(),
Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
receive
{'EXIT', _, _} ->
ct:fail(no_ssh_connection);
ErlShellStart ->
ct:pal("Erlang shell start: ~p~n", [ErlShellStart]),
do_shell(IO, Shell)
end.
%%--------------------------------------------------------------------
cli() ->
[{doc, ""}].
cli(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},
{password, "morot"},
{ssh_cli, {ssh_test_cli, [cli]}},
{subsystems, []},
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
{password, "morot"},
{user_interaction, false},
{user_dir, UserDir}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
ssh_connection:shell(ConnectionRef, ChannelId),
ok = ssh_connection:send(ConnectionRef, ChannelId, <<"q">>),
receive
{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">>)
end,
receive
{ssh_cm, ConnectionRef,{closed, ChannelId}} ->
ok
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"}].
daemon_already_started(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
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}]),
{error, eaddrinuse} = ssh_test_lib:daemon(Port, [{system_dir, SystemDir},
{user_dir, UserDir},
{failfun,
fun ssh_test_lib:failfun/2}]),
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.
%%--------------------------------------------------------------------
known_hosts() ->
[{doc, "check that known_hosts is updated correctly"}].
known_hosts(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{user_dir, PrivDir},{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
KnownHosts = filename:join(PrivDir, "known_hosts"),
file:delete(KnownHosts),
{error, enoent} = file:read_file(KnownHosts),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
{user_interaction, false},
silently_accept_hosts]),
{ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh:close(ConnectionRef),
{ok, Binary} = file:read_file(KnownHosts),
Lines = string:tokens(binary_to_list(Binary), "\n"),
[Line] = Lines,
[HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
[Host, _Ip] = string:tokens(HostAndIp, ","),
"ssh-" ++ _ = Alg,
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
pass_phrase() ->
[{doc, "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),
UserDir = ?config(priv_dir, Config),
PhraseArg = ?config(pass_phrase, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
PhraseArg,
{user_dir, UserDir},
{user_interaction, false}]),
{ok, _ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
internal_error() ->
[{doc,"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}]),
{error, Error} =
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"}].
send(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}]),
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),
ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
peername_sockname() ->
[{doc, "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),
UserDir = ?config(priv_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{subsystems, [{"peername_sockname",
{ssh_peername_sockname_server, []}}
]}
]),
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:subsystem(ConnectionRef, ChannelId, "peername_sockname", infinity),
[{peer, {_Name, {HostPeerClient,PortPeerClient} = ClientPeer}}] =
ssh:connection_info(ConnectionRef, [peer]),
[{sockname, {HostSockClient,PortSockClient} = ClientSock}] =
ssh:connection_info(ConnectionRef, [sockname]),
ct:pal("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]),
host_equal(HostPeerSrv, HostSockClient),
PortPeerSrv = PortSockClient,
host_equal(HostSockSrv, HostPeerClient),
PortSockSrv = PortPeerClient,
host_equal(HostSockSrv, Host),
PortSockSrv = Port
after 10000 ->
throw(timeout)
end.
host_equal(H1, H2) ->
not ordsets:is_disjoint(ips(H1), ips(H2)).
ips(IP) when is_tuple(IP) -> ordsets:from_list([IP]);
ips(Name) when is_list(Name) ->
{ok,#hostent{h_addr_list=IPs4}} = inet:gethostbyname(Name,inet),
{ok,#hostent{h_addr_list=IPs6}} = inet:gethostbyname(Name,inet6),
ordsets:from_list(IPs4++IPs6).
%%--------------------------------------------------------------------
close() ->
[{doc, "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),
UserDir = ?config(priv_dir, Config),
{Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
Client =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
{ok, ChannelId} = ssh_connection:session_channel(Client, infinity),
ssh:stop_daemon(Server),
receive
{ssh_cm, Client,{closed, ChannelId}} ->
ok
after 5000 ->
ct:fail(timeout)
end.
%%--------------------------------------------------------------------
double_close() ->
[{doc, "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),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
file:make_dir(UserDir),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{user_passwords, [{"vego", "morot"}]},
{failfun, fun ssh_test_lib:failfun/2}]),
{ok, CM} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user, "vego"},
{password, "morot"},
{user_interaction, false}]),
exit(CM, {shutdown, normal}),
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.
%% Help for the test above
connect(_Host, _Port, _Opts, Timeout) ->
{error, {faked_transport,connect,Timeout}}.
%%--------------------------------------------------------------------
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;
{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.
%% Help function, elapsed milliseconds since T0
ms_passed(T0) ->
%% OTP 18
erlang:convert_time_unit(erlang:monotonic_time() - T0,
native,
micro_seconds) / 1000.
%%--------------------------------------------------------------------
packet_size_zero(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"}]}]),
Conn =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false},
{user, "vego"},
{password, "morot"}]),
{ok,Chan} = ssh_connection:session_channel(Conn, 1000, _MaxPacketSize=0, 60000),
ok = ssh_connection:shell(Conn, Chan),
ssh:close(Conn),
ssh:stop_daemon(Server),
receive
{ssh_cm,Conn,{data,Chan,_Type,_Msg1}} = M ->
ct:pal("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).
%%--------------------------------------------------------------------
%% 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.
%%--------------------------------------------------------------------
openssh_zlib_basic_test() ->
[{doc, "Test basic connection with openssh_zlib"}].
openssh_zlib_basic_test(Config) ->
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, ['[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).
%%--------------------------------------------------------------------
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"}
end.
%%--------------------------------------------------------------------
%% 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(Error) ->
ct:fail(Error).
basic_test(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
{Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts),
{ok, CM} = ssh:connect(Host, Port, ClientOpts),
ok = ssh:close(CM),
ssh:stop_daemon(Pid).
do_shell(IO, Shell) ->
receive
ErlPrompt0 ->
ct:pal("Erlang prompt: ~p~n", [ErlPrompt0])
end,
IO ! {input, self(), "1+1.\r\n"},
receive
Echo0 ->
ct:pal("Echo: ~p ~n", [Echo0])
end,
receive
?NEWLINE ->
ok
end,
receive
Result0 = <<"2">> ->
ct:pal("Result: ~p~n", [Result0])
end,
receive
?NEWLINE ->
ok
end,
receive
ErlPrompt1 ->
ct:pal("Erlang prompt: ~p~n", [ErlPrompt1])
end,
exit(Shell, kill).
%%Does not seem to work in the testserver!
%% IO ! {input, self(), "q().\r\n"},
%% receive
%% ?NEWLINE ->
%% ok
%% end,
%% receive
%% Echo1 ->
%% ct:pal("Echo: ~p ~n", [Echo1])
%% end,
%% receive
%% ?NEWLINE ->
%% ok
%% end,
%% receive
%% Result1 ->
%% ct:pal("Result: ~p~n", [Result1])
%% end,
%% receive
%% {'EXIT', Shell, killed} ->
%% ok
%% 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
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
receive
{sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort}
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
end.