aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiclas Eklund <[email protected]>2011-05-19 14:39:46 +0200
committerNiclas Eklund <[email protected]>2011-05-19 14:39:46 +0200
commit33f86c08865fdbac959248fb02fc2f9d10dba44c (patch)
tree964fd772aee9c27460042f7e3fa5396628fca1f0
parent2a3bb0347d691703cc1eb9b1fd6f29f489ff4e3a (diff)
parent358af31ec75e32046e44645c6b6d511062e46393 (diff)
downloadotp-33f86c08865fdbac959248fb02fc2f9d10dba44c.tar.gz
otp-33f86c08865fdbac959248fb02fc2f9d10dba44c.tar.bz2
otp-33f86c08865fdbac959248fb02fc2f9d10dba44c.zip
Merge branch 'nick/ssh-test-suite' into dev
* nick/ssh-test-suite: Removed unused record and fixed a few failing test cases, Generic key management for test suites. Some minor updates regarding how test data is generated. Fixed host handling in ssh_test_lib (daemon and connect functions). Fixed include problem. Corrected include path and spelling of a test case function. Resolved merge conflict. Resolved merge issue. Added code for generating certificates. Altering SSH test keys. Add test suite for ssh Update init_per_suite so that tests are skipped if crypto/ssh cannot be started. Add cover spec file Convert ssh test cases to common test stardard Add test suite for ssh
-rw-r--r--lib/ssh/test/Makefile121
-rw-r--r--lib/ssh/test/ssh.cover2
-rw-r--r--lib/ssh/test/ssh.spec7
-rw-r--r--lib/ssh/test/ssh.spec.vxworks3
-rw-r--r--lib/ssh/test/ssh_SUITE.erl72
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl389
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl543
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub1
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt252
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl934
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE_data/test.txt1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl328
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl100
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/test.txt1
-rw-r--r--lib/ssh/test/ssh_test_lib.erl684
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl458
17 files changed, 3911 insertions, 0 deletions
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
new file mode 100644
index 0000000000..5a2a6de24a
--- /dev/null
+++ b/lib/ssh/test/Makefile
@@ -0,0 +1,121 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2004-2011. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(GS_VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ ssh_test_lib \
+ ssh_SUITE \
+ ssh_basic_SUITE \
+ ssh_to_openssh_SUITE \
+ ssh_sftp_SUITE \
+ ssh_sftpd_SUITE \
+ ssh_sftpd_erlclient_SUITE
+
+HRL_FILES_NEEDED_IN_TEST= \
+ $(ERL_TOP)/lib/ssh/src/ssh.hrl \
+ $(ERL_TOP)/lib/ssh/src/ssh_xfer.hrl
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+DATA_DIRS = $(MODULES:%=%_data)
+
+INCLUDES = -I$(ERL_TOP)/lib/test_server/include \
+ -I$(ERL_TOP)/lib/ssh/src \
+
+EMAKEFILE=Emakefile
+MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
+
+ifeq ($(MAKE_EMAKE),)
+BUILDTARGET = $(TARGET_FILES)
+RELTEST_FILES = $(INETS_SPECS) $(SOURCE)
+else
+BUILDTARGET = emakebuild
+RELTEST_FILES = $(EMAKEFILE) $(INETS_SPECS) $(SOURCE)
+endif
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/ssh_test
+
+# ----------------------------------------------------
+# FLAGS
+# The path to the test_server ebin dir is needed when
+# running the target "targets".
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += -pa ../../../internal_tools/test_server/ebin \
+ $(INCLUDES)
+
+EBIN = .
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+tests debug opt: $(BUILDTARGET)
+
+targets: $(TARGET_FILES)
+
+.PHONY: emakebuild
+
+emakebuild: $(EMAKEFILE)
+
+$(EMAKEFILE):
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE)
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE)
+
+clean:
+ rm -f $(EMAKEFILE)
+ rm -f $(TARGET_FILES)
+ rm -f core
+docs:
+
+info:
+ @echo "TARGET_FILES = $(TARGET_FILES)"
+ @echo "DATA_DIRS = $(DATA_DIRS)"
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)
+ $(INSTALL_DATA) ssh.spec ssh.cover $(RELSYSDIR)
+ $(INSTALL_DATA) $(HRL_FILES_NEEDED_IN_TEST) $(RELSYSDIR)
+ chmod -f -R u+w $(RELSYSDIR)
+ @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
diff --git a/lib/ssh/test/ssh.cover b/lib/ssh/test/ssh.cover
new file mode 100644
index 0000000000..a4221fbbbe
--- /dev/null
+++ b/lib/ssh/test/ssh.cover
@@ -0,0 +1,2 @@
+{incl_app,ssh,details}.
+
diff --git a/lib/ssh/test/ssh.spec b/lib/ssh/test/ssh.spec
new file mode 100644
index 0000000000..8de0fe44e4
--- /dev/null
+++ b/lib/ssh/test/ssh.spec
@@ -0,0 +1,7 @@
+{suites,"../ssh_test",all}.
+{skip_cases,"../ssh_test",ssh_ssh_SUITE,
+ [ssh],
+ "Current implementation is timingdependent and\nhence will succeed/fail on a whim"}.
+{skip_cases,"../ssh_test",ssh_ssh_SUITE,
+ [ssh_compressed],
+ "Current implementation is timingdependent hence will succeed/fail on a whim"}.
diff --git a/lib/ssh/test/ssh.spec.vxworks b/lib/ssh/test/ssh.spec.vxworks
new file mode 100644
index 0000000000..81f665283c
--- /dev/null
+++ b/lib/ssh/test/ssh.spec.vxworks
@@ -0,0 +1,3 @@
+{topcase, {dir, "../ssh_test"}}.
+{require_nodenames, 1}.
+%{skip, {M, F, "Not yet implemented"}}.
diff --git a/lib/ssh/test/ssh_SUITE.erl b/lib/ssh/test/ssh_SUITE.erl
new file mode 100644
index 0000000000..953c9080f9
--- /dev/null
+++ b/lib/ssh/test/ssh_SUITE.erl
@@ -0,0 +1,72 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%%----------------------------------------------------------------
+%%% Purpose:ssh application test suite.
+%%%-----------------------------------------------------------------
+-module(ssh_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+
+% Default timetrap timeout (set in init_per_testcase).
+-define(default_timeout, ?t:minutes(1)).
+-define(application, ssh).
+
+% Test server specific exports
+-export([all/0,groups/0,init_per_group/2,end_per_group/2]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
+% Test cases must be exported.
+-export([app_test/1]).
+-define(cases, [app_test]).
+
+%%
+%% all/1
+%%
+all() ->
+ [app_test].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(_Case, Config) ->
+ Dog=test_server:timetrap(?default_timeout),
+ [{watchdog, Dog}|Config].
+end_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+%
+% Test cases starts here.
+%
+app_test(suite) ->
+ [];
+app_test(doc) ->
+ ["Application consistency test."];
+app_test(Config) when is_list(Config) ->
+ ?t:app_test(?application),
+ ok.
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
new file mode 100644
index 0000000000..5ea0d98980
--- /dev/null
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -0,0 +1,389 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_basic_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(NEWLINE, <<"\r\n">>).
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initialization before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ Dir = ?config(priv_dir, Config),
+ {ok, _} = ssh_test_lib:get_id_keys(Dir),
+ ssh_test_lib:make_dsa_files(Config),
+ Config;
+ _Else ->
+ {skip, "Crypto could not be started!"}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ Dir = ?config(priv_dir, Config),
+ crypto:stop(),
+ ssh_test_lib:remove_id_keys(Dir),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initialization before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initialization before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssh_test_lib:known_hosts(backup),
+ ssh:start(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ssh:stop(),
+ ssh_test_lib:known_hosts(restore),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all() ->
+ [exec, exec_compressed, shell, daemon_already_started,
+ server_password_option, server_userpassword_option,
+ known_hosts].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+sign_and_verify_rsa(doc) ->
+ ["Test api function ssh:sign_data and ssh:verify_data"];
+
+sign_and_verify_rsa(suite) ->
+ [];
+sign_and_verify_rsa(Config) when is_list(Config) ->
+ Data = ssh:sign_data(<<"correct data">>, "ssh-rsa"),
+ ok = ssh:verify_data(<<"correct data">>, Data, "ssh-rsa"),
+ {error,invalid_signature} = ssh:verify_data(<<"incorrect data">>, Data,"ssh-rsa").
+
+
+exec(doc) ->
+ ["Test api function ssh_connection:exec"];
+
+exec(suite) ->
+ [];
+
+exec(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "1+1.", infinity),
+ Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"2\n">>}},
+ case ssh_test_lib:receive_exec_result(Data0) of
+ expected ->
+ ok;
+ Other0 ->
+ test_server:fail(Other0)
+ end,
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
+
+ %% Test that it is possible to start a new channel and
+ %% run an other exec on the same connection.
+ {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId1,
+ "2+2.", infinity),
+ Data1 = {ssh_cm, ConnectionRef, {data, ChannelId1, 0, <<"4\n">>}},
+ case ssh_test_lib:receive_exec_result(Data1) of
+ expected ->
+ ok;
+ Other1 ->
+ test_server:fail(Other1)
+ end,
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+exec_compressed(doc) ->
+ ["Test that compression option works"];
+
+exec_compressed(suite) ->
+ [];
+
+exec_compressed(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {compression, zlib},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {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 ->
+ test_server:fail(Other)
+ end,
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+
+shell(doc) ->
+ ["Test that ssh:shell/2 works"];
+
+shell(suite) ->
+ [];
+
+shell(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = ?config(data_dir, Config),
+ {_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ test_server:sleep(500),
+
+ IO = ssh_test_lib:start_io_server(),
+ Shell = ssh_test_lib:start_shell(Port, IO),
+ receive
+ ErlShellStart ->
+ test_server:format("Erlang shell start: ~p~n", [ErlShellStart])
+ end,
+ receive
+ ErlPrompt0 ->
+ test_server:format("Erlang prompt: ~p~n", [ErlPrompt0])
+ end,
+ IO ! {input, self(), "1+1.\r\n"},
+ receive
+ Echo0 ->
+ test_server:format("Echo: ~p ~n", [Echo0])
+ end,
+ receive
+ ?NEWLINE ->
+ ok
+ end,
+ receive
+ Result0 = <<"2">> ->
+ test_server:format("Result: ~p~n", [Result0])
+ end,
+ receive
+ ?NEWLINE ->
+ ok
+ end,
+ receive
+ ErlPrompt1 ->
+ test_server:format("Erlang prompt: ~p~n", [ErlPrompt1])
+ end,
+ exit(Shell, kill),
+ %% Does not seem to work in the testserver!
+ %% IO ! {input, self(), "q().\r\n"},
+ %% receive
+ %% ?NEWLINE ->
+ %% ok
+ %% end,
+ %% receive
+ %% Echo1 ->
+ %% test_server:format("Echo: ~p ~n", [Echo1])
+ %% end,
+ %% receive
+ %% ?NEWLINE ->
+ %% ok
+ %% end,
+ %% receive
+ %% Result1 ->
+ %% test_server:format("Result: ~p~n", [Result1])
+ %% end,
+ receive
+ {'EXIT', Shell, killed} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+daemon_already_started(doc) ->
+ ["Test that get correct error message if you try to start a daemon",
+ "on an adress that already runs a daemon see also seq10667" ];
+
+daemon_already_started(suite) ->
+ [];
+
+daemon_already_started(Config) when is_list(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ {Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ {error, eaddrinuse} = ssh_test_lib:daemon(Port, [{system_dir, SystemDir},
+ {failfun,
+ fun ssh_test_lib:failfun/2}]),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+server_password_option(doc) ->
+ ["validate to server that uses the 'password' option"];
+server_password_option(suite) ->
+ [];
+server_password_option(Config) when is_list(Config) ->
+ UserDir = ?config(data_dir, Config), % to make sure we don't use
+ SysDir = ?config(data_dir, Config), % public-key-auth
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {password, "morot"}]),
+
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {error, Reason} =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "foo"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ test_server:format("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(suite) ->
+ [];
+server_userpassword_option(Config) when is_list(Config) ->
+ UserDir = ?config(data_dir, Config), % to make sure we don't use
+ SysDir = ?config(data_dir, Config), % public-key-auth
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {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),
+
+ {error, Reason0} =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ test_server:format("Test of user foo that does not exist. "
+ "Error msg: ~p ~n", [Reason0]),
+
+ {error, Reason1} =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "vego"},
+ {password, "foo"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ test_server:format("Test of wrong Password. "
+ "Error msg: ~p ~n", [Reason1]),
+
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+known_hosts(doc) ->
+ ["check that known_hosts is updated correctly"];
+known_hosts(suite) ->
+ [];
+known_hosts(Config) when is_list(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ UserDir = ?config(priv_dir, Config),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ KnownHosts = filename:join(UserDir, "known_hosts"),
+ file:delete(KnownHosts),
+ {error, enoent} = file:read_file(KnownHosts),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{user_dir, UserDir},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ {ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh:close(ConnectionRef),
+ {ok, Binary} = file:read_file(KnownHosts),
+ Lines = string:tokens(binary_to_list(Binary), "\n"),
+ [Line] = Lines,
+ [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
+ [Host, _Ip] = string:tokens(HostAndIp, ","),
+ "ssh-" ++ _ = Alg,
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%% Internal functions
+%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
new file mode 100644
index 0000000000..c96b6de3ea
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -0,0 +1,543 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssh_sftp_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+% Default timetrap timeout
+-define(default_timeout, ?t:minutes(1)).
+
+-define(SFPD_PORT, 9999).
+-define(USER, "Alladin").
+-define(PASSWD, "Sesame").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case {catch crypto:start(),catch ssh:start()} of
+ {ok,ok} ->
+ Dir = ?config(priv_dir, Config),
+ {ok, _} = ssh_test_lib:get_id_keys(Dir),
+ ssh_test_lib:make_dsa_files(Config),
+ Config;
+ {ok,_} ->
+ {skip,"Could not start ssh!"};
+ {_,ok} ->
+ {skip,"Could not start crypto!"};
+ {_,_} ->
+ {skip,"Could not start crypto and ssh!"}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ crypto:stop(),
+ Dir = ?config(priv_dir, Config),
+ ssh_test_lib:remove_id_keys(Dir),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ prep(Config),
+ TmpConfig0 = lists:keydelete(watchdog, 1, Config),
+ TmpConfig = lists:keydelete(sftp, 1, TmpConfig0),
+ Dog = test_server:timetrap(?default_timeout),
+ Dir = ?config(priv_dir, Config),
+ SysDir = ?config(data_dir, Config),
+ Host = ssh_test_lib:hostname(),
+
+ Sftp = case (catch ssh_sftp:start_channel(Host,
+ [{user_dir, Dir},
+ {user_interaction, false},
+ {silently_accept_hosts, true}])) of
+ {ok, ChannelPid, Connection} ->
+ {ChannelPid, Connection};
+ _Error ->
+ {_Sftpd, _Host, _Port} =
+ ssh_test_lib:daemon(Host, ?SFPD_PORT,
+ [{system_dir, SysDir},
+ {user_passwords,
+ [{?USER, ?PASSWD}]},
+ {failfun,
+ fun ssh_test_lib:failfun/2}]),
+ Result = (catch ssh_sftp:start_channel(Host, ?SFPD_PORT,
+ [{user, ?USER},
+ {password, ?PASSWD},
+ {user_interaction, false},
+ {silently_accept_hosts, true}])),
+ {ok, ChannelPid, Connection} = Result,
+ {ChannelPid, Connection}
+ end,
+
+ [{sftp, Sftp}, {watchdog, Dog} | TmpConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_Case, Config) ->
+ {Sftp, Connection} = ?config(sftp, Config),
+ ssh_sftp:stop_channel(Sftp),
+ ssh:close(Connection),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all() ->
+ [open_close_file, open_close_dir, read_file, read_dir,
+ write_file, rename_file, mk_rm_dir, remove_file, links,
+ retrieve_attributes, set_attributes, async_read,
+ async_write, position, pos_read, pos_write].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+open_close_file(doc) ->
+ ["Test API functions open/3 and close/2"];
+open_close_file(suite) ->
+ [];
+open_close_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ ok = open_close_file(Sftp, FileName, [read]),
+ ok = open_close_file(Sftp, FileName, [write]),
+ ok = open_close_file(Sftp, FileName, [write, creat]),
+ ok = open_close_file(Sftp, FileName, [write, trunc]),
+ ok = open_close_file(Sftp, FileName, [append]),
+ ok = open_close_file(Sftp, FileName, [read, binary]),
+
+ ok.
+
+open_close_file(Server, File, Mode) ->
+ {ok, Handle} = ssh_sftp:open(Server, File, Mode),
+ ok = ssh_sftp:close(Server, Handle),
+ ok.
+
+
+%%--------------------------------------------------------------------
+open_close_dir(doc) ->
+ ["Test API functions opendir/2 and close/2"];
+open_close_dir(suite) ->
+ [];
+open_close_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Sftp, _} = ?config(sftp, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+
+ {ok, Handle} = ssh_sftp:opendir(Sftp, PrivDir),
+ ok = ssh_sftp:close(Sftp, Handle),
+ {error, _} = ssh_sftp:opendir(Sftp, FileName),
+
+ ok.
+%%--------------------------------------------------------------------
+read_file(doc) ->
+ ["Test API funtion read_file/2"];
+read_file(suite) ->
+ [];
+read_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Data} = ssh_sftp:read_file(Sftp, FileName),
+
+ {ok, Data} = file:read_file(FileName),
+
+ ok.
+%%--------------------------------------------------------------------
+read_dir(doc) ->
+ ["Test API function list_dir/2"];
+read_dir(suite) ->
+ [];
+read_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Sftp, _} = ?config(sftp, Config),
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+ test_server:format("sftp list dir: ~p~n", [Files]),
+ ok.
+
+%%--------------------------------------------------------------------
+write_file(doc) ->
+ ["Test API function write_file/2"];
+write_file(suite) ->
+ [];
+write_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ Data = list_to_binary("Hej hopp!"),
+
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ {ok, Data} = file:read_file(FileName),
+
+ ok.
+
+%%--------------------------------------------------------------------
+remove_file(doc) ->
+ ["Test API function delete/2"];
+remove_file(suite) ->
+ [];
+remove_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+
+ true = lists:member(filename:basename(FileName), Files),
+
+ ok = ssh_sftp:delete(Sftp, FileName),
+
+ {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
+
+ false = lists:member(filename:basename(FileName), NewFiles),
+
+ {error, _} = ssh_sftp:delete(Sftp, FileName),
+
+ ok.
+
+%%--------------------------------------------------------------------
+rename_file(doc) ->
+ ["Test API function rename_file/2"];
+rename_file(suite) ->
+ [];
+rename_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+ NewFileName = filename:join(PrivDir, "test.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+
+ test_server:format("FileName: ~p, Files: ~p~n", [FileName, Files]),
+
+ true = lists:member(filename:basename(FileName), Files),
+ false = lists:member(filename:basename(NewFileName), Files),
+
+ ok = ssh_sftp:rename(Sftp, FileName, NewFileName),
+
+ {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
+
+ test_server:format("FileName: ~p, Files: ~p~n", [FileName, NewFiles]),
+
+ false = lists:member(filename:basename(FileName), NewFiles),
+ true = lists:member(filename:basename(NewFileName), NewFiles),
+
+ ok.
+
+%%--------------------------------------------------------------------
+mk_rm_dir(doc) ->
+ ["Test API functions make_dir/2, del_dir/2"];
+mk_rm_dir(suite) ->
+ [];
+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),
+
+ NewDirName = filename:join(PrivDir, "foo/bar"),
+
+ {error, _} = ssh_sftp:make_dir(Sftp, NewDirName),
+ {error, _} = ssh_sftp:del_dir(Sftp, PrivDir),
+
+ ok.
+
+%%--------------------------------------------------------------------
+links(doc) ->
+ ["Tests API function make_symlink/3"];
+links(suite) ->
+ [];
+links(Config) when is_list(Config) ->
+ case test_server:os_type() of
+ {win32, _} ->
+ {skip, "Links are not fully supported by windows"};
+ _ ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+ LinkFileName = filename:join(PrivDir, "link_test.txt"),
+
+ ok = ssh_sftp:make_symlink(Sftp, FileName, LinkFileName),
+ {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName),
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+retrieve_attributes(doc) ->
+ ["Test API function read_file_info/3"];
+retrieve_attributes(suite) ->
+ [];
+retrieve_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, FileInfo} = ssh_sftp:read_file_info(Sftp, FileName),
+
+ {ok, NewFileInfo} = file:read_file_info(FileName),
+
+ %% TODO comparison. There are some differences now is that ok?
+ test_server:format("SFTP: ~p FILE: ~p~n", [FileInfo, NewFileInfo]),
+ ok.
+
+%%--------------------------------------------------------------------
+set_attributes(doc) ->
+ ["Test API function write_file_info/3"];
+set_attributes(suite) ->
+ [];
+set_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok,Fd} = file:open(FileName, write),
+ io:put_chars(Fd,"foo"),
+
+ ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
+ {error, eacces} = file:write_file(FileName, "hello again"),
+ ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
+ ok = file:write_file(FileName, "hello again"),
+
+ ok.
+
+%%--------------------------------------------------------------------
+
+async_read(doc) ->
+ ["Test API aread/3"];
+async_read(suite) ->
+ [];
+async_read(Config) when is_list(Config) ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "sftp.txt"),
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
+ {async, Ref} = ssh_sftp:aread(Sftp, Handle, 20),
+
+ receive
+ {async_reply, Ref, {ok, Data}} ->
+ test_server:format("Data: ~p~n", [Data]),
+ ok;
+ Msg ->
+ test_server:fail(Msg)
+ end,
+ ok.
+%%--------------------------------------------------------------------
+async_write(doc) ->
+ ["Test API awrite/3"];
+async_write(suite) ->
+ [];
+async_write(Config) when is_list(Config) ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
+ Data = list_to_binary("foobar"),
+ {async, Ref} = ssh_sftp:awrite(Sftp, Handle, Data),
+
+ receive
+ {async_reply, Ref, ok} ->
+ {ok, Data} = file:read_file(FileName);
+ Msg ->
+ test_server:fail(Msg)
+ end,
+ ok.
+
+%%--------------------------------------------------------------------
+
+position(doc) ->
+ ["Test API functions position/3"];
+position(suite) ->
+ [];
+position(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {Sftp, _} = ?config(sftp, Config),
+
+ Data = list_to_binary("1234567890"),
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
+
+ {ok, 3} = ssh_sftp:position(Sftp, Handle, {bof, 3}),
+ {ok, "4"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 10} = ssh_sftp:position(Sftp, Handle, eof),
+ eof = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 6} = ssh_sftp:position(Sftp, Handle, {bof, 6}),
+ {ok, "7"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 9} = ssh_sftp:position(Sftp, Handle, {cur, 2}),
+ {ok, "0"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 0} = ssh_sftp:position(Sftp, Handle, bof),
+ {ok, "1"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 1} = ssh_sftp:position(Sftp, Handle, cur),
+ {ok, "2"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ ok.
+
+%%--------------------------------------------------------------------
+pos_read(doc) ->
+ ["Test API functions pread/3 and apread/3"];
+pos_read(suite) ->
+ [];
+pos_read(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {Sftp, _} = ?config(sftp, Config),
+ Data = list_to_binary("Hej hopp!"),
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
+
+ {async, Ref} = ssh_sftp:apread(Sftp, Handle, {bof, 5}, 4),
+
+ NewData = "opp!",
+
+ receive
+ {async_reply, Ref, {ok, NewData}} ->
+ ok;
+ Msg ->
+ test_server:fail(Msg)
+ end,
+
+ NewData1 = "hopp",
+
+ {ok, NewData1} = ssh_sftp:pread(Sftp, Handle, {bof, 4}, 4),
+
+ ok.
+%%--------------------------------------------------------------------
+pos_write(doc) ->
+ ["Test API functions pwrite/4 and apwrite/4"];
+pos_write(suite) ->
+ [];
+pos_write(Config) when is_list(Config) ->
+
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
+
+ Data = list_to_binary("Bye,"),
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ NewData = list_to_binary(" see you tomorrow"),
+ {async, Ref} = ssh_sftp:apwrite(Sftp, Handle, {bof, 4}, NewData),
+ receive
+ {async_reply, Ref, ok} ->
+ ok;
+ Msg ->
+ test_server:fail(Msg)
+ end,
+
+ ok = ssh_sftp:pwrite(Sftp, Handle, eof, list_to_binary("!")),
+
+ NewData1 = list_to_binary("Bye, see you tomorrow!"),
+ {ok, NewData1} = ssh_sftp:read_file(Sftp, FileName),
+
+ ok.
+
+%% Internal functions
+%%--------------------------------------------------------------------
+prep(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ TestFile = filename:join(PrivDir, "sftp.txt"),
+ TestFile1 = filename:join(PrivDir, "test.txt"),
+ TestLink = filename:join(PrivDir, "link_test.txt"),
+
+ file:delete(TestFile),
+ file:delete(TestFile1),
+ file:delete(TestLink),
+
+ %% Initial config
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join(DataDir, "sftp.txt"),
+ file:copy(FileName, TestFile),
+ Mode = 8#00400 bor 8#00200 bor 8#00040, % read & write owner, read group
+ {ok, FileInfo} = file:read_file_info(TestFile),
+ ok = file:write_file_info(TestFile,
+ FileInfo#file_info{mode = Mode}).
diff --git a/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa b/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa
new file mode 100644
index 0000000000..7e3f885f5d
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDLKYTdRnGzphcN+pF8UuI3sYB7rxZUHbOT87K3vh8XOLkDOsS3
+8VREtNS8Wb3uYXsRtyDoUvrLIDnyllOfJSDupWLr4ibckUZd/nhFAaC6WryVmH6k
+GlQLLp9KU+vcn2DwYeo14gbwHYDB3pmv4CWAlnO1m/BkX4aLz1zC314OkQIBIwKB
+gD/Z2UzboBPjvhpWEHeHw3CW3zzQoJ4X9pw2peH57IOkHOPCA0/A3/hWFvleCH4e
+owWRU3w3ViKVGYbBh/7RJ5rllN+ENUmVn536srJTxLKUtvb5jRGj3W6EWgAGHSUB
+hm83Kt9Lb5hprL7dPrNGvSseBm/LQSfBQ4vUUyiVRKGPAkEA/rPxWoLdBBP+FZtE
+fGzz9izPM6Fe6o8ZGNZIlRBProOhgEvvIqdgzQWObgLVVrw+M/YApPpiYS3PEmWj
+b2b+jwJBAMwyYeL6coKTl8swDu8HvLnshgUFJFTtHhOTXsKtXQNI1b24xhUrB3Sb
+X8fmoByyRNRpOfvg4Jdqi3Z6KfIcsN8CQQDEfC83McBw3DkJWoVKCugVrYnmACSm
+USH9N5cT6AL0VupNB2C0VTwL37cEaJXyc/V4ipLIaWHV8CNl9qKmZWVJAkEAurG4
+lQI8zyfbPW3EgsU+1d+QeZ5NGnJkpC73jWtNudwxIn0M4CdXRgpmMxwAGjyWs5No
+Nr75OfsDKn5SPHIAywJAKrtONlOizgDiG3EvAXZlwFtOb+HkQ7lrFwczrQu9m7yi
+brSAcnTrLKI6CrR33b/QJLvb9C/HTEZojFABGq8M7A==
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub b/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub
new file mode 100644
index 0000000000..77f57de4af
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyymE3UZxs6YXDfqRfFLiN7GAe68WVB2zk/Oyt74fFzi5AzrEt/FURLTUvFm97mF7Ebcg6FL6yyA58pZTnyUg7qVi6+Im3JFGXf54RQGgulq8lZh+pBpUCy6fSlPr3J9g8GHqNeIG8B2Awd6Zr+AlgJZztZvwZF+Gi89cwt9eDpE= jakob@balin
diff --git a/lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt b/lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt
new file mode 100644
index 0000000000..2a878ae255
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt
@@ -0,0 +1,252 @@
+There are 5 KeySyms per KeyCode; KeyCodes range from 8 to 254.
+
+ KeyCode Keysym (Keysym) ...
+ Value Value (Name) ...
+
+ 8
+ 9
+ 10
+ 11 0x0041 (A)
+ 12 0x0042 (B)
+ 13 0x0043 (C)
+ 14 0x0044 (D)
+ 15 0x0065 (e) 0x0045 (E) 0x20ac (EuroSign)
+ 16 0x0046 (F)
+ 17 0x0047 (G)
+ 18 0x0048 (H)
+ 19 0x0049 (I)
+ 20 0x004a (J)
+ 21 0x004b (K)
+ 22 0x004c (L)
+ 23 0x004d (M)
+ 24 0x004e (N)
+ 25 0x004f (O)
+ 26 0x0050 (P)
+ 27 0x0051 (Q)
+ 28 0x0052 (R)
+ 29 0x0053 (S)
+ 30 0x0054 (T)
+ 31 0x0055 (U)
+ 32 0x0056 (V)
+ 33 0x0057 (W)
+ 34 0x0058 (X)
+ 35 0x0059 (Y)
+ 36 0x005a (Z)
+ 37 0x0031 (1) 0x0021 (exclam)
+ 38 0x0032 (2) 0x0022 (quotedbl) 0x0040 (at)
+ 39 0x0033 (3) 0x0023 (numbersign) 0x00a3 (sterling)
+ 40 0x0034 (4) 0x00a4 (currency) 0x0024 (dollar)
+ 41 0x0035 (5) 0x0025 (percent)
+ 42 0x0036 (6) 0x0026 (ampersand)
+ 43 0x0037 (7) 0x002f (slash) 0x007b (braceleft)
+ 44 0x0038 (8) 0x0028 (parenleft) 0x005b (bracketleft)
+ 45 0x0039 (9) 0x0029 (parenright) 0x005d (bracketright)
+ 46 0x0030 (0) 0x003d (equal) 0x007d (braceright)
+ 47 0xff0d (Return)
+ 48 0xff1b (Escape)
+ 49 0xff08 (BackSpace)
+ 50 0xff09 (Tab)
+ 51 0x0020 (space)
+ 52 0x002b (plus) 0x003f (question) 0x005c (backslash)
+ 53 0x1005ff03 (SunFA_Acute) 0x1005ff00 (SunFA_Grave)
+ 54 0x00c5 (Aring)
+ 55 0x1005ff04 (SunFA_Diaeresis) 0x005e (asciicircum) 0x007e (asciitilde)
+ 56
+ 57 0x0027 (apostrophe) 0x002a (asterisk) 0x0060 (grave)
+ 58 0x00d6 (Odiaeresis)
+ 59 0x00c4 (Adiaeresis)
+ 60 0x00a7 (section) 0x00bd (onehalf)
+ 61 0x002c (comma) 0x003b (semicolon)
+ 62 0x002e (period) 0x003a (colon)
+ 63 0x002d (minus) 0x005f (underscore)
+ 64 0xffe5 (Caps_Lock)
+ 65 0xffbe (F1)
+ 66 0xffbf (F2)
+ 67 0xffc0 (F3)
+ 68 0xffc1 (F4)
+ 69 0xffc2 (F5)
+ 70 0xffc3 (F6)
+ 71 0xffc4 (F7)
+ 72 0xffc5 (F8)
+ 73 0xffc6 (F9)
+ 74 0xffc7 (F10)
+ 75 0x1005ff10 (SunF36)
+ 76 0x1005ff11 (SunF37)
+ 77 0xffd3 (F22) 0xffd3 (F22) 0xff61 (Print) 0x1005ff60 (SunSys_Req)
+ 78 0xffd4 (F23) 0xffd4 (F23) 0xff14 (Scroll_Lock)
+ 79 0xffd2 (F21) 0xffd2 (F21) 0xff13 (Pause) 0xff6b (Break)
+ 80 0xff63 (Insert)
+ 81 0xff50 (Home)
+ 82 0xff55 (Prior)
+ 83 0xffff (Delete)
+ 84 0xff57 (End)
+ 85 0xff56 (Next)
+ 86 0xff53 (Right)
+ 87 0xff51 (Left)
+ 88 0xff54 (Down)
+ 89 0xff52 (Up)
+ 90 0xff7f (Num_Lock)
+ 91 0xffd6 (F25) 0xffd6 (F25) 0xffaf (KP_Divide)
+ 92 0xffd7 (F26) 0xffd7 (F26) 0xffaa (KP_Multiply)
+ 93 0xffd5 (F24) 0xffd5 (F24) 0xffad (KP_Subtract)
+ 94 0xffab (KP_Add)
+ 95 0xff8d (KP_Enter)
+ 96 0xffde (F33) 0xffde (F33) 0xffb1 (KP_1) 0xff57 (End)
+ 97 0xff54 (Down) 0xffdf (F34) 0xffb2 (KP_2)
+ 98 0xffe0 (F35) 0xffe0 (F35) 0xffb3 (KP_3) 0xff56 (Next)
+ 99 0xff51 (Left) 0xffdb (F30) 0xffb4 (KP_4)
+ 100 0xffdc (F31) 0xffdc (F31) 0xffb5 (KP_5)
+ 101 0xff53 (Right) 0xffdd (F32) 0xffb6 (KP_6)
+ 102 0xffd8 (F27) 0xffd8 (F27) 0xffb7 (KP_7) 0xff50 (Home)
+ 103 0xff52 (Up) 0xffd9 (F28) 0xffb8 (KP_8)
+ 104 0xffda (F29) 0xffda (F29) 0xffb9 (KP_9) 0xff55 (Prior)
+ 105 0xff9e (KP_Insert) 0xff9e (KP_Insert) 0xffb0 (KP_0)
+ 106 0xffff (Delete) 0xffff (Delete) 0xffac (KP_Separator)
+ 107 0x003c (less) 0x003e (greater) 0x007c (bar)
+ 108 0xff20 (Multi_key)
+ 109 0x1005ff76 (SunPowerSwitch) 0x1005ff7d (SunPowerSwitchShift)
+ 110
+ 111
+ 112
+ 113
+ 114
+ 115
+ 116
+ 117
+ 118
+ 119
+ 120
+ 121
+ 122
+ 123 0xffce (F17) 0xffce (F17) 0x1005ff73 (SunOpen)
+ 124 0xff6a (Help)
+ 125 0xffca (F13) 0xffca (F13) 0x1005ff70 (SunProps)
+ 126 0xffcc (F15) 0xffcc (F15) 0x1005ff71 (SunFront)
+ 127 0xffc8 (F11) 0xffc8 (F11) 0xff69 (Cancel)
+ 128 0xffc9 (F12) 0xffc9 (F12) 0xff66 (Redo)
+ 129 0xffcb (F14) 0xffcb (F14) 0xff65 (Undo)
+ 130 0xffd1 (F20) 0xffd1 (F20) 0x1005ff75 (SunCut)
+ 131 0xffcd (F16) 0xffcd (F16) 0x1005ff72 (SunCopy)
+ 132 0xffcf (F18) 0xffcf (F18) 0x1005ff74 (SunPaste)
+ 133 0xffd0 (F19) 0xffd0 (F19) 0xff68 (Find)
+ 134 0x1005ff78 (SunAudioMute) 0x1005ff7a (SunVideoDegauss)
+ 135 0x1005ff79 (SunAudioRaiseVolume) 0x1005ff7c (SunVideoRaiseBrightness)
+ 136 0x1005ff77 (SunAudioLowerVolume) 0x1005ff7b (SunVideoLowerBrightness)
+ 137
+ 138
+ 139
+ 140
+ 141
+ 142
+ 143
+ 144
+ 145
+ 146
+ 147
+ 148
+ 149
+ 150
+ 151
+ 152
+ 153
+ 154
+ 155
+ 156
+ 157
+ 158
+ 159
+ 160
+ 161
+ 162
+ 163
+ 164
+ 165
+ 166
+ 167
+ 168
+ 169
+ 170
+ 171
+ 172
+ 173
+ 174
+ 175
+ 176
+ 177
+ 178
+ 179
+ 180
+ 181
+ 182
+ 183
+ 184
+ 185
+ 186
+ 187
+ 188
+ 189
+ 190
+ 191
+ 192
+ 193
+ 194
+ 195
+ 196
+ 197
+ 198
+ 199
+ 200
+ 201
+ 202
+ 203
+ 204
+ 205
+ 206
+ 207
+ 208
+ 209
+ 210
+ 211
+ 212
+ 213
+ 214
+ 215
+ 216
+ 217
+ 218
+ 219
+ 220
+ 221
+ 222
+ 223
+ 224
+ 225
+ 226
+ 227
+ 228
+ 229
+ 230
+ 231 0xffe3 (Control_L)
+ 232 0xffe1 (Shift_L)
+ 233 0xffe9 (Alt_L)
+ 234 0xffe7 (Meta_L)
+ 235
+ 236 0xffe2 (Shift_R)
+ 237 0xff7e (Mode_switch)
+ 238 0xffe8 (Meta_R)
+ 239
+ 240
+ 241
+ 242
+ 243
+ 244
+ 245
+ 246
+ 247
+ 248
+ 249
+ 250
+ 251
+ 252
+ 253
+ 254
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
new file mode 100644
index 0000000000..bfe54a3e75
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -0,0 +1,934 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssh_sftpd_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+-include("ssh_xfer.hrl").
+-include("ssh.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+-define(SFPD_PORT, 9999).
+-define(USER, "Alladin").
+-define(PASSWD, "Sesame").
+-define(XFER_PACKET_SIZE, 32768).
+-define(XFER_WINDOW_SIZE, 4*?XFER_PACKET_SIZE).
+-define(TIMEOUT, 10000).
+-define(REG_ATTERS, <<0,0,0,0,1>>).
+-define(UNIX_EPOCH, 62167219200).
+
+-define(is_set(F, Bits),
+ ((F) band (Bits)) == (F)).
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case {catch ssh:stop(),catch crypto:start()} of
+ {ok,ok} ->
+ ssh_test_lib:make_dsa_files(Config),
+ Config;
+ {ok,_} ->
+ {skip,"Could not start ssh!"};
+ {_,ok} ->
+ {skip,"Could not start crypto!"};
+ {_,_} ->
+ {skip,"Could not start crypto and ssh!"}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ crypto:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(TestCase, Config) ->
+ ssh:start(),
+ prep(Config),
+ SysDir = ?config(data_dir, Config),
+ {ok, Sftpd} =
+ ssh_sftpd:listen(?SFPD_PORT, [{system_dir, SysDir},
+ {user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end}]),
+
+ Cm = ssh_test_lib:connect(?SFPD_PORT,
+ [{system_dir, SysDir},
+ {user_dir, SysDir},
+ {user, ?USER}, {password, ?PASSWD},
+ {user_interaction, false},
+ {silently_accept_hosts, true},
+ {pwdfun, fun(_,_) -> true end}]),
+ {ok, Channel} =
+ ssh_connection:session_channel(Cm, ?XFER_WINDOW_SIZE,
+ ?XFER_PACKET_SIZE, ?TIMEOUT),
+
+ success = ssh_connection:subsystem(Cm, Channel, "sftp", ?TIMEOUT),
+
+ ProtocolVer = case atom_to_list(TestCase) of
+ "ver3_" ++ _ ->
+ 3;
+ _ ->
+ ?SSH_SFTP_PROTOCOL_VERSION
+ end,
+
+ Data = <<?UINT32(ProtocolVer)>> ,
+
+ Size = 1 + size(Data),
+
+ ssh_connection:send(Cm, Channel, << ?UINT32(Size),
+ ?SSH_FXP_INIT, Data/binary >>),
+
+ {ok, <<?SSH_FXP_VERSION, ?UINT32(Version), _Ext/binary>>, _}
+ = reply(Cm, Channel),
+
+ test_server:format("Client: ~p Server ~p~n", [ProtocolVer, Version]),
+
+ [{sftp, {Cm, Channel}}, {sftpd, Sftpd }| Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ ssh_sftpd:stop(?config(sftpd, Config)),
+ {Cm, Channel} = ?config(sftp, Config),
+ ssh_connection:close(Cm, Channel),
+ ssh:close(Cm),
+ ssh:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all() ->
+ [open_close_file, open_close_dir, read_file, read_dir,
+ write_file, rename_file, mk_rm_dir, remove_file,
+ real_path, retrieve_attributes, set_attributes, links,
+ ver3_rename_OTP_6352, seq10670, sshd_read_file].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+open_close_file(doc) ->
+ ["Test SSH_FXP_OPEN and SSH_FXP_CLOSE commands"];
+open_close_file(suite) ->
+ [];
+open_close_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ {Cm, Channel} = ?config(sftp, Config),
+ ReqId = 0,
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} = close(Handle, ReqId,
+ Cm, Channel),
+ NewReqId = ReqId + 1,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_INVALID_HANDLE), _/binary>>, _} =
+ close(Handle, ReqId, Cm, Channel),
+
+ NewReqId1 = NewReqId + 1,
+ %% {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), % Ver 6 we have 5
+ %% ?UINT32(?SSH_FX_FILE_IS_A_DIRECTORY), _/binary>>, _} =
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
+ ?UINT32(?SSH_FX_FAILURE), _/binary>>, _} =
+ open_file(PrivDir, Cm, Channel, NewReqId1,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ ok.
+
+%%--------------------------------------------------------------------
+open_close_dir(doc) ->
+ ["Test SSH_FXP_OPENDIR and SSH_FXP_CLOSE commands"];
+open_close_dir(suite) ->
+ [];
+open_close_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Cm, Channel} = ?config(sftp, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ ReqId = 0,
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_dir(PrivDir, Cm, Channel, ReqId),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} = close(Handle, ReqId,
+ Cm, Channel),
+
+ NewReqId = 1,
+ case open_dir(FileName, Cm, Channel, NewReqId) of
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
+ ?UINT32(?SSH_FX_NOT_A_DIRECTORY), _/binary>>, _} ->
+ %% Only if server is using vsn > 5.
+ ok;
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
+ ?UINT32(?SSH_FX_FAILURE), _/binary>>, _} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+read_file(doc) ->
+ ["Test SSH_FXP_READ command"];
+read_file(suite) ->
+ [];
+read_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ NewReqId = 1,
+
+ {ok, <<?SSH_FXP_DATA, ?UINT32(NewReqId), ?UINT32(_Length),
+ Data/binary>>, _} =
+ read_file(Handle, 100, 0, Cm, Channel, NewReqId),
+
+ {ok, Data} = file:read_file(FileName),
+
+ ok.
+%%--------------------------------------------------------------------
+read_dir(doc) ->
+ ["Test SSH_FXP_READDIR command"];
+read_dir(suite) ->
+ [];
+read_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Cm, Channel} = ?config(sftp, Config),
+ ReqId = 0,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_dir(PrivDir, Cm, Channel, ReqId),
+ ok = read_dir(Handle, Cm, Channel, ReqId),
+ ok.
+
+%%--------------------------------------------------------------------
+write_file(doc) ->
+ ["Test SSH_FXP_WRITE command"];
+write_file(suite) ->
+ [];
+write_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_WRITE_DATA bor ?ACE4_WRITE_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ NewReqId = 1,
+ Data = list_to_binary("Write file test"),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), ?UINT32(?SSH_FX_OK),
+ _/binary>>, _}
+ = write_file(Handle, Data, 0, Cm, Channel, NewReqId),
+
+ {ok, Data} = file:read_file(FileName),
+
+ ok.
+
+%%--------------------------------------------------------------------
+remove_file(doc) ->
+ ["Test SSH_FXP_REMOVE command"];
+remove_file(suite) ->
+ [];
+remove_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ remove(FileName, Cm, Channel, ReqId),
+
+ NewReqId = 1,
+ %% {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), % ver 6 we have 5
+ %% ?UINT32(?SSH_FX_FILE_IS_A_DIRECTORY ), _/binary>>, _} =
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
+ ?UINT32(?SSH_FX_FAILURE), _/binary>>, _} =
+ remove(PrivDir, Cm, Channel, NewReqId),
+
+ ok.
+
+%%--------------------------------------------------------------------
+rename_file(doc) ->
+ ["Test SSH_FXP_RENAME command"];
+rename_file(suite) ->
+ [];
+rename_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ NewFileName = filename:join(PrivDir, "test1.txt"),
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ rename(FileName, NewFileName, Cm, Channel, ReqId, 6, 0),
+
+ NewReqId = ReqId + 1,
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ rename(NewFileName, FileName, Cm, Channel, NewReqId, 6,
+ ?SSH_FXP_RENAME_OVERWRITE),
+
+ NewReqId1 = NewReqId + 1,
+ file:copy(FileName, NewFileName),
+
+ %% No owerwrite
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
+ ?UINT32(?SSH_FX_FILE_ALREADY_EXISTS), _/binary>>, _} =
+ rename(FileName, NewFileName, Cm, Channel, NewReqId1, 6,
+ ?SSH_FXP_RENAME_NATIVE),
+
+ NewReqId2 = NewReqId1 + 1,
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2),
+ ?UINT32(?SSH_FX_OP_UNSUPPORTED), _/binary>>, _} =
+ rename(FileName, NewFileName, Cm, Channel, NewReqId2, 6,
+ ?SSH_FXP_RENAME_ATOMIC),
+
+ ok.
+
+%%--------------------------------------------------------------------
+mk_rm_dir(doc) ->
+ ["Test SSH_FXP_MKDIR and SSH_FXP_RMDIR command"];
+mk_rm_dir(suite) ->
+ [];
+mk_rm_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Cm, Channel} = ?config(sftp, Config),
+ DirName = filename:join(PrivDir, "test"),
+ ReqId = 0,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(?SSH_FX_OK),
+ _/binary>>, _} = mkdir(DirName, Cm, Channel, ReqId),
+
+ NewReqId = 1,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), ?UINT32(?SSH_FX_FAILURE),
+ _/binary>>, _} = mkdir(DirName, Cm, Channel, NewReqId),
+
+ NewReqId1 = 2,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1), ?UINT32(?SSH_FX_OK),
+ _/binary>>, _} = rmdir(DirName, Cm, Channel, NewReqId1),
+
+ NewReqId2 = 3,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2), ?UINT32(?SSH_FX_NO_SUCH_FILE),
+ _/binary>>, _} = rmdir(DirName, Cm, Channel, NewReqId2),
+
+ ok.
+%%--------------------------------------------------------------------
+real_path(doc) ->
+ ["Test SSH_FXP_REALPATH command"];
+real_path(suite) ->
+ [];
+real_path(Config) when is_list(Config) ->
+ case test_server:os_type() of
+ {win32, _} ->
+ {skip, "Not a relevant test on windows"};
+ _ ->
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ TestDir = filename:join(PrivDir, "ssh_test"),
+ ok = file:make_dir(TestDir),
+
+ OrigPath = filename:join(TestDir, ".."),
+
+ {ok, <<?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(_), ?UINT32(Len),
+ Path:Len/binary, _/binary>>, _}
+ = real_path(OrigPath, Cm, Channel, ReqId),
+
+ RealPath = filename:absname(binary_to_list(Path)),
+ AbsPrivDir = filename:absname(PrivDir),
+
+ test_server:format("Path: ~p PrivDir: ~p~n", [RealPath, AbsPrivDir]),
+
+ true = RealPath == AbsPrivDir,
+
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+links(doc) ->
+ [];
+links(suite) ->
+ [];
+links(Config) when is_list(Config) ->
+ case test_server:os_type() of
+ {win32, _} ->
+ {skip, "Links are not fully supported by windows"};
+ _ ->
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ LinkFileName = filename:join(PrivDir, "link_test.txt"),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ create_link(LinkFileName, FileName, Cm, Channel, ReqId),
+
+ NewReqId = 1,
+ {ok, <<?SSH_FXP_NAME, ?UINT32(NewReqId), ?UINT32(_), ?UINT32(Len),
+ Path:Len/binary, _/binary>>, _}
+ = read_link(LinkFileName, Cm, Channel, NewReqId),
+
+
+ true = binary_to_list(Path) == FileName,
+
+ test_server:format("Path: ~p~n", [binary_to_list(Path)]),
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+retrieve_attributes(doc) ->
+ ["Test SSH_FXP_STAT, SSH_FXP_LSTAT AND SSH_FXP_FSTAT commands"];
+retrieve_attributes(suite) ->
+ [];
+retrieve_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, FileInfo} = file:read_file_info(FileName),
+
+ AttrValues =
+ retrive_attributes(FileName, Cm, Channel, ReqId),
+
+ Type = encode_file_type(FileInfo#file_info.type),
+ Size = FileInfo#file_info.size,
+ Owner = FileInfo#file_info.uid,
+ Group = FileInfo#file_info.gid,
+ Permissions = FileInfo#file_info.mode,
+ Atime = calendar:datetime_to_gregorian_seconds(
+ erlang:localtime_to_universaltime(FileInfo#file_info.atime))
+ - ?UNIX_EPOCH,
+ Mtime = calendar:datetime_to_gregorian_seconds(
+ erlang:localtime_to_universaltime(FileInfo#file_info.mtime))
+ - ?UNIX_EPOCH,
+ Ctime = calendar:datetime_to_gregorian_seconds(
+ erlang:localtime_to_universaltime(FileInfo#file_info.ctime))
+ - ?UNIX_EPOCH,
+
+ lists:foreach(fun(Value) ->
+ <<?UINT32(Flags), _/binary>> = Value,
+ true = ?is_set(?SSH_FILEXFER_ATTR_SIZE,
+ Flags),
+ true = ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,
+ Flags),
+ true = ?is_set(?SSH_FILEXFER_ATTR_ACCESSTIME,
+ Flags),
+ true = ?is_set(?SSH_FILEXFER_ATTR_CREATETIME,
+ Flags),
+ true = ?is_set(?SSH_FILEXFER_ATTR_MODIFYTIME,
+ Flags),
+ true = ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP,
+ Flags),
+ false = ?is_set(?SSH_FILEXFER_ATTR_ACL,
+ Flags),
+ false = ?is_set(?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,
+ Flags),
+ false = ?is_set(?SSH_FILEXFER_ATTR_BITS,
+ Flags),
+ false = ?is_set(?SSH_FILEXFER_ATTR_EXTENDED,
+ Flags),
+
+ <<?UINT32(_Flags), ?BYTE(Type),
+ ?UINT64(Size),
+ ?UINT32(OwnerLen), BinOwner:OwnerLen/binary,
+ ?UINT32(GroupLen), BinGroup:GroupLen/binary,
+ ?UINT32(Permissions),
+ ?UINT64(Atime),
+ ?UINT64(Ctime),
+ ?UINT64(Mtime)>> = Value,
+
+ Owner = list_to_integer(binary_to_list(BinOwner)),
+ Group = list_to_integer(binary_to_list(BinGroup))
+ end, AttrValues),
+
+ ok.
+%%--------------------------------------------------------------------
+set_attributes(doc) ->
+ ["Test SSH_FXP_SETSTAT AND SSH_FXP_FSETSTAT commands"];
+set_attributes(suite) ->
+ [];
+set_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, FileInfo} = file:read_file_info(FileName),
+
+ OrigPermissions = FileInfo#file_info.mode,
+ Permissions = 8#400, %% User read-only
+
+ Flags = ?SSH_FILEXFER_ATTR_PERMISSIONS,
+
+ Atters = [?uint32(Flags), ?byte(?SSH_FILEXFER_TYPE_REGULAR),
+ ?uint32(Permissions)],
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ set_attributes_file(FileName, Atters, Cm, Channel, ReqId),
+
+ {ok, NewFileInfo} = file:read_file_info(FileName),
+ NewPermissions = NewFileInfo#file_info.mode,
+
+ %% Can not test that NewPermissions = Permissions as
+ %% on Unix platforms, other bits than those listed in the
+ %% API may be set.
+ test_server:format("Org: ~p New: ~p~n", [OrigPermissions, NewPermissions]),
+ true = OrigPermissions =/= NewPermissions,
+
+ test_server:format("Try to open the file"),
+ NewReqId = 2,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(NewReqId), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, NewReqId,
+ ?ACE4_READ_DATA bor ?ACE4_WRITE_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ NewAtters = [?uint32(Flags), ?byte(?SSH_FILEXFER_TYPE_REGULAR),
+ ?uint32(OrigPermissions)],
+
+ NewReqId1 = 3,
+
+ test_server:format("Set original permissions on the now open file"),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ set_attributes_open_file(Handle, NewAtters, Cm, Channel, NewReqId1),
+
+ {ok, NewFileInfo1} = file:read_file_info(FileName),
+ OrigPermissions = NewFileInfo1#file_info.mode,
+
+ ok.
+
+%%--------------------------------------------------------------------
+ver3_rename_OTP_6352(doc) ->
+ ["Test that ver3 rename message is handled"];
+
+ver3_rename_OTP_6352(suite) ->
+ [];
+
+ver3_rename_OTP_6352(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+ NewFileName = filename:join(PrivDir, "test1.txt"),
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_OK), _/binary>>, _} =
+ rename(FileName, NewFileName, Cm, Channel, ReqId, 3, 0),
+
+ ok.
+
+%%--------------------------------------------------------------------
+seq10670(doc) ->
+ ["Check that realpath works ok"];
+
+seq10670(suite) ->
+ [];
+
+seq10670(Config) when is_list(Config) ->
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ case test_server:os_type() of
+ {win32, _} ->
+ {skip, "Not a relevant test on windows"};
+ _ ->
+ {ok, <<?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(_), ?UINT32(Len),
+ Root:Len/binary, _/binary>>, _}
+ = real_path("/..", Cm, Channel, ReqId),
+
+ <<"/">> = Root,
+
+ {ok, <<?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(_), ?UINT32(Len),
+ Path:Len/binary, _/binary>>, _}
+ = real_path("/usr/bin/../..", Cm, Channel, ReqId),
+
+ Root = Path
+ end.
+
+%% Internal functions
+%%--------------------------------------------------------------------
+prep(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ TestFile = filename:join(PrivDir, "test.txt"),
+ TestFile1 = filename:join(PrivDir, "test1.txt"),
+
+ file:delete(TestFile),
+ file:delete(TestFile1),
+
+ %% Initial config
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join(DataDir, "test.txt"),
+ file:copy(FileName, TestFile),
+ Mode = 8#00400 bor 8#00200 bor 8#00040, % read & write owner, read group
+ {ok, FileInfo} = file:read_file_info(TestFile),
+ ok = file:write_file_info(TestFile,
+ FileInfo#file_info{mode = Mode}).
+
+reply(Cm, Channel) ->
+ reply(Cm, Channel,<<>>).
+
+reply(Cm, Channel, RBuf) ->
+ receive
+ {ssh_cm, Cm, {data, Channel, 0, Data}} ->
+ case <<RBuf/binary, Data/binary>> of
+ <<?UINT32(Len),Reply:Len/binary,Rest/binary>> ->
+ {ok, Reply, Rest};
+ RBuf2 ->
+ reply(Cm, Channel, RBuf2)
+ end;
+ {ssh_cm, Cm, {eof, Channel}} ->
+ eof;
+ {ssh_cm, Cm, {closed, Channel}} ->
+ closed;
+ {ssh_cm, Cm, Msg} ->
+ test_server:fail(Msg)
+ end.
+
+
+open_file(File, Cm, Channel, ReqId, Access, Flags) ->
+
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(File)),
+ ?uint32(Access),
+ ?uint32(Flags),
+ ?REG_ATTERS]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_OPEN, Data/binary>>),
+ reply(Cm, Channel).
+
+
+
+close(Handle, ReqId, Cm , Channel) ->
+ Data = list_to_binary([?uint32(ReqId), Handle]),
+
+ Size = 1 + size(Data),
+
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size), ?SSH_FXP_CLOSE,
+ Data/binary>>),
+
+ reply(Cm, Channel).
+
+
+
+open_dir(Dir, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(Dir))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_OPENDIR, Data/binary>>),
+ reply(Cm, Channel).
+
+
+rename(OldName, NewName, Cm, Channel, ReqId, Version, Flags) ->
+ Data =
+ case Version of
+ 3 ->
+ list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(OldName)),
+ ?binary(list_to_binary(NewName))]);
+ _ ->
+ list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(OldName)),
+ ?binary(list_to_binary(NewName)),
+ ?uint32(Flags)])
+ end,
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_RENAME, Data/binary>>),
+ reply(Cm, Channel).
+
+
+mkdir(Dir, Cm, Channel, ReqId)->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(Dir)),
+ ?REG_ATTERS]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_MKDIR, Data/binary>>),
+ reply(Cm, Channel).
+
+
+rmdir(Dir, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(Dir))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_RMDIR, Data/binary>>),
+ reply(Cm, Channel).
+
+remove(File, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(File))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_REMOVE, Data/binary>>),
+ reply(Cm, Channel).
+
+
+read_dir(Handle, Cm, Channel, ReqId) ->
+
+ Data = list_to_binary([?uint32(ReqId), Handle]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_READDIR, Data/binary>>),
+ case reply(Cm, Channel) of
+ {ok, <<?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(Count),
+ ?UINT32(Len), Listing:Len/binary, _/binary>>, _} ->
+ test_server:format("Count: ~p Listing: ~p~n",
+ [Count, binary_to_list(Listing)]),
+ read_dir(Handle, Cm, Channel, ReqId);
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_EOF), _/binary>>, _} ->
+ ok
+ end.
+
+read_file(Handle, MaxLength, OffSet, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId), Handle,
+ ?uint64(OffSet),
+ ?uint32(MaxLength)]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_READ, Data/binary>>),
+ reply(Cm, Channel).
+
+
+write_file(Handle, FileData, OffSet, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId), Handle,
+ ?uint64(OffSet),
+ ?binary(FileData)]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_WRITE, Data/binary>>),
+ reply(Cm, Channel).
+
+
+real_path(OrigPath, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(OrigPath))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_REALPATH, Data/binary>>),
+ reply(Cm, Channel).
+
+create_link(LinkPath, Path, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(LinkPath)),
+ ?binary(list_to_binary(Path))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_SYMLINK, Data/binary>>),
+ reply(Cm, Channel).
+
+
+read_link(Link, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(Link))]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_READLINK, Data/binary>>),
+ reply(Cm, Channel).
+
+retrive_attributes_file(FilePath, Flags, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(FilePath)),
+ ?uint32(Flags)]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_STAT, Data/binary>>),
+ reply(Cm, Channel).
+
+retrive_attributes_file_or_link(FilePath, Flags, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(FilePath)),
+ ?uint32(Flags)]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_LSTAT, Data/binary>>),
+ reply(Cm, Channel).
+
+retrive_attributes_open_file(Handle, Flags, Cm, Channel, ReqId) ->
+
+ Data = list_to_binary([?uint32(ReqId),
+ Handle,
+ ?uint32(Flags)]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_FSTAT, Data/binary>>),
+ reply(Cm, Channel).
+
+retrive_attributes(FileName, Cm, Channel, ReqId) ->
+
+ Attr = ?SSH_FILEXFER_ATTR_SIZE,
+
+ {ok, <<?SSH_FXP_ATTRS, ?UINT32(ReqId), Value/binary>>, _}
+ = retrive_attributes_file(FileName, Attr,
+ Cm, Channel, ReqId),
+
+ NewReqId = ReqId + 1,
+ {ok, <<?SSH_FXP_ATTRS, ?UINT32(NewReqId), Value1/binary>>, _}
+ = retrive_attributes_file_or_link(FileName,
+ Attr, Cm, Channel, NewReqId),
+
+ NewReqId1 = NewReqId + 1,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(NewReqId1), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, NewReqId1,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ NewReqId2 = NewReqId1 + 1,
+ {ok, <<?SSH_FXP_ATTRS, ?UINT32(NewReqId2), Value2/binary>>, _}
+ = retrive_attributes_open_file(Handle, Attr, Cm, Channel, NewReqId2),
+
+ [Value, Value1, Value2].
+
+set_attributes_file(FilePath, Atters, Cm, Channel, ReqId) ->
+ Data = list_to_binary([?uint32(ReqId),
+ ?binary(list_to_binary(FilePath)),
+ Atters]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_SETSTAT, Data/binary>>),
+ reply(Cm, Channel).
+
+
+set_attributes_open_file(Handle, Atters, Cm, Channel, ReqId) ->
+
+ Data = list_to_binary([?uint32(ReqId),
+ Handle,
+ Atters]),
+ Size = 1 + size(Data),
+ ssh_connection:send(Cm, Channel, <<?UINT32(Size),
+ ?SSH_FXP_FSETSTAT, Data/binary>>),
+ reply(Cm, Channel).
+
+
+encode_file_type(Type) ->
+ case Type of
+ regular -> ?SSH_FILEXFER_TYPE_REGULAR;
+ directory -> ?SSH_FILEXFER_TYPE_DIRECTORY;
+ symlink -> ?SSH_FILEXFER_TYPE_SYMLINK;
+ special -> ?SSH_FILEXFER_TYPE_SPECIAL;
+ unknown -> ?SSH_FILEXFER_TYPE_UNKNOWN;
+ other -> ?SSH_FILEXFER_TYPE_UNKNOWN;
+ socket -> ?SSH_FILEXFER_TYPE_SOCKET;
+ char_device -> ?SSH_FILEXFER_TYPE_CHAR_DEVICE;
+ block_device -> ?SSH_FILEXFER_TYPE_BLOCK_DEVICE;
+ fifo -> ?SSH_FILEXFER_TYPE_FIFO;
+ undefined -> ?SSH_FILEXFER_TYPE_UNKNOWN
+ end.
+
+%%--------------------------------------------------------------------
+sshd_read_file(doc) ->
+ ["Test SSH_FXP_READ command, using sshd-server"];
+sshd_read_file(suite) ->
+ [];
+sshd_read_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
+
+ ReqId = 0,
+ {Cm, Channel} = ?config(sftp, Config),
+
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+
+ NewReqId = 1,
+
+ {ok, <<?SSH_FXP_DATA, ?UINT32(NewReqId), ?UINT32(_Length),
+ Data/binary>>, _} =
+ read_file(Handle, 100, 0, Cm, Channel, NewReqId),
+
+ {ok, Data} = file:read_file(FileName),
+
+ ok.
diff --git a/lib/ssh/test/ssh_sftpd_SUITE_data/test.txt b/lib/ssh/test/ssh_sftpd_SUITE_data/test.txt
new file mode 100644
index 0000000000..681bff80a0
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_SUITE_data/test.txt
@@ -0,0 +1 @@
+Sftp test file. \ No newline at end of file
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
new file mode 100644
index 0000000000..2209af05d5
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -0,0 +1,328 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssh_sftpd_erlclient_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+
+-include_lib("kernel/include/file.hrl").
+
+-define(SSHD_PORT, 9999).
+-define(USER, "Alladin").
+-define(PASSWD, "Sesame").
+-define(SSH_MAX_PACKET_SIZE, 32768).
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ catch ssh:stop(),
+ case catch crypto:start() of
+ ok ->
+ DataDir = ?config(data_dir, Config),
+ FileAlt = filename:join(DataDir, "ssh_sftpd_file_alt.erl"),
+ c:c(FileAlt),
+ FileName = filename:join(DataDir, "test.txt"),
+ {ok, FileInfo} = file:read_file_info(FileName),
+ ok = file:write_file_info(FileName,
+ FileInfo#file_info{mode = 8#400}),
+ ssh_test_lib:make_dsa_files(Config),
+ Config;
+ _Else ->
+ {skip,"Could not start ssh!"}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ crypto:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initiation before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(TestCase, Config) ->
+ ssh:start(),
+ DataDir = ?config(data_dir, Config),
+
+ Options =
+ case atom_to_list(TestCase) of
+ "file_cb" ++ _ ->
+ Spec =
+ ssh_sftpd:subsystem_spec([{file_handler,
+ ssh_sftpd_file_alt}]),
+ [{user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end},
+ {system_dir, DataDir},
+ {user_dir, DataDir},
+ {subsystems, [Spec]}];
+ "root_dir" ->
+ Privdir = ?config(priv_dir, Config),
+ Root = filename:join(Privdir, root),
+ file:make_dir(Root),
+ Spec = ssh_sftpd:subsystem_spec([{root,Root}]),
+ [{user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end},
+ {system_dir, DataDir},
+ {user_dir, DataDir},
+ {subsystems, [Spec]}];
+ "list_dir_limited" ->
+ Spec =
+ ssh_sftpd:subsystem_spec([{max_files,1}]),
+ [{user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end},
+ {system_dir, DataDir},
+ {user_dir, DataDir},
+ {subsystems, [Spec]}];
+
+ _ ->
+ [{user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end},
+ {user_dir, DataDir},
+ {system_dir, DataDir}]
+ end,
+
+ {Sftpd, Host, _Port} = ssh_test_lib:daemon(any, ?SSHD_PORT, Options),
+
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host, ?SSHD_PORT,
+ [{silently_accept_hosts, true},
+ {user, ?USER}, {password, ?PASSWD},
+ {pwdfun, fun(_,_) -> true end},
+ {system_dir, DataDir},
+ {user_dir, DataDir},
+ {timeout, 30000}]),
+ TmpConfig = lists:keydelete(sftp, 1, Config),
+ NewConfig = lists:keydelete(sftpd, 1, TmpConfig),
+ [{sftp, {ChannelPid, Connection}}, {sftpd, Sftpd} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ catch ssh_sftpd:stop(?config(sftpd, Config)),
+ {Sftp, Connection} = ?config(sftp, Config),
+ catch ssh_sftp:stop_channel(Sftp),
+ catch ssh:close(Connection),
+ ssh:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all() ->
+ [close_file_OTP_6350, quit_OTP_6349, file_cb_OTP_6356,
+ root_dir, list_dir_limited].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+close_file_OTP_6350(doc) ->
+ ["Test that sftpd closes its fildescriptors after compleating the "
+ "transfer"];
+
+close_file_OTP_6350(suite) ->
+ [];
+
+close_file_OTP_6350(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join(DataDir, "test.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ NumOfPorts = length(erlang:ports()),
+
+ test_server:format("Number of open ports: ~p~n", [NumOfPorts]),
+
+ {ok, <<_/binary>>} = ssh_sftp:read_file(Sftp, FileName),
+
+ NumOfPorts = length(erlang:ports()),
+
+ test_server:format("Number of open ports: ~p~n",
+ [length(erlang:ports())]),
+
+ ok.
+
+%%--------------------------------------------------------------------
+
+quit_OTP_6349(doc) ->
+ [" When the sftp client ends the session the "
+ "server will now behave correctly and not leave the "
+ "client hanging."];
+
+quit_OTP_6349(suite) ->
+ [];
+
+quit_OTP_6349(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join(DataDir, "test.txt"),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, <<_/binary>>} = ssh_sftp:read_file(Sftp, FileName),
+
+ ok = ssh_sftp:stop_channel(Sftp),
+
+ Host = ssh_test_lib:hostname(),
+
+ timer:sleep(5000),
+ {ok, NewSftp, _Conn} = ssh_sftp:start_channel(Host, ?SSHD_PORT,
+ [{silently_accept_hosts, true},
+ {pwdfun, fun(_,_) -> true end},
+ {system_dir, DataDir},
+ {user_dir, DataDir},
+ {user, ?USER}, {password, ?PASSWD}]),
+
+ {ok, <<_/binary>>} = ssh_sftp:read_file(NewSftp, FileName),
+
+ ok = ssh_sftp:stop_channel(NewSftp),
+ ok.
+
+%%--------------------------------------------------------------------
+
+file_cb_OTP_6356(doc) ->
+ ["Test that it is possible to change the callback module for"
+ " the sftpds filehandling."];
+
+file_cb_OTP_6356(suite) ->
+ [];
+
+file_cb_OTP_6356(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(DataDir, "test.txt"),
+
+ register(sftpd_file_alt_tester, self()),
+
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Bin} = ssh_sftp:read_file(Sftp, FileName),
+ alt_file_handler_check(alt_open),
+ alt_file_handler_check(alt_read_file_info),
+ alt_file_handler_check(alt_position),
+ alt_file_handler_check(alt_read),
+ alt_file_handler_check(alt_position),
+ alt_file_handler_check(alt_read),
+ alt_file_handler_check(alt_close),
+
+
+ NewFileName = filename:join(PrivDir, "test.txt"),
+ ok = ssh_sftp:write_file(Sftp, NewFileName, Bin),
+ alt_file_handler_check(alt_open),
+ alt_file_handler_check(alt_read_file_info),
+ alt_file_handler_check(alt_position),
+ alt_file_handler_check(alt_write),
+ alt_file_handler_check(alt_close),
+
+ ReFileName = filename:join(PrivDir, "test1.txt"),
+ ok = ssh_sftp:rename(Sftp, NewFileName, ReFileName),
+ alt_file_handler_check(alt_rename),
+
+ ok = ssh_sftp:delete(Sftp, ReFileName),
+ alt_file_handler_check(alt_delete),
+
+ NewDir = filename:join(PrivDir, "testdir"),
+ ok = ssh_sftp:make_dir(Sftp, NewDir),
+ alt_file_handler_check(alt_make_dir),
+
+ ok = ssh_sftp:del_dir(Sftp, NewDir),
+ alt_file_handler_check(alt_read_link_info),
+ alt_file_handler_check(alt_write_file_info),
+ alt_file_handler_check(alt_del_dir),
+ ok.
+
+root_dir(doc) ->
+ [""];
+root_dir(suite) ->
+ [];
+root_dir(Config) when is_list(Config) ->
+ {Sftp, _} = ?config(sftp, Config),
+ FileName = "test.txt",
+ Bin = <<"Test file for root dir option">>,
+ ok = ssh_sftp:write_file(Sftp, FileName, Bin),
+ {ok, Bin} = ssh_sftp:read_file(Sftp, FileName),
+ {ok, Listing} =
+ ssh_sftp:list_dir(Sftp, "."),
+ test_server:format("Listing: ~p~n", [Listing]),
+ ok.
+
+list_dir_limited(doc) ->
+ [""];
+list_dir_limited(suite) ->
+ [];
+list_dir_limited(Config) when is_list(Config) ->
+ {Sftp, _} = ?config(sftp, Config),
+ {ok, Listing} =
+ ssh_sftp:list_dir(Sftp, "."),
+ test_server:format("Listing: ~p~n", [Listing]),
+ ok.
+
+alt_file_handler_check(Msg) ->
+ receive
+ Msg ->
+ ok;
+ Other ->
+ test_server:fail({Msg, Other})
+ after 10000 ->
+ test_server:fail("Not alt file handler")
+ end.
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl
new file mode 100644
index 0000000000..9e119c4929
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl
@@ -0,0 +1,100 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2010. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Description: Dummy Callback module for ssh_sftpd to test
+%%% the possibility to switch file handling implementation.
+
+-module(ssh_sftpd_file_alt).
+
+-behaviour(ssh_sftpd_file_api).
+
+%% API
+-export([close/2, delete/2, del_dir/2, get_cwd/1, is_dir/2, list_dir/2,
+ make_dir/2, make_symlink/3, open/3, position/3, read/3,
+ read_file_info/2, read_link/2, read_link_info/2, rename/3,
+ write/3, write_file_info/3]).
+
+close(IoDevice, State) ->
+ sftpd_file_alt_tester ! alt_close,
+ {file:close(IoDevice), State}.
+
+delete(Path, State) ->
+ sftpd_file_alt_tester ! alt_delete,
+ {file:delete(Path), State}.
+
+del_dir(Path, State) ->
+ sftpd_file_alt_tester ! alt_del_dir,
+ {file:del_dir(Path), State}.
+
+get_cwd(State) ->
+ {file:get_cwd(), State}.
+
+is_dir(AbsPath, State) ->
+ sftpd_file_alt_tester ! alt_is_dir,
+ {filelib:is_dir(AbsPath), State}.
+
+list_dir(AbsPath, State) ->
+ sftpd_file_alt_tester ! alt_list_dir,
+ {file:list_dir(AbsPath), State}.
+
+make_dir(Dir, State) ->
+ sftpd_file_alt_tester ! alt_make_dir,
+ {file:make_dir(Dir), State}.
+
+make_symlink(Path2, Path, State) ->
+ sftpd_file_alt_tester ! alt_make_symlink,
+ {file:make_symlink(Path2, Path), State}.
+
+open(Path, Flags, State) ->
+ sftpd_file_alt_tester ! alt_open,
+ {file:open(Path, Flags), State}.
+
+position(IoDevice, Offs, State) ->
+ sftpd_file_alt_tester ! alt_position,
+ {file:position(IoDevice, Offs), State}.
+
+read(IoDevice, Len, State) ->
+ sftpd_file_alt_tester ! alt_read,
+ {file:read(IoDevice, Len), State}.
+
+read_link(Path, State) ->
+ sftpd_file_alt_tester ! alt_read_link,
+ {file:read_link(Path), State}.
+
+read_link_info(Path, State) ->
+ sftpd_file_alt_tester ! alt_read_link_info,
+ {file:read_link_info(Path), State}.
+
+read_file_info(Path, State) ->
+ sftpd_file_alt_tester ! alt_read_file_info,
+ {file:read_file_info(Path), State}.
+
+rename(Path, Path2, State) ->
+ sftpd_file_alt_tester ! alt_rename,
+ {file:rename(Path, Path2), State}.
+
+write(IoDevice, Data, State) ->
+ sftpd_file_alt_tester ! alt_write,
+ {file:write(IoDevice, Data), State}.
+
+write_file_info(Path,Info, State) ->
+ sftpd_file_alt_tester ! alt_write_file_info,
+ {file:write_file_info(Path, Info), State}.
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/test.txt b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/test.txt
new file mode 100644
index 0000000000..681bff80a0
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/test.txt
@@ -0,0 +1 @@
+Sftp test file. \ No newline at end of file
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
new file mode 100644
index 0000000000..425fae22c1
--- /dev/null
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -0,0 +1,684 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%%----------------------------------------------------------------------
+-module(ssh_test_lib).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("public_key/include/public_key.hrl").
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+
+-define(TIMEOUT, 50000).
+
+connect(Options) ->
+ connect(hostname(), inet_port(), Options).
+
+connect(Port, Options) when is_integer(Port) ->
+ connect(hostname(), Port, Options);
+connect(any, Options) ->
+ connect(hostname(), inet_port(), Options);
+connect(Host, Options) ->
+ connect(Host, inet_port(), Options).
+
+connect(any, Port, Options) ->
+ connect(hostname(), Port, Options);
+connect(Host, Port, Options) ->
+ case ssh:connect(Host, Port, Options) of
+ {ok, ConnectionRef} ->
+ ConnectionRef;
+ Error ->
+ Error
+ end.
+
+daemon(Options) ->
+ daemon(any, inet_port(), Options).
+
+daemon(Port, Options) when is_integer(Port) ->
+ daemon(any, Port, Options);
+daemon(Host, Options) ->
+ daemon(Host, inet_port(), Options).
+
+daemon(Host, Port, Options) ->
+ case ssh:daemon(Host, Port, Options) of
+ {ok, Pid} when Host == any ->
+ {Pid, hostname(), Port};
+ {ok, Pid} ->
+ {Pid, Host, Port};
+ Error ->
+ Error
+ end.
+
+
+
+
+start_shell(Port, IOServer) ->
+ spawn_link(?MODULE, init_shell, [Port, IOServer]).
+
+init_shell(Port, IOServer) ->
+ Host = hostname(),
+ UserDir = get_user_dir(),
+ Options = [{user_interaction, false}, {silently_accept_hosts,
+ true}] ++ UserDir,
+ group_leader(IOServer, self()),
+ loop_shell(Host, Port, Options).
+
+loop_shell(Host, Port, Options) ->
+ ssh:shell(Host, Port, Options).
+
+start_io_server() ->
+ spawn_link(?MODULE, init_io_server, [self()]).
+
+init_io_server(TestCase) ->
+ process_flag(trap_exit, true),
+ loop_io_server(TestCase, []).
+
+loop_io_server(TestCase, Buff0) ->
+ receive
+ {input, TestCase, Line} ->
+ %io:format("~p~n",[{input, TestCase, Line}]),
+ loop_io_server(TestCase, Buff0 ++ [Line]);
+ {io_request, From, ReplyAs, Request} ->
+ %io:format("request -> ~p~n",[Request]),
+ {ok, Reply, Buff} = io_request(Request, TestCase, From,
+ ReplyAs, Buff0),
+ %io:format("reply -> ~p~n",[Reply]),
+ io_reply(From, ReplyAs, Reply),
+ loop_io_server(TestCase, Buff);
+ {'EXIT',_, _} ->
+ erlang:display('EXIT'),
+ ok
+ end.
+
+io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
+ reply(TestCase, Chars),
+ {ok, ok, Buff};
+io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
+ reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
+ {ok, ok, Buff};
+
+io_request({get_line, _} = Request, _, From, ReplyAs, [] = Buff) ->
+ erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
+ {ok, [], Buff};
+io_request({get_line, _Enc, _Prompt} = Request, _, From, ReplyAs, [] = Buff) ->
+ erlang:send_after(1000, self(), {io_request, From, ReplyAs, Request}),
+ {ok, [], Buff};
+
+io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
+ {ok, Line, Buff}.
+
+io_reply(_, _, []) ->
+ ok;
+io_reply(From, ReplyAs, Reply) ->
+ From ! {io_reply, ReplyAs, Reply}.
+
+reply(_, []) ->
+ ok;
+reply(TestCase, Result) ->
+ TestCase ! Result.
+
+receive_exec_result(Msg) ->
+ test_server:format("Expect data! ~p", [Msg]),
+ receive
+ Msg ->
+ test_server:format("1: Collected data ~p", [Msg]),
+ expected;
+ Other ->
+ {unexpected_msg, Other}
+ end.
+receive_exec_end(ConnectionRef, ChannelId) ->
+ Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
+ ExitStatus = {ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}},
+ Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
+ case receive_exec_result(ExitStatus) of
+ {unexpected_msg, Eof} -> %% Open ssh seems to not allways send these messages
+ %% in the same order!
+ test_server:format("2: Collected data ~p", [Eof]),
+ case receive_exec_result(ExitStatus) of
+ expected ->
+ expected = receive_exec_result(Closed);
+ {unexpected_msg, Closed} ->
+ test_server:format("3: Collected data ~p", [Closed])
+ end;
+ expected ->
+ test_server:format("4: Collected data ~p", [ExitStatus]),
+ expected = receive_exec_result(Eof),
+ expected = receive_exec_result(Closed);
+ Other ->
+ test_server:fail({unexpected_msg, Other})
+ end.
+
+receive_exec_result(Data, ConnectionRef, ChannelId) ->
+ Eof = {ssh_cm, ConnectionRef, {eof, ChannelId}},
+ Closed = {ssh_cm, ConnectionRef,{closed, ChannelId}},
+ expected = receive_exec_result(Data),
+ expected = receive_exec_result(Eof),
+ expected = receive_exec_result(Closed).
+
+
+inet_port()->
+ {ok, Socket} = gen_tcp:listen(0, [{reuseaddr, true}]),
+ {ok, Port} = inet:port(Socket),
+ gen_tcp:close(Socket),
+ Port.
+
+
+%% copy private keys to given dir from ~/.ssh
+get_id_keys(DstDir) ->
+ SrcDir = filename:join(os:getenv("HOME"), ".ssh"),
+ RsaOk = copyfile(SrcDir, DstDir, "id_rsa"),
+ DsaOk = copyfile(SrcDir, DstDir, "id_dsa"),
+ case {RsaOk, DsaOk} of
+ {{ok, _}, {ok, _}} -> {ok, both};
+ {{ok, _}, _} -> {ok, rsa};
+ {_, {ok, _}} -> {ok, dsa};
+ {Error, _} -> Error
+ end.
+
+remove_id_keys(Dir) ->
+ file:delete(filename:join(Dir, "id_rsa")),
+ file:delete(filename:join(Dir, "id_dsa")).
+
+copyfile(SrcDir, DstDir, Fn) ->
+ file:copy(filename:join(SrcDir, Fn),
+ filename:join(DstDir, Fn)).
+
+failfun(_User, {authmethod,none}) ->
+ ok;
+failfun(User, Reason) ->
+ error_logger:format("~p failed XXX to login: ~p~n", [User, Reason]).
+
+hostname() ->
+ {ok,Host} = inet:gethostname(),
+ Host.
+
+known_hosts(BR) ->
+ KnownHosts = ssh_file:file_name(user, "known_hosts", []),
+ B = KnownHosts ++ "xxx",
+ case BR of
+ backup ->
+ file:rename(KnownHosts, B);
+ restore ->
+ file:delete(KnownHosts),
+ file:rename(B, KnownHosts)
+ end.
+
+
+get_user_dir() ->
+ case os:type() of
+ {win32, _} ->
+ [{user_dir, filename:join([os:getenv("HOME"), ".ssh"])}];
+ _ ->
+ []
+ end.
+
+
+make_dsa_cert_files(Config) ->
+ make_dsa_cert_files("", Config).
+
+make_dsa_cert_files(RoleStr, Config) ->
+
+ CaInfo = {CaCert, _} = make_cert([{key, dsa}]),
+ {Cert, CertKey} = make_cert([{key, dsa}, {issuer, CaInfo}]),
+ CaCertFile = filename:join([?config(data_dir, Config),
+ RoleStr, "dsa_cacerts.pem"]),
+ CertFile = filename:join([?config(data_dir, Config),
+ RoleStr, "dsa_cert.pem"]),
+ KeyFile = filename:join([?config(data_dir, Config),
+ RoleStr, "dsa_key.pem"]),
+
+ der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]),
+ der_to_pem(CertFile, [{'Certificate', Cert, not_encrypted}]),
+ der_to_pem(KeyFile, [CertKey]),
+ {CaCertFile, CertFile, KeyFile}.
+
+make_dsa_files(Config) ->
+ make_dsa_files(Config, rfc4716_public_key).
+make_dsa_files(Config, Type) ->
+ {DSA, EncodedKey} = ssh_test_lib:gen_dsa(128, 20),
+ PKey = DSA#'DSAPrivateKey'.y,
+ P = DSA#'DSAPrivateKey'.p,
+ Q = DSA#'DSAPrivateKey'.q,
+ G = DSA#'DSAPrivateKey'.g,
+ Dss = #'Dss-Parms'{p=P, q=Q, g=G},
+ {ok, Hostname} = inet:gethostname(),
+ {ok, {A, B, C, D}} = inet:getaddr(Hostname, inet),
+ IP = lists:concat([A, ".", B, ".", C, ".", D]),
+ Attributes = [], % Could be [{comment,"user@" ++ Hostname}],
+ HostNames = [{hostnames,[IP, IP]}],
+ PublicKey = [{{PKey, Dss}, Attributes}],
+ KnownHosts = [{{PKey, Dss}, HostNames}],
+
+ KnownHostsEnc = public_key:ssh_encode(KnownHosts, known_hosts),
+ KnownHosts = public_key:ssh_decode(KnownHostsEnc, known_hosts),
+
+ PublicKeyEnc = public_key:ssh_encode(PublicKey, Type),
+% PublicKey = public_key:ssh_decode(PublicKeyEnc, Type),
+
+ SystemTmpDir = ?config(data_dir, Config),
+ filelib:ensure_dir(SystemTmpDir),
+ file:make_dir(SystemTmpDir),
+
+ DSAFile = filename:join(SystemTmpDir, "ssh_host_dsa_key.pub"),
+ file:delete(DSAFile),
+
+ DSAPrivateFile = filename:join(SystemTmpDir, "ssh_host_dsa_key"),
+ file:delete(DSAPrivateFile),
+
+ KHFile = filename:join(SystemTmpDir, "known_hosts"),
+ file:delete(KHFile),
+
+ PemBin = public_key:pem_encode([EncodedKey]),
+
+ file:write_file(DSAFile, PublicKeyEnc),
+ file:write_file(KHFile, KnownHostsEnc),
+ file:write_file(DSAPrivateFile, PemBin),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Create and return a der encoded certificate
+%% Option Default
+%% -------------------------------------------------------
+%% digest sha1
+%% validity {date(), date() + week()}
+%% version 3
+%% subject [] list of the following content
+%% {name, Name}
+%% {email, Email}
+%% {city, City}
+%% {state, State}
+%% {org, Org}
+%% {org_unit, OrgUnit}
+%% {country, Country}
+%% {serial, Serial}
+%% {title, Title}
+%% {dnQualifer, DnQ}
+%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created)
+%% (obs IssuerKey migth be {Key, Password}
+%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key
+%%
+%%
+%% (OBS: The generated keys are for testing only)
+%% make_cert([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()}
+%%--------------------------------------------------------------------
+make_cert(Opts) ->
+ SubjectPrivateKey = get_key(Opts),
+ {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts),
+ Cert = public_key:pkix_sign(TBSCert, IssuerKey),
+ true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok
+ {Cert, encode_key(SubjectPrivateKey)}.
+
+%%--------------------------------------------------------------------
+%% Writes cert files in Dir with FileName and FileName ++ Suffix
+%% write_cert(::string(), ::string(), {Cert,Key}) -> ok
+%%--------------------------------------------------------------------
+write_cert(Dir, FileName, Suffix, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) ->
+ ok = der_to_pem(filename:join(Dir, FileName),
+ [{'Certificate', Cert, not_encrypted}]),
+ ok = der_to_pem(filename:join(Dir, FileName ++ Suffix), [Key]).
+
+%%--------------------------------------------------------------------
+%% Creates a rsa key (OBS: for testing only)
+%% the size are in bytes
+%% gen_rsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
+%%--------------------------------------------------------------------
+gen_rsa(Size) when is_integer(Size) ->
+ Key = gen_rsa2(Size),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% Creates a dsa key (OBS: for testing only)
+%% the sizes are in bytes
+%% gen_dsa(::integer()) -> {::atom(), ::binary(), ::opaque()}
+%%--------------------------------------------------------------------
+gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
+ Key = gen_dsa2(LSize, NSize),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% Verifies cert signatures
+%% verify_signature(::binary(), ::tuple()) -> ::boolean()
+%%--------------------------------------------------------------------
+verify_signature(DerEncodedCert, DerKey, _KeyParams) ->
+ Key = decode_key(DerKey),
+ case Key of
+ #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} ->
+ public_key:pkix_verify(DerEncodedCert,
+ #'RSAPublicKey'{modulus=Mod, publicExponent=Exp});
+ #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} ->
+ public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}})
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_key(Opts) ->
+ case proplists:get_value(key, Opts) of
+ undefined -> make_key(rsa, Opts);
+ rsa -> make_key(rsa, Opts);
+ dsa -> make_key(dsa, Opts);
+ Key ->
+ Password = proplists:get_value(password, Opts, no_passwd),
+ decode_key(Key, Password)
+ end.
+
+decode_key({Key, Pw}) ->
+ decode_key(Key, Pw);
+decode_key(Key) ->
+ decode_key(Key, no_passwd).
+
+
+decode_key(#'RSAPublicKey'{} = Key,_) ->
+ Key;
+decode_key(#'RSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(#'DSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(PemEntry = {_,_,_}, Pw) ->
+ public_key:pem_entry_decode(PemEntry, Pw);
+decode_key(PemBin, Pw) ->
+ [KeyInfo] = public_key:pem_decode(PemBin),
+ decode_key(KeyInfo, Pw).
+
+encode_key(Key = #'RSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
+ {'RSAPrivateKey', list_to_binary(Der), not_encrypted};
+encode_key(Key = #'DSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
+ {'DSAPrivateKey', list_to_binary(Der), not_encrypted}.
+
+make_tbs(SubjectKey, Opts) ->
+ Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
+
+ IssuerProp = proplists:get_value(issuer, Opts, true),
+ {Issuer, IssuerKey} = issuer(IssuerProp, Opts, SubjectKey),
+
+ {Algo, Parameters} = sign_algorithm(IssuerKey, Opts),
+
+ SignAlgo = #'SignatureAlgorithm'{algorithm = Algo,
+ parameters = Parameters},
+ Subject = case IssuerProp of
+ true -> %% Is a Root Ca
+ Issuer;
+ _ ->
+ subject(proplists:get_value(subject, Opts),false)
+ end,
+
+ {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
+ signature = SignAlgo,
+ issuer = Issuer,
+ validity = validity(Opts),
+ subject = Subject,
+ subjectPublicKeyInfo = publickey(SubjectKey),
+ version = Version,
+ extensions = extensions(Opts)
+ }, IssuerKey}.
+
+issuer(true, Opts, SubjectKey) ->
+ %% Self signed
+ {subject(proplists:get_value(subject, Opts), true), SubjectKey};
+issuer({Issuer, IssuerKey}, _Opts, _SubjectKey) when is_binary(Issuer) ->
+ {issuer_der(Issuer), decode_key(IssuerKey)};
+issuer({File, IssuerKey}, _Opts, _SubjectKey) when is_list(File) ->
+ {ok, [{cert, Cert, _}|_]} = pem_to_der(File),
+ {issuer_der(Cert), decode_key(IssuerKey)}.
+
+issuer_der(Issuer) ->
+ Decoded = public_key:pkix_decode_cert(Issuer, otp),
+ #'OTPCertificate'{tbsCertificate=Tbs} = Decoded,
+ #'OTPTBSCertificate'{subject=Subject} = Tbs,
+ Subject.
+
+subject(undefined, IsRootCA) ->
+ User = if IsRootCA -> "RootCA"; true -> os:getenv("USER") end,
+ Opts = [{email, User ++ "@erlang.org"},
+ {name, User},
+ {city, "Stockholm"},
+ {country, "SE"},
+ {org, "erlang"},
+ {org_unit, "testing dep"}],
+ subject(Opts);
+subject(Opts, _) ->
+ subject(Opts).
+
+subject(SubjectOpts) when is_list(SubjectOpts) ->
+ Encode = fun(Opt) ->
+ {Type,Value} = subject_enc(Opt),
+ [#'AttributeTypeAndValue'{type=Type, value=Value}]
+ end,
+ {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}.
+
+%% Fill in the blanks
+subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}};
+subject_enc({email, Email}) -> {?'id-emailAddress', Email};
+subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}};
+subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}};
+subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}};
+subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}};
+subject_enc({country, Country}) -> {?'id-at-countryName', Country};
+subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial};
+subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}};
+subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ};
+subject_enc(Other) -> Other.
+
+
+extensions(Opts) ->
+ case proplists:get_value(extensions, Opts, []) of
+ false ->
+ asn1_NOVALUE;
+ Exts ->
+ lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)])
+ end.
+
+default_extensions(Exts) ->
+ Def = [{key_usage,undefined},
+ {subject_altname, undefined},
+ {issuer_altname, undefined},
+ {basic_constraints, default},
+ {name_constraints, undefined},
+ {policy_constraints, undefined},
+ {ext_key_usage, undefined},
+ {inhibit_any, undefined},
+ {auth_key_id, undefined},
+ {subject_key_id, undefined},
+ {policy_mapping, undefined}],
+ Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end,
+ Exts ++ lists:foldl(Filter, Def, Exts).
+
+extension({_, undefined}) -> [];
+extension({basic_constraints, Data}) ->
+ case Data of
+ default ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true},
+ critical=true};
+ false ->
+ [];
+ Len when is_integer(Len) ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len},
+ critical=true};
+ _ ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = Data}
+ end;
+extension({Id, Data, Critical}) ->
+ #'Extension'{extnID = Id, extnValue = Data, critical = Critical}.
+
+
+publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
+ parameters={params, #'Dss-Parms'{p=P, q=Q, g=G}}},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}.
+
+validity(Opts) ->
+ DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
+ DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
+ {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
+ Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end,
+ #'Validity'{notBefore={generalTime, Format(DefFrom)},
+ notAfter ={generalTime, Format(DefTo)}}.
+
+sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
+ Type = case proplists:get_value(digest, Opts, sha1) of
+ sha1 -> ?'sha1WithRSAEncryption';
+ sha512 -> ?'sha512WithRSAEncryption';
+ sha384 -> ?'sha384WithRSAEncryption';
+ sha256 -> ?'sha256WithRSAEncryption';
+ md5 -> ?'md5WithRSAEncryption';
+ md2 -> ?'md2WithRSAEncryption'
+ end,
+ {Type, 'NULL'};
+sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
+ {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}.
+
+make_key(rsa, _Opts) ->
+ %% (OBS: for testing only)
+ gen_rsa2(64);
+make_key(dsa, _Opts) ->
+ gen_dsa2(128, 20). %% Bytes i.e. {1024, 160}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RSA key generation (OBS: for testing only)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53,
+ 47,43,41,37,31,29,23,19,17,13,11,7,5,3]).
+
+gen_rsa2(Size) ->
+ P = prime(Size),
+ Q = prime(Size),
+ N = P*Q,
+ Tot = (P - 1) * (Q - 1),
+ [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES),
+ {D1,D2} = extended_gcd(E, Tot),
+ D = erlang:max(D1,D2),
+ case D < E of
+ true ->
+ gen_rsa2(Size);
+ false ->
+ {Co1,Co2} = extended_gcd(Q, P),
+ Co = erlang:max(Co1,Co2),
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = N,
+ publicExponent = E,
+ privateExponent = D,
+ prime1 = P,
+ prime2 = Q,
+ exponent1 = D rem (P-1),
+ exponent2 = D rem (Q-1),
+ coefficient = Co
+ }
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DSA key generation (OBS: for testing only)
+%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
+%% and the fips_186-3.pdf
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+gen_dsa2(LSize, NSize) ->
+ Q = prime(NSize), %% Choose N-bit prime Q
+ X0 = prime(LSize),
+ P0 = prime((LSize div 2) +1),
+
+ %% Choose L-bit prime modulus P such that p-1 is a multiple of q.
+ case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
+ error ->
+ gen_dsa2(LSize, NSize);
+ P ->
+ G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
+ %% such that This may be done by setting g = h^(p-1)/q mod p, commonly h=2 is used.
+
+ X = prime(20), %% Choose x by some random method, where 0 < x < q.
+ Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
+
+ #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
+ end.
+
+%% See fips_186-3.pdf
+dsa_search(T, P0, Q, Iter) when Iter > 0 ->
+ P = 2*T*Q*P0 + 1,
+ case is_prime(crypto:mpint(P), 50) of
+ true -> P;
+ false -> dsa_search(T+1, P0, Q, Iter-1)
+ end;
+dsa_search(_,_,_,_) ->
+ error.
+
+
+%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+prime(ByteSize) ->
+ Rand = odd_rand(ByteSize),
+ crypto:erlint(prime_odd(Rand, 0)).
+
+prime_odd(Rand, N) ->
+ case is_prime(Rand, 50) of
+ true ->
+ Rand;
+ false ->
+ NotPrime = crypto:erlint(Rand),
+ prime_odd(crypto:mpint(NotPrime+2), N+1)
+ end.
+
+%% see http://en.wikipedia.org/wiki/Fermat_primality_test
+is_prime(_, 0) -> true;
+is_prime(Candidate, Test) ->
+ CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
+ case crypto:mod_exp(CoPrime, Candidate, Candidate) of
+ CoPrime -> is_prime(Candidate, Test-1);
+ _ -> false
+ end.
+
+odd_rand(Size) ->
+ Min = 1 bsl (Size*8-1),
+ Max = (1 bsl (Size*8))-1,
+ odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
+
+odd_rand(Min,Max) ->
+ Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
+ BitSkip = (Sz+4)*8-1,
+ case Rand of
+ Odd = <<_:BitSkip, 1:1>> -> Odd;
+ Even = <<_:BitSkip, 0:1>> ->
+ crypto:mpint(crypto:erlint(Even)+1)
+ end.
+
+extended_gcd(A, B) ->
+ case A rem B of
+ 0 ->
+ {0, 1};
+ N ->
+ {X, Y} = extended_gcd(B, N),
+ {Y, X-Y*(A div B)}
+ end.
+
+pem_to_der(File) ->
+ {ok, PemBin} = file:read_file(File),
+ public_key:pem_decode(PemBin).
+
+der_to_pem(File, Entries) ->
+ PemBin = public_key:pem_encode(Entries),
+ file:write_file(File, PemBin).
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
new file mode 100644
index 0000000000..f959d50484
--- /dev/null
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -0,0 +1,458 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+-module(ssh_to_openssh_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("test_server_line.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(TIMEOUT, 50000).
+-define(SSH_DEFAULT_PORT, 22).
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initialization before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ ssh_test_lib:make_dsa_files(Config),
+ Config;
+ _Else ->
+ {skip,"Could not start crypto!"}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ crypto:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initialization before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initialization before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ssh:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all() ->
+ case os:find_executable("ssh") of
+ false ->
+ {skip, "openSSH not installed on host"};
+ _ ->
+ [erlang_shell_client_openssh_server,
+ erlang_client_openssh_server_exec,
+ erlang_client_openssh_server_exec_compressed,
+ erlang_server_openssh_client_exec,
+ erlang_server_openssh_client_exec_compressed,
+ erlang_client_openssh_server_setenv,
+ erlang_client_openssh_server_publickey_rsa,
+ erlang_client_openssh_server_publickey_dsa,
+ erlang_server_openssh_client_pulic_key_dsa,
+ erlang_client_openssh_server_password]
+ end.
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+%% TEST cases starts here.
+%%--------------------------------------------------------------------
+erlang_shell_client_openssh_server(doc) ->
+ ["Test that ssh:shell/2 works"];
+
+erlang_shell_client_openssh_server(suite) ->
+ [];
+
+erlang_shell_client_openssh_server(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ IO = ssh_test_lib:start_io_server(),
+ Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO),
+ IO ! {input, self(), "echo Hej\n"},
+ receive_hej(),
+ IO ! {input, self(), "exit\n"},
+ receive
+ <<"logout">> ->
+ receive
+ <<"Connection closed">> ->
+ ok
+ end;
+ Other0 ->
+ test_server:fail({unexpected_msg, Other0})
+ end,
+ receive
+ {'EXIT', Shell, normal} ->
+ ok;
+ Other1 ->
+ test_server:fail({unexpected_msg, Other1})
+ end.
+
+%--------------------------------------------------------------------
+erlang_client_openssh_server_exec(doc) ->
+ ["Test api function ssh_connection:exec"];
+
+erlang_client_openssh_server_exec(suite) ->
+ [];
+
+erlang_client_openssh_server_exec(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{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} ->
+ test_server:format("0: Collected data ~p", [ExitStatus0]),
+ ssh_test_lib:receive_exec_result(Data0,
+ ConnectionRef, ChannelId0);
+ Other0 ->
+ test_server: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} ->
+ test_server:format("0: Collected data ~p", [ExitStatus1]),
+ ssh_test_lib:receive_exec_result(Data1,
+ ConnectionRef, ChannelId1);
+ Other1 ->
+ test_server:fail(Other1)
+ end.
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_exec_compressed(doc) ->
+ ["Test that compression option works"];
+
+erlang_client_openssh_server_exec_compressed(suite) ->
+ [];
+
+erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {compression, zlib}]),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId,
+ "echo testing", infinity),
+ Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"testing\n">>}},
+ case ssh_test_lib:receive_exec_result(Data) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
+ {unexpected_msg,{ssh_cm, ConnectionRef,
+ {exit_status, ChannelId, 0}} = ExitStatus} ->
+ test_server:format("0: Collected data ~p", [ExitStatus]),
+ ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId);
+ Other ->
+ test_server:fail(Other)
+ end.
+
+%%--------------------------------------------------------------------
+erlang_server_openssh_client_exec(doc) ->
+ ["Test that exec command works."];
+
+erlang_server_openssh_client_exec(suite) ->
+ [];
+
+erlang_server_openssh_client_exec(Config) when is_list(Config) ->
+ SystemDir = ?config(data_dir, Config),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+
+ test_server:sleep(500),
+
+ Cmd = "ssh -p " ++ integer_to_list(Port) ++
+ " -o StrictHostKeyChecking=no "++ Host ++ " 1+1.",
+ SshPort = open_port({spawn, Cmd}, [binary]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ test_server:fail("Did not receive answer")
+
+ end,
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+erlang_server_openssh_client_exec_compressed(doc) ->
+ ["Test that exec command works."];
+
+erlang_server_openssh_client_exec_compressed(suite) ->
+ [];
+
+erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {compression, zlib},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ test_server:sleep(500),
+
+ Cmd = "ssh -p " ++ integer_to_list(Port) ++
+ " -o StrictHostKeyChecking=no -C "++ Host ++ " 1+1.",
+ SshPort = open_port({spawn, Cmd}, [binary]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ test_server:fail("Did not receive answer")
+
+ end,
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_setenv(doc) ->
+ ["Test api function ssh_connection:setenv"];
+
+erlang_client_openssh_server_setenv(suite) ->
+ [];
+
+erlang_client_openssh_server_setenv(Config) when is_list(Config) ->
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user_interaction, false}]),
+ {ok, ChannelId} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ Env = case ssh_connection:setenv(ConnectionRef, ChannelId,
+ "ENV_TEST", "testing_setenv",
+ infinity) of
+ success ->
+ <<"tesing_setenv\n">>;
+ failure ->
+ <<"\n">>
+ end,
+ success = ssh_connection:exec(ConnectionRef, ChannelId,
+ "echo $ENV_TEST", infinity),
+ Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, Env}},
+ case ssh_test_lib:receive_exec_result(Data) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
+ {unexpected_msg,{ssh_cm, ConnectionRef,
+ {data,0,1, UnxpectedData}}} ->
+ %% Some os may return things as
+ %% ENV_TEST: Undefined variable.\n"
+ test_server:format("UnxpectedData: ~p", [UnxpectedData]),
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId);
+ {unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId, 0}}
+ = ExitStatus} ->
+ test_server:format("0: Collected data ~p", [ExitStatus]),
+ ssh_test_lib:receive_exec_result(Data,
+ ConnectionRef, ChannelId);
+ Other ->
+ test_server:fail(Other)
+ end.
+
+%%--------------------------------------------------------------------
+
+%% setenv not meaningfull on erlang ssh daemon!
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_publickey_rsa(doc) ->
+ ["Validate using rsa publickey."];
+erlang_client_openssh_server_publickey_rsa(suite) ->
+ [];
+erlang_client_openssh_server_publickey_rsa(Config) when is_list(Config) ->
+ {ok,[[Home]]} = init:get_argument(home),
+ SrcDir = filename:join(Home, ".ssh"),
+ UserDir = ?config(priv_dir, Config),
+ case ssh_test_lib:copyfile(SrcDir, UserDir, "id_rsa") of
+ {ok, _} ->
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [{user_dir, UserDir},
+ {public_key_alg, ssh_rsa},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ {ok, Channel} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:close(ConnectionRef, Channel),
+ ok = ssh:close(ConnectionRef),
+ ok = file:delete(filename:join(UserDir, "id_rsa"));
+ {error, enoent} ->
+ {skip, "no ~/.ssh/id_rsa"}
+ end.
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_publickey_dsa(doc) ->
+ ["Validate using dsa publickey."];
+erlang_client_openssh_server_publickey_dsa(suite) ->
+ [];
+erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) ->
+ {ok,[[Home]]} = init:get_argument(home),
+ SrcDir = filename:join(Home, ".ssh"),
+ UserDir = ?config(priv_dir, Config),
+ case ssh_test_lib:copyfile(SrcDir, UserDir, "id_dsa") of
+ {ok, _} ->
+ ConnectionRef =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [{user_dir, UserDir},
+ {public_key_alg, ssh_dsa},
+ {user_interaction, false},
+ silently_accept_hosts]),
+ {ok, Channel} =
+ ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:close(ConnectionRef, Channel),
+ ok = ssh:close(ConnectionRef),
+ ok = file:delete(filename:join(UserDir, "id_dsa"));
+ {error, enoent} ->
+ {skip, "no ~/.ssh/id_dsa"}
+ end.
+
+%%--------------------------------------------------------------------
+erlang_server_openssh_client_pulic_key_dsa(doc) ->
+ ["Validate using dsa publickey."];
+
+erlang_server_openssh_client_pulic_key_dsa(suite) ->
+ [];
+
+erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {public_key_alg, ssh_dsa},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ test_server:sleep(500),
+
+ Cmd = "ssh -p " ++ integer_to_list(Port) ++
+ " -o StrictHostKeyChecking=no "++ Host ++ " 1+1.",
+ SshPort = open_port({spawn, Cmd}, [binary]),
+
+ receive
+ {SshPort,{data, <<"2\n">>}} ->
+ ok
+ after ?TIMEOUT ->
+ test_server:fail("Did not receive answer")
+
+ end,
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_password(doc) ->
+ ["Test client password option"];
+
+erlang_client_openssh_server_password(suite) ->
+ [];
+
+erlang_client_openssh_server_password(Config) when is_list(Config) ->
+ %% to make sure we don't public-key-auth
+ UserDir = ?config(data_dir, Config),
+ {error, Reason0} =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ test_server:format("Test of user foo that does not exist. "
+ "Error msg: ~p~n", [Reason0]),
+
+ User = string:strip(os:cmd("whoami"), right, $\n),
+
+ case length(string:tokens(User, " ")) of
+ 1 ->
+ {error, Reason1} =
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [{silently_accept_hosts, true},
+ {user, User},
+ {password, "foo"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ test_server:format("Test of wrong Pasword. "
+ "Error msg: ~p~n", [Reason1]);
+ _ ->
+ test_server:format("Whoami failed reason: ~n", [])
+ end.
+
+%%--------------------------------------------------------------------
+%
+%% Not possible to send password with openssh without user interaction
+%%
+%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+receive_hej() ->
+ receive
+ <<"Hej\n">> = Hej->
+ test_server:format("Expected result: ~p~n", [Hej]);
+ Info ->
+ test_server:format("Extra info: ~p~n", [Info]),
+ receive_hej()
+ end.