aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Nilsson <[email protected]>2015-08-30 11:21:35 +0200
committerHans Nilsson <[email protected]>2015-08-30 11:21:35 +0200
commitbd698a302da6f4ca0940add26852c32dc27d3d07 (patch)
tree420a1429d471c9bd246e4f60afb9e46a47207384
parent8fac1e6b35b62321d98655866e8f76400c6d0cbf (diff)
parentee71e0e20819e2ab9e251f4bb46e8b0b19f2a1b3 (diff)
downloadotp-bd698a302da6f4ca0940add26852c32dc27d3d07.tar.gz
otp-bd698a302da6f4ca0940add26852c32dc27d3d07.tar.bz2
otp-bd698a302da6f4ca0940add26852c32dc27d3d07.zip
Merge branch 'maint'
* maint: ssh: Reorganize and extend the test suites
-rw-r--r--lib/ssh/src/ssh_transport.erl9
-rw-r--r--lib/ssh/src/ssh_transport.hrl11
-rw-r--r--lib/ssh/test/Makefile15
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl297
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa13
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl1305
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl1024
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_dsa13
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl11
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE.erl223
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa13
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl99
-rw-r--r--lib/ssh/test/ssh_test_lib.erl181
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl309
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl1
30 files changed, 2209 insertions, 1495 deletions
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 235d8918f3..1914b223bc 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -801,14 +801,15 @@ alg_final(SSH0) ->
{ok,SSH6} = decompress_final(SSH5),
SSH6.
-select_all(CL, SL) when length(CL) + length(SL) < 50 ->
+select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS ->
A = CL -- SL, %% algortihms only used by client
%% algorithms used by client and server (client pref)
lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A));
-select_all(_CL, _SL) ->
+select_all(CL, SL) ->
+ Err = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]),
throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Too many algorithms",
- language = "en"}).
+ description = Err,
+ language = ""}).
select([], []) ->
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index e6449e93c5..0bc6b7953b 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -30,6 +30,13 @@
-define(DEFAULT_CLIENT_VERSION, {2, 0}).
-define(DEFAULT_SERVER_VERSION, {2, 0}).
+-define(MAX_NUM_ALGORITHMS, 100).
+
+-define(DEFAULT_DH_GROUP_MIN, 512).
+-define(DEFAULT_DH_GROUP_NBITS, 1024).
+-define(DEFAULT_DH_GROUP_MAX, 4096).
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% BASIC transport messages
@@ -132,10 +139,6 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% diffie-hellman-group-exchange-sha1 | diffie-hellman-group-exchange-sha256
--define(DEFAULT_DH_GROUP_MIN, 512).
--define(DEFAULT_DH_GROUP_NBITS, 1024).
--define(DEFAULT_DH_GROUP_MAX, 4096).
-
-define(SSH_MSG_KEX_DH_GEX_REQUEST_OLD, 30).
-define(SSH_MSG_KEX_DH_GEX_REQUEST, 34).
-define(SSH_MSG_KEX_DH_GEX_GROUP, 31).
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 47c189c162..96c74c6c8a 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -32,17 +32,22 @@ VSN=$(GS_VSN)
# ----------------------------------------------------
MODULES= \
- ssh_test_lib \
- ssh_trpt_test_lib \
- ssh_sup_SUITE \
+ ssh_algorithms_SUITE \
+ ssh_options_SUITE \
+ ssh_renegotiate_SUITE \
+ \
ssh_basic_SUITE \
+ \
+ ssh_connection_SUITE \
ssh_protocol_SUITE \
- ssh_to_openssh_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
ssh_sftpd_erlclient_SUITE \
+ ssh_sup_SUITE \
+ ssh_to_openssh_SUITE \
ssh_upgrade_SUITE \
- ssh_connection_SUITE \
+ ssh_test_lib \
+ ssh_trpt_test_lib \
ssh_echo_server \
ssh_peername_sockname_server \
ssh_test_cli \
diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl
new file mode 100644
index 0000000000..e67fa2469f
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -0,0 +1,297 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_algorithms_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(TIMEOUT, 50000).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ %% [{group,kex},{group,cipher}... etc
+ [{group,C} || C <- tags()].
+
+
+groups() ->
+ ErlAlgos = extract_algos(ssh:default_algorithms()),
+ SshcAlgos = extract_algos(ssh_test_lib:default_algorithms(sshc)),
+ SshdAlgos = extract_algos(ssh_test_lib:default_algorithms(sshd)),
+
+ DoubleAlgos =
+ [{Tag, double(Algs)} || {Tag,Algs} <- ErlAlgos,
+ length(Algs) > 1,
+ lists:member(Tag, two_way_tags())],
+ TagGroupSet =
+ [{Tag, [], group_members_for_tag(Tag,Algs,DoubleAlgos)}
+ || {Tag,Algs} <- ErlAlgos,
+ lists:member(Tag,tags())
+ ],
+
+ AlgoTcSet =
+ [{Alg, [], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos)}
+ || {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
+ Alg <- Algs],
+
+ TagGroupSet ++ AlgoTcSet.
+
+tags() -> [kex,cipher,mac,compression].
+two_way_tags() -> [cipher,mac,compression].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ ct:log("~n~n"
+ "OS ssh:~n=======~n~p~n~n~n"
+ "Erl ssh:~n========~n~p~n~n~n"
+ "Installed ssh client:~n=====================~n~p~n~n~n"
+ "Installed ssh server:~n=====================~n~p~n~n~n",
+ [os:cmd("ssh -V"),
+ ssh:default_algorithms(),
+ ssh_test_lib:default_algorithms(sshc),
+ ssh_test_lib:default_algorithms(sshd)]),
+ ct:log("all() ->~n ~p.~n~ngroups()->~n ~p.~n",[all(),groups()]),
+ catch crypto:stop(),
+ case catch crypto:start() of
+ ok ->
+ ssh:start(),
+ [{std_simple_sftp_size,25000} % Sftp transferred data size
+ | setup_pubkey(Config)];
+ _Else ->
+ {skip, "Crypto could not be started!"}
+ end.
+end_per_suite(_Config) ->
+ ssh:stop(),
+ crypto:stop().
+
+
+init_per_group(Group, Config) ->
+ case lists:member(Group, tags()) of
+ true ->
+ %% A tag group
+ Tag = Group,
+ ct:comment("==== ~p ====",[Tag]),
+ Config;
+ false ->
+ %% An algorithm group
+ [[{name,Tag}]|_] = ?config(tc_group_path, Config),
+ Alg = Group,
+ PA =
+ case split(Alg) of
+ [_] ->
+ [Alg];
+ [A1,A2] ->
+ [{client2server,[A1]},
+ {server2client,[A2]}]
+ end,
+ ct:log("Init tests for tag=~p alg=~p",[Tag,PA]),
+ PrefAlgs = {preferred_algorithms,[{Tag,PA}]},
+ start_std_daemon([PrefAlgs],
+ [{pref_algs,PrefAlgs} | Config])
+ end.
+
+end_per_group(_Alg, Config) ->
+ case ?config(srvr_pid,Config) of
+ Pid when is_pid(Pid) ->
+ ssh:stop_daemon(Pid),
+ ct:log("stopped ~p",[?config(srvr_addr,Config)]);
+ _ ->
+ ok
+ end.
+
+
+
+init_per_testcase(sshc_simple_exec, Config) ->
+ start_pubkey_daemon([?config(pref_algs,Config)], Config);
+
+init_per_testcase(_TC, Config) ->
+ Config.
+
+
+end_per_testcase(sshc_simple_exec, Config) ->
+ case ?config(srvr_pid,Config) of
+ Pid when is_pid(Pid) ->
+ ssh:stop_daemon(Pid),
+ ct:log("stopped ~p",[?config(srvr_addr,Config)]);
+ _ ->
+ ok
+ end;
+end_per_testcase(_TC, Config) ->
+ Config.
+
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+%% A simple sftp transfer
+simple_sftp(Config) ->
+ {Host,Port} = ?config(srvr_addr, Config),
+ ssh_test_lib:std_simple_sftp(Host, Port, Config).
+
+%%--------------------------------------------------------------------
+%% A simple exec call
+simple_exec(Config) ->
+ {Host,Port} = ?config(srvr_addr, Config),
+ ssh_test_lib:std_simple_exec(Host, Port, Config).
+
+%%--------------------------------------------------------------------
+%% Use the ssh client of the OS to connect
+sshc_simple_exec(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
+ {Host,Port} = ?config(srvr_addr, Config),
+ Cmd = lists:concat(["ssh -p ",Port,
+ " -C -o UserKnownHostsFile=",KnownHosts,
+ " ",Host," 1+1."]),
+ ct:log("~p",[Cmd]),
+ SshPort = open_port({spawn, Cmd}, [binary]),
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ ct:fail("Did not receive answer")
+ end.
+
+%%--------------------------------------------------------------------
+%% Connect to the ssh server of the OS
+sshd_simple_exec(_Config) ->
+ ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "echo testing", infinity),
+ Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}},
+ 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:log("0: Collected data ~p", [ExitStatus0]),
+ ssh_test_lib:receive_exec_result(Data0,
+ ConnectionRef, ChannelId0);
+ Other0 ->
+ ct:fail(Other0)
+ end,
+
+ {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId1,
+ "echo testing1", infinity),
+ Data1 = {ssh_cm, ConnectionRef, {data, ChannelId1, 0, <<"testing1\n">>}},
+ case ssh_test_lib:receive_exec_result(Data1) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1);
+ {unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId1, 0}}
+ = ExitStatus1} ->
+ ct:log("0: Collected data ~p", [ExitStatus1]),
+ ssh_test_lib:receive_exec_result(Data1,
+ ConnectionRef, ChannelId1);
+ Other1 ->
+ ct:fail(Other1)
+ end.
+
+%%%================================================================
+%%%
+%%% Lib functions
+%%%
+
+%%%----------------------------------------------------------------
+%%%
+%%% For construction of the result of all/0 and groups/0
+%%%
+group_members_for_tag(Tag, Algos, DoubleAlgos) ->
+ [{group,Alg} || Alg <- Algos++proplists:get_value(Tag,DoubleAlgos,[])].
+
+double(Algs) -> [concat(A1,A2) || A1 <- Algs,
+ A2 <- Algs,
+ A1 =/= A2].
+
+concat(A1, A2) -> list_to_atom(lists:concat([A1," + ",A2])).
+
+split(Alg) -> ssh_test_lib:to_atoms(string:tokens(atom_to_list(Alg), " + ")).
+
+specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos) ->
+ [simple_exec, simple_sftp] ++
+ case supports(Tag, Alg, SshcAlgos) of
+ true ->
+ case ssh_test_lib:ssh_type() of
+ openSSH ->
+ [sshc_simple_exec];
+ _ ->
+ []
+ end;
+ false ->
+ []
+ end ++
+ case supports(Tag, Alg, SshdAlgos) of
+ true ->
+ [sshd_simple_exec];
+ _ ->
+ []
+ end.
+
+supports(Tag, Alg, Algos) ->
+ lists:all(fun(A) ->
+ lists:member(A, proplists:get_value(Tag, Algos,[]))
+ end,
+ split(Alg)).
+
+
+extract_algos(Spec) ->
+ [{Tag,get_atoms(List)} || {Tag,List} <- Spec].
+
+get_atoms(L) ->
+ lists:usort(
+ [ A || X <- L,
+ A <- case X of
+ {_,L1} when is_list(L1) -> L1;
+ Y when is_atom(Y) -> [Y]
+ end]).
+
+%%%----------------------------------------------------------------
+%%%
+%%% Test case related
+%%%
+start_std_daemon(Opts, Config) ->
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config, Opts),
+ ct:log("started ~p:~p ~p",[Host,Port,Opts]),
+ [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
+
+start_pubkey_daemon(Opts, Config) ->
+ {Pid, Host, Port} = ssh_test_lib:std_daemon1(Config, Opts),
+ ct:log("started1 ~p:~p ~p",[Host,Port,Opts]),
+ [{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
+
+
+setup_pubkey(Config) ->
+ DataDir = ?config(data_dir, Config),
+ UserDir = ?config(priv_dir, Config),
+ ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
+ Config.
+
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
new file mode 100644
index 0000000000..d306f8b26e
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_dsa
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
+APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
+/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
+kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
+JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
+OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
+uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
+Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
+ZU8w8Q+H7z0j+a+70x2iAw==
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
+DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
+zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
+AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
+TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
+CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
+SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
+z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
+WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
+sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
+xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
+dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
+ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 27b611780d..51431da48e 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -27,11 +27,44 @@
-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,
+ double_close/1,
+ exec/1,
+ exec_compressed/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
+ ]).
+
+%%% 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 -----------------------------------
%%--------------------------------------------------------------------
@@ -42,38 +75,14 @@ suite() ->
all() ->
[app_test,
appup_test,
- {group, key_exchange},
{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,
- 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
].
@@ -82,27 +91,7 @@ groups() ->
{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
- ]},
- {key_exchange, [], ['diffie-hellman-group-exchange-sha1',
- 'diffie-hellman-group-exchange-sha256',
- 'diffie-hellman-group1-sha1',
- 'diffie-hellman-group14-sha1',
- 'ecdh-sha2-nistp256',
- 'ecdh-sha2-nistp384',
- 'ecdh-sha2-nistp521'
- ]},
- {dir_options, [], [user_dir_option,
- system_dir_option]}
+ {internal_error, [], [internal_error]}
].
@@ -111,7 +100,8 @@ basic_tests() ->
exec, exec_compressed,
shell, shell_no_unicode, shell_unicode_string,
cli, known_hosts,
- idle_time, openssh_zlib_basic_test, misc_ssh_options, inet_option].
+ idle_time, openssh_zlib_basic_test,
+ misc_ssh_options, inet_option, inet6_option].
%%--------------------------------------------------------------------
@@ -155,11 +145,6 @@ init_per_group(internal_error, Config) ->
ssh_test_lib:setup_dsa(DataDir, PrivDir),
file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
Config;
-init_per_group(key_exchange, Config) ->
- DataDir = ?config(data_dir, Config),
- PrivDir = ?config(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- Config;
init_per_group(dir_options, Config) ->
PrivDir = ?config(priv_dir, Config),
%% Make unreadable dir:
@@ -207,8 +192,6 @@ init_per_group(_, Config) ->
end_per_group(hardening_tests, Config) ->
end_per_group(dsa_key, Config);
-end_per_group(key_exchange, Config) ->
- end_per_group(rsa_key, Config);
end_per_group(dsa_key, Config) ->
PrivDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(PrivDir),
@@ -279,21 +262,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),
@@ -307,8 +287,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),
@@ -324,8 +303,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),
@@ -341,8 +319,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),
@@ -383,8 +360,7 @@ 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),
@@ -412,8 +388,7 @@ exec_compressed(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
-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),
@@ -433,183 +408,9 @@ 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),
- 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},
- {max_random_length_padding, 0},
- {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},
- {max_random_length_padding, 0},
- {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"}].
+%%% 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),
@@ -630,8 +431,6 @@ shell(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-cli() ->
- [{doc, ""}].
cli(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -665,9 +464,8 @@ cli(Config) when is_list(Config) ->
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),
@@ -682,489 +480,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:log("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.
-
-%%--------------------------------------------------------------------
-'diffie-hellman-group-exchange-sha1'(Config) ->
- kextest('diffie-hellman-group-exchange-sha1',Config).
-
-'diffie-hellman-group-exchange-sha256'(Config) ->
- kextest('diffie-hellman-group-exchange-sha256',Config).
-
-'diffie-hellman-group1-sha1'(Config) ->
- kextest('diffie-hellman-group1-sha1',Config).
-
-'diffie-hellman-group14-sha1'(Config) ->
- kextest('diffie-hellman-group14-sha1',Config).
-
-'ecdh-sha2-nistp256'(Config) ->
- kextest('ecdh-sha2-nistp256',Config).
-
-'ecdh-sha2-nistp384'(Config) ->
- kextest('ecdh-sha2-nistp384',Config).
-
-'ecdh-sha2-nistp521'(Config) ->
- kextest('ecdh-sha2-nistp521',Config).
-
-
-kextest(Kex, Config) ->
- case lists:member(Kex, ssh_transport:supported_algorithms(kex)) of
- 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},
- {user_passwords, [{"foo", "bar"}]},
- {preferred_algorithms,
- [{kex, [Kex]}]},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
- ConnectionRef =
- ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
- {user, "foo"},
- {password, "bar"},
- {user_dir, UserDir},
- {preferred_algorithms,
- [{kex, [Kex]}]},
- {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);
- false ->
- {skip, lists:concat([Kex, " is not supported"])}
- 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),
@@ -1190,8 +506,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),
@@ -1209,28 +524,26 @@ pass_phrase(Config) when is_list(Config) ->
{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"}].
+%%--------------------------------------------------------------------
+%%% 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),
@@ -1250,8 +563,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),
@@ -1301,8 +613,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),
@@ -1326,8 +637,7 @@ close(Config) when is_list(Config) ->
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),
@@ -1348,91 +658,6 @@ 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.
-
-%% 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),
@@ -1464,249 +689,6 @@ packet_size_zero(Config) ->
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:log("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:log("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:log("~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:log("~p",[Error]),
- ct:fail(no_ssh_connection);
- ErlShellStart ->
- ct:log("---Erlang shell start: ~p~n", [ErlShellStart]),
- one_shell_op(IO, NegTimeOut),
- one_shell_op(IO, NegTimeOut),
-
- Factor = 2,
- ct:log("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:log("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_no_unicode(Config) ->
new_do_shell(?config(io,Config),
[new_prompt,
@@ -1724,8 +706,7 @@ shell_unicode_string(Config) ->
]).
%%--------------------------------------------------------------------
-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),
@@ -1745,102 +726,6 @@ openssh_zlib_basic_test(Config) ->
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:log("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:log("~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:log("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.
-
-%%--------------------------------------------------------------------
ssh_info_print(Config) ->
%% Just check that ssh_print:info() crashes
PrivDir = ?config(priv_dir, Config),
@@ -1911,7 +796,6 @@ 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") ->
@@ -2070,62 +954,3 @@ new_do_shell_prompt(IO, N, Op, Str, More) ->
new_do_shell(IO, N, [{Op,Str}|More]).
%%--------------------------------------------------------------------
-
-
-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.
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
new file mode 100644
index 0000000000..d64c78da35
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -0,0 +1,1024 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_options_SUITE).
+
+%%% This test suite tests different options for the ssh functions
+
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/file.hrl").
+
+
+%%% Test cases
+-export([connectfun_disconnectfun_client/1,
+ disconnectfun_option_client/1,
+ disconnectfun_option_server/1,
+ id_string_no_opt_client/1,
+ id_string_no_opt_server/1,
+ id_string_own_string_client/1,
+ id_string_own_string_server/1,
+ id_string_random_client/1,
+ id_string_random_server/1,
+ max_sessions_sftp_start_channel_parallel/1,
+ max_sessions_sftp_start_channel_sequential/1,
+ max_sessions_ssh_connect_parallel/1,
+ max_sessions_ssh_connect_sequential/1,
+ server_password_option/1,
+ server_userpassword_option/1,
+ ssh_connect_arg4_timeout/1,
+ ssh_connect_negtimeout_parallel/1,
+ ssh_connect_negtimeout_sequential/1,
+ ssh_connect_nonegtimeout_connected_parallel/1,
+ ssh_connect_nonegtimeout_connected_sequential/1,
+ ssh_connect_timeout/1, connect/4,
+ ssh_daemon_minimal_remote_max_packet_size_option/1,
+ ssh_msg_debug_fun_option_client/1,
+ ssh_msg_debug_fun_option_server/1,
+ system_dir_option/1,
+ unexpectedfun_option_client/1,
+ unexpectedfun_option_server/1,
+ user_dir_option/1,
+ connectfun_disconnectfun_server/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">>).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [connectfun_disconnectfun_server,
+ connectfun_disconnectfun_client,
+ server_password_option,
+ server_userpassword_option,
+ {group, dir_options},
+ ssh_connect_timeout,
+ ssh_connect_arg4_timeout,
+ 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,
+ 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() ->
+ [{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]}
+ ].
+
+
+%%--------------------------------------------------------------------
+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) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ 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, Id=#file_info{type=directory, access=Md}},
+ {ok, If=#file_info{type=regular, access=Mf}}} ->
+ AccessOK =
+ case {Md, Mf} of
+ {read, _} -> false;
+ {read_write, _} -> false;
+ {_, read} -> true;
+ {_, read_write} -> true;
+ _ -> false
+ end,
+
+ case AccessOK of
+ true ->
+ %% Save:
+ [{unreadable_dir, Dir_unreadable},
+ {readable_file, File_readable}
+ | Config];
+ false ->
+ ct:log("File#file_info : ~p~n"
+ "Dir#file_info : ~p",[If,Id]),
+ {skip, "File or dir mode settings failed"}
+ end;
+
+ NotDirFile ->
+ ct:log("{Dir,File} -> ~p",[NotDirFile]),
+ {skip, "File/Dir creation failed"}
+ end;
+init_per_group(_, Config) ->
+ 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 --------------------------------------------------------
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+
+%%% 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:log("Test of wrong password: Error msg: ~p ~n", [Reason]),
+
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+%%% 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.
+
+%%--------------------------------------------------------------------
+%%% 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.
+
+%%--------------------------------------------------------------------
+%%% 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.
+
+%%--------------------------------------------------------------------
+%%% 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.
+
+%% Plugin function for the test above
+connect(_Host, _Port, _Opts, Timeout) ->
+ {error, {faked_transport,connect,Timeout}}.
+
+%%--------------------------------------------------------------------
+%%% 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 = 2.5*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.
+
+%%--------------------------------------------------------------------
+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
+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} = ssh_test_lib: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} = ssh_test_lib: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} = ssh_test_lib: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:log("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.
+
+%%--------------------------------------------------------------------
+%%% 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).
+
+%%% 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:log("~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:log("~p",[Error]),
+ ct:fail(no_ssh_connection);
+ ErlShellStart ->
+ ct:log("---Erlang shell start: ~p~n", [ErlShellStart]),
+ one_shell_op(IO, NegTimeOut),
+ one_shell_op(IO, NegTimeOut),
+
+ Factor = 2,
+ ct:log("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:log("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.
+
+%%--------------------------------------------------------------------
+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:log("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:log("~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:log("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 ------------------------------------------------
+%%--------------------------------------------------------------------
+
+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.
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_dsa b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
new file mode 100644
index 0000000000..d306f8b26e
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_dsa
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
+APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
+/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
+kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
+JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
+OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
+uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
+Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
+ZU8w8Q+H7z0j+a+70x2iAw==
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/id_rsa b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
+DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
+zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
+AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
+TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
+CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
+SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
+z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
+WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
+sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
+xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
+dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
+ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_options_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 132be3beb2..cf2df5028a 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -73,6 +73,9 @@ end_per_suite(Config) ->
+init_per_testcase(no_common_alg_server_disconnects, Config) ->
+ start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']}]}]);
+
init_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
TC == gex_client_init_default_exact ;
TC == gex_client_init_option_groups ;
@@ -93,6 +96,8 @@ init_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
init_per_testcase(_TestCase, Config) ->
check_std_daemon_works(Config, ?LINE).
+end_per_testcase(no_common_alg_server_disconnects, Config) ->
+ stop_std_daemon(Config);
end_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
TC == gex_client_init_default_exact ;
TC == gex_client_init_option_groups ;
@@ -101,7 +106,6 @@ end_per_testcase(TC, Config) when TC == gex_client_init_default_noexact ;
end_per_testcase(_TestCase, Config) ->
check_std_daemon_works(Config, ?LINE).
-
%%%--------------------------------------------------------------------
%%% Test Cases --------------------------------------------------------
%%%--------------------------------------------------------------------
@@ -412,8 +416,9 @@ start_std_daemon(Config, ExtraOpts) ->
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
file:make_dir(UserDir),
UserPasswords = [{"user1","pwd1"}],
- Options = [{system_dir, system_dir(Config)},
- {user_dir, user_dir(Config)},
+ Options = [%%{preferred_algorithms,[{public_key,['ssh-rsa']}]}, %% For some test cases
+ {system_dir, system_dir(Config)},
+ {user_dir, UserDir},
{user_passwords, UserPasswords},
{failfun, fun ssh_test_lib:failfun/2}
| ExtraOpts],
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl
new file mode 100644
index 0000000000..9daa6efc02
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl
@@ -0,0 +1,223 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(ssh_renegotiate_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(REKEY_DATA_TMO, 65000).
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() -> [{ct_hooks,[ts_install_cth]}].
+
+all() -> [rekey, rekey_limit, renegotiate1, renegotiate2].
+
+groups() -> [].
+
+%%--------------------------------------------------------------------
+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_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ssh:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+%%% Idle timeout test
+
+rekey(Config) ->
+ {Pid, Host, Port} =
+ ssh_test_lib:std_daemon(Config,
+ [{rekey_limit, 0}]),
+ ConnectionRef =
+ ssh_test_lib:std_connect(Config, Host, Port,
+ [{rekey_limit, 0}]),
+ Kex1 = get_kex_init(ConnectionRef),
+ receive
+ after ?REKEY_DATA_TMO ->
+ %%By this time rekeying would have been done
+ Kex2 = get_kex_init(ConnectionRef),
+ false = (Kex2 == Kex1),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid)
+ end.
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying by data volume
+
+rekey_limit(Config) ->
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey.data"),
+
+ {Pid, Host, Port} = ssh_test_lib:std_daemon(Config,[]),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, [{rekey_limit, 4500}]),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ Kex1 = get_kex_init(ConnectionRef),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex1 = get_kex_init(ConnectionRef),
+
+ Data = lists:duplicate(159000,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).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with simulataneous send request
+
+renegotiate1(Config) ->
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate1.data"),
+
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[]),
+
+ RPort = ssh_test_lib:inet_port(),
+ {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
+
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, []),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ 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).
+
+%%--------------------------------------------------------------------
+
+%%% Test rekeying with inflight messages from peer
+
+renegotiate2(Config) ->
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate2.data"),
+
+ {Pid, Host, DPort} = ssh_test_lib:std_daemon(Config,[]),
+
+ RPort = ssh_test_lib:inet_port(),
+ {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
+
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, []),
+ {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
+
+ 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).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+%% 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.
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
new file mode 100644
index 0000000000..d306f8b26e
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_dsa
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
+APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
+/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
+kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
+JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
+OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
+uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
+Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
+ZU8w8Q+H7z0j+a+70x2iAw==
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
+DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
+zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
+AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
+TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
+CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
+SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
+z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
+WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
+sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
+xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
+dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
+ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_renegotiate_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index 8d0b887d83..32fdec9842 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -27,7 +27,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
-% Default timetrap timeout
+ % Default timetrap timeout
-define(default_timeout, ?t:minutes(1)).
%%--------------------------------------------------------------------
@@ -64,19 +64,11 @@ end_per_suite(Config) ->
groups() ->
[{not_unicode, [], [{group,erlang_server},
{group,openssh_server},
- {group,'diffie-hellman-group-exchange-sha1'},
- {group,'diffie-hellman-group-exchange-sha256'},
sftp_nonexistent_subsystem]},
{unicode, [], [{group,erlang_server},
{group,openssh_server},
sftp_nonexistent_subsystem]},
-
- {'diffie-hellman-group-exchange-sha1', [], [{group,erlang_server},
- {group,openssh_server}]},
-
- {'diffie-hellman-group-exchange-sha256', [], [{group,erlang_server},
- {group,openssh_server}]},
{erlang_server, [], [{group,write_read_tests},
version_option,
@@ -159,7 +151,7 @@ init_per_group(unicode, Config) ->
_ ->
{skip, "Not unicode file encoding"}
end;
-
+
init_per_group(erlang_server, Config) ->
ct:comment("Begin ~p",[grps(Config)]),
PrivDir = ?config(priv_dir, Config),
@@ -167,20 +159,18 @@ init_per_group(erlang_server, Config) ->
User = ?config(user, Config),
Passwd = ?config(passwd, Config),
Sftpd = {_, HostX, PortX} =
- ssh_test_lib:daemon(extra_opts(Config) ++
- [{system_dir, SysDir},
- {user_dir, PrivDir},
- {user_passwords,
- [{User, Passwd}]}]),
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {user_passwords,
+ [{User, Passwd}]}]),
[{peer, {fmt_host(HostX),PortX}}, {group, erlang_server}, {sftpd, Sftpd} | Config];
init_per_group(openssh_server, Config) ->
ct:comment("Begin ~p",[grps(Config)]),
Host = ssh_test_lib:hostname(),
case (catch ssh_sftp:start_channel(Host,
- extra_opts(Config) ++
- [{user_interaction, false},
- {silently_accept_hosts, true}])) of
+ [{user_interaction, false},
+ {silently_accept_hosts, true}])) of
{ok, _ChannelPid, Connection} ->
[{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]),
ssh:close(Connection),
@@ -201,11 +191,10 @@ init_per_group(remote_tar, Config) ->
case ?config(group, Config) of
erlang_server ->
ssh:connect(Host, Port,
- extra_opts(Config) ++
- [{user, User},
- {password, Passwd},
- {user_interaction, false},
- {silently_accept_hosts, true}]);
+ [{user, User},
+ {password, Passwd},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]);
openssh_server ->
ssh:connect(Host, Port,
[{user_interaction, false},
@@ -214,28 +203,6 @@ init_per_group(remote_tar, Config) ->
[{remote_tar, true},
{connection, Connection} | Config];
-init_per_group('diffie-hellman-group-exchange-sha1', Config) ->
- case lists:member('diffie-hellman-group-exchange-sha1',
- ssh_transport:supported_algorithms(kex)) of
- true ->
- [{extra_opts, [{preferred_algorithms, [{kex,['diffie-hellman-group-exchange-sha1']}]}]}
- | Config];
-
- false ->
- {skip,"'diffie-hellman-group-exchange-sha1' not supported by this version of erlang ssh"}
- end;
-
-init_per_group('diffie-hellman-group-exchange-sha256', Config) ->
- case lists:member('diffie-hellman-group-exchange-sha256',
- ssh_transport:supported_algorithms(kex)) of
- true ->
- [{extra_opts, [{preferred_algorithms, [{kex,['diffie-hellman-group-exchange-sha256']}]}]}
- | Config];
-
- false ->
- {skip,"'diffie-hellman-group-exchange-sha256' not supported by this version of erlang ssh"}
- end;
-
init_per_group(write_read_tests, Config) ->
ct:comment("Begin ~p",[grps(Config)]),
Config.
@@ -278,12 +245,11 @@ init_per_testcase(version_option, Config) ->
Passwd = ?config(passwd, Config),
{ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host, Port,
- extra_opts(Config) ++
- [{sftp_vsn, 3},
- {user, User},
- {password, Passwd},
- {user_interaction, false},
- {silently_accept_hosts, true}]),
+ [{sftp_vsn, 3},
+ {user, User},
+ {password, Passwd},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
Sftp = {ChannelPid, Connection},
[{sftp,Sftp}, {watchdog, Dog} | TmpConfig];
@@ -301,11 +267,10 @@ init_per_testcase(Case, Config0) ->
{_,Host, Port} = ?config(sftpd, Config2),
{ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host, Port,
- extra_opts(Config2) ++
- [{user, User},
- {password, Passwd},
- {user_interaction, false},
- {silently_accept_hosts, true}]
+ [{user, User},
+ {password, Passwd},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]
),
Sftp = {ChannelPid, Connection},
[{sftp, Sftp}, {watchdog, Dog} | Config2];
@@ -315,9 +280,8 @@ init_per_testcase(Case, Config0) ->
Host = ssh_test_lib:hostname(),
{ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host,
- extra_opts(Config2) ++
- [{user_interaction, false},
- {silently_accept_hosts, true}]),
+ [{user_interaction, false},
+ {silently_accept_hosts, true}]),
Sftp = {ChannelPid, Connection},
[{sftp, Sftp}, {watchdog, Dog} | Config2]
end,
@@ -494,7 +458,7 @@ mk_rm_dir() ->
mk_rm_dir(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
{Sftp, _} = ?config(sftp, Config),
-
+
DirName = filename:join(PrivDir, "test"),
ok = ssh_sftp:make_dir(Sftp, DirName),
ok = ssh_sftp:del_dir(Sftp, DirName),
@@ -767,7 +731,7 @@ directory_to_tar(Config) ->
ok = erl_tar:add(Handle, fn("d1",Config), "d1", [verbose]),
ok = erl_tar:close(Handle),
chk_tar(["d1"], Config).
-
+
%%--------------------------------------------------------------------
binaries_to_tar(Config) ->
ChPid2 = ?config(channel_pid2, Config),
@@ -831,9 +795,9 @@ simple_crypto_tar_big(Config) ->
chk_tar([{"b1",Bin}, F1, "big.txt"], Config, [{crypto,{Cinit,Cdec}}]).
stuff(Bin) -> << <<C,C>> || <<C>> <= Bin >>.
-
+
unstuff(Bin) -> << <<C>> || <<C,C>> <= Bin >>.
-
+
%%--------------------------------------------------------------------
read_tar(Config) ->
ChPid2 = ?config(channel_pid2, Config),
@@ -1002,9 +966,6 @@ prep(Config) ->
ok = file:write_file_info(TestFile,
FileInfo#file_info{mode = Mode}).
-extra_opts(Config) ->
- proplists:get_value(extra_opts, Config, []).
-
chk_tar(Items, Config) ->
chk_tar(Items, Config, []).
@@ -1041,7 +1002,7 @@ analyze_report([E={NameE,BinE}|Es], [A={NameA,BinA}|As]) ->
NameE < NameA ->
[["Component ",NameE," is missing.\n\n"]
| analyze_report(Es,[A|As])];
-
+
NameE > NameA ->
[["Component ",NameA," is not expected.\n\n"]
| analyze_report([E|Es],As)];
@@ -1054,7 +1015,7 @@ analyze_report([], [{NameA,_BinA}|As]) ->
[["Component ",NameA," not expected.\n\n"] | analyze_report([],As)];
analyze_report([], []) ->
"".
-
+
tar_size(TarFileName, Config) ->
{ChPid,_} = ?config(sftp,Config),
{ok,Data} = ssh_sftp:read_file(ChPid, TarFileName),
@@ -1088,4 +1049,4 @@ fn(Name, Config) ->
fmt_host({A,B,C,D}) -> lists:concat([A,".",B,".",C,".",D]);
fmt_host(S) -> S.
-
+
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 988ea47bd8..6d568125bb 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -27,6 +27,8 @@
-include_lib("public_key/include/public_key.hrl").
-include_lib("common_test/include/ct.hrl").
+-include_lib("ssh/src/ssh_transport.hrl").
+
-define(TIMEOUT, 50000).
@@ -65,6 +67,55 @@ daemon(Host, Port, Options) ->
end.
+std_daemon(Config, ExtraOpts) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ std_daemon1(Config,
+ ExtraOpts ++
+ [{user_dir, UserDir},
+ {user_passwords, [{"usr1","pwd1"}]}]).
+
+std_daemon1(Config, ExtraOpts) ->
+ SystemDir = ?config(data_dir, Config),
+ {_Server, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}
+ | ExtraOpts]).
+
+std_connect(Config, Host, Port, ExtraOpts) ->
+ UserDir = ?config(priv_dir, Config),
+ _ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user, "usr1"},
+ {password, "pwd1"},
+ {user_interaction, false}
+ | ExtraOpts]).
+
+std_simple_sftp(Host, Port, Config) ->
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "test.data"),
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, []),
+ {ok, ChannelRef} = ssh_sftp:start_channel(ConnectionRef),
+ Data = crypto:rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)),
+ ok = ssh_sftp:write_file(ChannelRef, DataFile, Data),
+ {ok,ReadData} = file:read_file(DataFile),
+ ok = ssh:close(ConnectionRef),
+ Data == ReadData.
+
+std_simple_exec(Host, Port, Config) ->
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, []),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId, "23+21-2.", infinity),
+ Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"42\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).
+
start_shell(Port, IOServer, UserDir) ->
start_shell(Port, IOServer, UserDir, []).
@@ -372,3 +423,133 @@ openssh_sanity_check(Config) ->
ssh:stop(),
{skip, Str}
end.
+
+%%--------------------------------------------------------------------
+%% Check if we have a "newer" ssh client that supports these test cases
+
+ssh_client_supports_Q() ->
+ ErlPort = open_port({spawn, "ssh -Q cipher"}, [exit_status, stderr_to_stdout]),
+ 0 == check_ssh_client_support2(ErlPort).
+
+check_ssh_client_support2(P) ->
+ receive
+ {P, {data, _A}} ->
+ check_ssh_client_support2(P);
+ {P, {exit_status, E}} ->
+ E
+ after 5000 ->
+
+ ct:log("Openssh command timed out ~n"),
+ -1
+ end.
+
+default_algorithms(Host, Port) ->
+ KexInitPattern =
+ #ssh_msg_kexinit{
+ kex_algorithms = '$kex_algorithms',
+ server_host_key_algorithms = '$server_host_key_algorithms',
+ encryption_algorithms_client_to_server = '$encryption_algorithms_client_to_server',
+ encryption_algorithms_server_to_client = '$encryption_algorithms_server_to_client',
+ mac_algorithms_client_to_server = '$mac_algorithms_client_to_server',
+ mac_algorithms_server_to_client = '$mac_algorithms_server_to_client',
+ compression_algorithms_client_to_server = '$compression_algorithms_client_to_server',
+ compression_algorithms_server_to_client = '$compression_algorithms_server_to_client',
+ _ = '_'
+ },
+
+ try ssh_trpt_test_lib:exec(
+ [{connect,Host,Port, [{silently_accept_hosts, true},
+ {user_interaction, false}]},
+ {send,hello},
+ receive_hello,
+ {send, ssh_msg_kexinit},
+ {match, KexInitPattern, receive_msg},
+ close_socket])
+ of
+ {ok,E} ->
+ [Kex, PubKey, EncC2S, EncS2C, MacC2S, MacS2C, CompC2S, CompS2C] =
+ ssh_trpt_test_lib:instantiate(['$kex_algorithms',
+ '$server_host_key_algorithms',
+ '$encryption_algorithms_client_to_server',
+ '$encryption_algorithms_server_to_client',
+ '$mac_algorithms_client_to_server',
+ '$mac_algorithms_server_to_client',
+ '$compression_algorithms_client_to_server',
+ '$compression_algorithms_server_to_client'
+ ], E),
+ [{kex, to_atoms(Kex)},
+ {public_key, to_atoms(PubKey)},
+ {cipher, [{client2server, to_atoms(EncC2S)},
+ {server2client, to_atoms(EncS2C)}]},
+ {mac, [{client2server, to_atoms(MacC2S)},
+ {server2client, to_atoms(MacS2C)}]},
+ {compression, [{client2server, to_atoms(CompC2S)},
+ {server2client, to_atoms(CompS2C)}]}];
+ _ ->
+ []
+ catch
+ _:_ ->
+ []
+ end.
+
+
+default_algorithms(sshd) ->
+ default_algorithms("localhost", 22);
+default_algorithms(sshc) ->
+ case os:find_executable("ssh") of
+ false ->
+ [];
+ _ ->
+ Cipher = sshc(cipher),
+ Mac = sshc(mac),
+ [{kex, sshc(kex)},
+ {public_key, sshc(key)},
+ {cipher, [{client2server, Cipher},
+ {server2client, Cipher}]},
+ {mac, [{client2server, Mac},
+ {server2client, Mac}]}
+ ]
+ end.
+
+sshc(Tag) ->
+ to_atoms(
+ string:tokens(os:cmd(lists:concat(["ssh -Q ",Tag])), "\n")
+ ).
+
+ssh_type() ->
+ case os:find_executable("ssh") of
+ false -> not_found;
+ _ ->
+ case os:cmd("ssh -V") of
+ "OpenSSH" ++ _ ->
+ openSSH;
+ Str ->
+ ct:log("ssh client ~p is unknown",[Str]),
+ unknown
+ end
+ end.
+
+algo_intersection([], _) -> [];
+algo_intersection(_, []) -> [];
+algo_intersection(L1=[A1|_], L2=[A2|_]) when is_atom(A1), is_atom(A2) ->
+ true = lists:all(fun erlang:is_atom/1, L1++L2),
+ lists:foldr(fun(A,Acc) ->
+ case lists:member(A,L2) of
+ true -> [A|Acc];
+ false -> Acc
+ end
+ end, [], L1);
+algo_intersection([{K,V1}|T1], L2) ->
+ case lists:keysearch(K,1,L2) of
+ {value, {K,V2}} ->
+ [{K,algo_intersection(V1,V2)} | algo_intersection(T1,L2)];
+ false ->
+ algo_intersection(T1,L2)
+ end;
+algo_intersection(_, _) ->
+ [].
+
+
+to_atoms(L) -> lists:map(fun erlang:list_to_atom/1, L).
+
+
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index 663168b169..104c1f9107 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -85,6 +85,11 @@ init_per_group(erlang_server, Config) ->
UserDir = ?config(priv_dir, Config),
ssh_test_lib:setup_dsa_known_host(DataDir, UserDir),
Config;
+init_per_group(erlang_client, Config) ->
+ CommonAlgs = ssh_test_lib:algo_intersection(
+ ssh:default_algorithms(),
+ ssh_test_lib:default_algorithms("localhost", 22)),
+ [{common_algs,CommonAlgs} | Config];
init_per_group(_, Config) ->
Config.
@@ -201,43 +206,49 @@ erlang_client_openssh_server_kexs() ->
[{doc, "Test that we can connect with different KEXs."}].
erlang_client_openssh_server_kexs(Config) when is_list(Config) ->
- Success =
- lists:foldl(
- fun(Kex, Acc) ->
- ct:log("============= ~p ============= ~p",[Kex,Acc]),
- ConnectionRef =
- ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false},
- {preferred_algorithms,
- [{kex,[Kex]}]}]),
-
- {ok, ChannelId} =
- ssh_connection:session_channel(ConnectionRef, infinity),
- success =
- ssh_connection:exec(ConnectionRef, ChannelId,
- "echo testing", infinity),
-
- ExpectedData = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
- case ssh_test_lib:receive_exec_result(ExpectedData) of
- expected ->
- ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
- Acc;
- {unexpected_msg,{ssh_cm, ConnectionRef,
- {exit_status, ChannelId, 0}} = ExitStatus} ->
- ct:log("0: Collected data ~p", [ExitStatus]),
- ssh_test_lib:receive_exec_result(ExpectedData, ConnectionRef, ChannelId),
- Acc;
- Other ->
- ct:log("~p failed: ~p",[Kex,Other]),
- [Kex|Acc]
- end
- end, [], ssh_transport:supported_algorithms(kex)),
- case Success of
- [] ->
- ok;
- BadKex ->
- ct:log("Bad kex algos: ~p",[BadKex]),
- {fail, "Kex failed for one or more algos"}
+ KexAlgos = try proplists:get_value(kex, ?config(common_algs,Config))
+ catch _:_ -> []
+ end,
+ comment(KexAlgos),
+ case KexAlgos of
+ [] -> {skip, "No common kex algorithms"};
+ _ ->
+ Success =
+ lists:foldl(
+ fun(Kex, Acc) ->
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {preferred_algorithms,
+ [{kex,[Kex]}]}]),
+
+ {ok, ChannelId} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ success =
+ ssh_connection:exec(ConnectionRef, ChannelId,
+ "echo testing", infinity),
+
+ ExpectedData = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
+ case ssh_test_lib:receive_exec_result(ExpectedData) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
+ Acc;
+ {unexpected_msg,{ssh_cm, ConnectionRef,
+ {exit_status, ChannelId, 0}} = ExitStatus} ->
+ ct:log("0: Collected data ~p", [ExitStatus]),
+ ssh_test_lib:receive_exec_result(ExpectedData, ConnectionRef, ChannelId),
+ Acc;
+ Other ->
+ ct:log("~p failed: ~p",[Kex,Other]),
+ false
+ end
+ end, true, KexAlgos),
+ case Success of
+ true ->
+ ok;
+ false ->
+ {fail, "Kex failed for one or more algos"}
+ end
end.
%%--------------------------------------------------------------------
@@ -283,45 +294,37 @@ erlang_server_openssh_client_cipher_suites(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
-
ct:sleep(500),
- Supports = crypto:supports(),
- Ciphers = proplists:get_value(ciphers, Supports),
- Tests = [
- {"3des-cbc", lists:member(des3_cbc, Ciphers)},
- {"aes128-cbc", lists:member(aes_cbc128, Ciphers)},
- {"aes128-ctr", lists:member(aes_ctr, Ciphers)},
- {"aes256-cbc", false}
- ],
- lists:foreach(fun({Cipher, Expect}) ->
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " " ++
- " -c " ++ Cipher ++ " 1+1.",
-
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- case Expect of
- true ->
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
- end;
- false ->
- receive
- {SshPort,{data, <<"no matching cipher found", _/binary>>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive no matching cipher message")
- end
- end
- end, Tests),
-
- ssh:stop_daemon(Pid).
+ OpenSshCiphers =
+ ssh_test_lib:to_atoms(
+ string:tokens(os:cmd("ssh -Q cipher"), "\n")),
+ ErlCiphers =
+ proplists:get_value(client2server,
+ proplists:get_value(cipher, ssh:default_algorithms())),
+ CommonCiphers =
+ ssh_test_lib:algo_intersection(ErlCiphers, OpenSshCiphers),
+
+ comment(CommonCiphers),
+
+ lists:foreach(
+ fun(Cipher) ->
+ Cmd = lists:concat(["ssh -p ",Port,
+ " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
+ " -c ",Cipher," 1+1."]),
+ ct:log("Cmd: ~p~n", [Cmd]),
+
+ SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ ct:fail("~p Did not receive answer",[Cipher])
+ end
+ end, CommonCiphers),
+
+ ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
erlang_server_openssh_client_macs() ->
@@ -333,45 +336,40 @@ erlang_server_openssh_client_macs(Config) when is_list(Config) ->
KnownHosts = filename:join(PrivDir, "known_hosts"),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2}]),
+ {failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
- Supports = crypto:supports(),
- Hashs = proplists:get_value(hashs, Supports),
- MACs = [{"hmac-sha1", lists:member(sha, Hashs)},
- {"hmac-sha2-256", lists:member(sha256, Hashs)},
- {"hmac-md5-96", false},
- {"hmac-ripemd160", false}],
- lists:foreach(fun({MAC, Expect}) ->
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " " ++
- " -o MACs=" ++ MAC ++ " 1+1.",
-
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- case Expect of
- true ->
- receive
- {SshPort,{data, <<"2\n">>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive answer")
- end;
- false ->
- receive
- {SshPort,{data, <<"no matching mac found", _/binary>>}} ->
- ok
- after ?TIMEOUT ->
- ct:fail("Did not receive no matching mac message")
- end
- end
- end, MACs),
+ OpenSshMacs =
+ ssh_test_lib:to_atoms(
+ string:tokens(os:cmd("ssh -Q mac"), "\n")),
+ ErlMacs =
+ proplists:get_value(client2server,
+ proplists:get_value(mac, ssh:default_algorithms())),
+ CommonMacs =
+ ssh_test_lib:algo_intersection(ErlMacs, OpenSshMacs),
+
+ comment(CommonMacs),
+
+ lists:foreach(
+ fun(MAC) ->
+ Cmd = lists:concat(["ssh -p ",Port,
+ " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
+ " -o MACs=",MAC," 1+1."]),
+ ct:log("Cmd: ~p~n", [Cmd]),
+
+ SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ ct:fail("~p Did not receive answer",[MAC])
+ end
+ end, CommonMacs),
- ssh:stop_daemon(Pid).
+ ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
erlang_server_openssh_client_kexs() ->
@@ -389,54 +387,34 @@ erlang_server_openssh_client_kexs(Config) when is_list(Config) ->
]),
ct:sleep(500),
- ErlKexs = lists:map(fun erlang:atom_to_list/1,
- ssh_transport:supported_algorithms(kex)),
- OpenSshKexs = string:tokens(os:cmd("ssh -Q kex"), "\n"),
-
- Kexs = [{OpenSshKex,lists:member(OpenSshKex,ErlKexs)}
- || OpenSshKex <- OpenSshKexs],
-
- Success =
- lists:foldl(
- fun({Kex, Expect}, Acc) ->
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " " ++
- " -o KexAlgorithms=" ++ Kex ++ " 1+1.",
-
- ct:log("Cmd: ~p~n", [Cmd]),
-
- SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
-
- case Expect of
- true ->
- receive
- {SshPort,{data, <<"2\n">>}} ->
- Acc
- after ?TIMEOUT ->
- ct:log("Did not receive answer for ~p",[Kex]),
- [Kex|Acc]
- end;
- false ->
- receive
- {SshPort,{data, <<"Unable to negotiate a key exchange method", _/binary>>}} ->
- Acc
- after ?TIMEOUT ->
- ct:log("Did not receive no matching kex message for ~p",[Kex]),
- [Kex|Acc]
- end
- end
- end, [], Kexs),
+ OpenSshKexs =
+ ssh_test_lib:to_atoms(
+ string:tokens(os:cmd("ssh -Q kex"), "\n")),
+ ErlKexs =
+ proplists:get_value(kex, ssh:default_algorithms()),
+ CommonKexs =
+ ssh_test_lib:algo_intersection(ErlKexs, OpenSshKexs),
+
+ comment(CommonKexs),
+
+ lists:foreach(
+ fun(Kex) ->
+ Cmd = lists:concat(["ssh -p ",Port,
+ " -o UserKnownHostsFile=",KnownHosts," ",Host," ",
+ " -o KexAlgorithms=",Kex," 1+1."]),
+ ct:log("Cmd: ~p~n", [Cmd]),
+
+ SshPort = open_port({spawn, Cmd}, [binary, stderr_to_stdout]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ ct:log("~p Did not receive answer",[Kex])
+ end
+ end, CommonKexs),
- ssh:stop_daemon(Pid),
-
- case Success of
- [] ->
- ok;
- BadKex ->
- ct:log("Bad kex algos: ~p",[BadKex]),
- {fail, "Kex failed for one or more algos"}
- end.
-
+ ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
erlang_server_openssh_client_exec_compressed() ->
@@ -698,26 +676,17 @@ extra_logout() ->
end.
%%--------------------------------------------------------------------
-%%--------------------------------------------------------------------
%% Check if we have a "newer" ssh client that supports these test cases
-%%--------------------------------------------------------------------
check_ssh_client_support(Config) ->
- Port = open_port({spawn, "ssh -Q cipher"}, [exit_status, stderr_to_stdout]),
- case check_ssh_client_support2(Port) of
- 0 -> % exit status from command (0 == ok)
+ case ssh_test_lib:ssh_client_supports_Q() of
+ true ->
ssh:start(),
Config;
_ ->
{skip, "test case not supported by ssh client"}
end.
-check_ssh_client_support2(P) ->
- receive
- {P, {data, _A}} ->
- check_ssh_client_support2(P);
- {P, {exit_status, E}} ->
- E
- after 5000 ->
- ct:log("Openssh command timed out ~n"),
- -1
- end.
+comment(AtomList) ->
+ ct:comment(
+ string:join(lists:map(fun erlang:atom_to_list/1, AtomList),
+ ", ")).
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index 66df890f5c..caf9bac3b6 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -23,6 +23,7 @@
%%-compile(export_all).
-export([exec/1, exec/2,
+ instantiate/2,
format_msg/1,
server_host_port/1
]