From d6e981e751b4565773204d7eb394ca445d63e771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn-Egil=20Dahlberg?= Date: Fri, 26 Nov 2010 14:55:41 +0100 Subject: Add test suite for ssh --- lib/ssh/test/Makefile | 131 +++ lib/ssh/test/ssh.spec | 6 + lib/ssh/test/ssh.spec.vxworks | 3 + lib/ssh/test/ssh_SUITE.erl | 64 ++ lib/ssh/test/ssh_basic_SUITE.erl | 395 +++++++++ lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key | 12 + .../test/ssh_basic_SUITE_data/ssh_host_dsa_key.pub | 1 + lib/ssh/test/ssh_host_dsa_key | 12 + lib/ssh/test/ssh_host_dsa_key.pub | 1 + lib/ssh/test/ssh_sftp_SUITE.erl | 531 ++++++++++++ lib/ssh/test/ssh_sftp_SUITE_data/id_rsa | 15 + lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub | 1 + lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt | 252 ++++++ lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key | 12 + .../test/ssh_sftp_SUITE_data/ssh_host_dsa_key.pub | 1 + lib/ssh/test/ssh_sftpd_SUITE.erl | 914 +++++++++++++++++++++ lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key | 12 + .../test/ssh_sftpd_SUITE_data/ssh_host_dsa_key.pub | 1 + lib/ssh/test/ssh_sftpd_SUITE_data/test.txt | 1 + lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl | 304 +++++++ .../ssh_host_dsa_key | 12 + .../ssh_host_dsa_key.pub | 1 + .../ssh_sftpd_file_alt.erl | 100 +++ .../test/ssh_sftpd_erlclient_SUITE_data/test.txt | 1 + lib/ssh/test/ssh_test_lib.erl | 189 +++++ lib/ssh/test/ssh_to_openssh_SUITE.erl | 463 +++++++++++ .../ssh_to_openssh_SUITE_data/ssh_host_dsa_key | 12 + .../ssh_to_openssh_SUITE_data/ssh_host_dsa_key.pub | 1 + 28 files changed, 3448 insertions(+) create mode 100644 lib/ssh/test/Makefile create mode 100644 lib/ssh/test/ssh.spec create mode 100644 lib/ssh/test/ssh.spec.vxworks create mode 100644 lib/ssh/test/ssh_SUITE.erl create mode 100644 lib/ssh/test/ssh_basic_SUITE.erl create mode 100644 lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key.pub create mode 100644 lib/ssh/test/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_host_dsa_key.pub create mode 100644 lib/ssh/test/ssh_sftp_SUITE.erl create mode 100644 lib/ssh/test/ssh_sftp_SUITE_data/id_rsa create mode 100644 lib/ssh/test/ssh_sftp_SUITE_data/id_rsa.pub create mode 100644 lib/ssh/test/ssh_sftp_SUITE_data/sftp.txt create mode 100644 lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key.pub create mode 100644 lib/ssh/test/ssh_sftpd_SUITE.erl create mode 100644 lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key.pub create mode 100644 lib/ssh/test/ssh_sftpd_SUITE_data/test.txt create mode 100644 lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl create mode 100644 lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key.pub create mode 100644 lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl create mode 100644 lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/test.txt create mode 100644 lib/ssh/test/ssh_test_lib.erl create mode 100644 lib/ssh/test/ssh_to_openssh_SUITE.erl create mode 100644 lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key create mode 100644 lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key.pub (limited to 'lib/ssh/test') diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile new file mode 100644 index 0000000000..116f894493 --- /dev/null +++ b/lib/ssh/test/Makefile @@ -0,0 +1,131 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 2004-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% +# + +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) + +KEY_FILES= ssh_host_dsa_key ssh_host_dsa_key.pub + +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 $(RELSYSDIR) + $(INSTALL_DATA) $(HRL_FILES_NEEDED_IN_TEST) $(RELSYSDIR) + @for dir in $(DATA_DIRS); do \ + if test ! -d $$dir ; then \ + echo "=== Skipping datadir $$dir" ; \ + else \ + echo "Installling $(KEY_FILES) in $$dir"; \ + $(INSTALL_DATA) $(KEY_FILES) $$dir; \ + fi ; \ + done + chmod -f -R u+w $(RELSYSDIR) + @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) + +release_docs_spec: diff --git a/lib/ssh/test/ssh.spec b/lib/ssh/test/ssh.spec new file mode 100644 index 0000000000..d99250654e --- /dev/null +++ b/lib/ssh/test/ssh.spec @@ -0,0 +1,6 @@ +{topcase, {dir, "../ssh_test"}}. +{require_nodenames, 1}. +{skip, {ssh_ssh_SUITE, ssh, "Current implementation is timingdependent and +hence will succeed/fail on a whim"}}. +{skip, {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..dd4571febe --- /dev/null +++ b/lib/ssh/test/ssh_SUITE.erl @@ -0,0 +1,64 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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% +%% + +%% +%%%---------------------------------------------------------------- +%%% Purpose:ssh application test suite. +%%%----------------------------------------------------------------- +-module(ssh_SUITE). +-include("test_server.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/1]). +-export([init_per_testcase/2, fin_per_testcase/2]). + +% Test cases must be exported. +-export([app_test/1]). +-define(cases, [app_test]). + +%% +%% all/1 +%% +all(doc) -> + []; +all(suite) -> + [?cases]. + +init_per_testcase(_Case, Config) -> + Dog=test_server:timetrap(?default_timeout), + [{watchdog, Dog}|Config]. +fin_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..d5eb5367e6 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -0,0 +1,395 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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% +%% + +%% + +-module(ssh_basic_SUITE). + +-include("test_server.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) -> + crypto:start(), + Dir = ?config(priv_dir, Config), + ssh_test_lib:save_known_hosts(Dir), + {ok, _} = ssh_test_lib:get_id_keys(Dir), + Config. + +%%-------------------------------------------------------------------- +%% 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), + ssh_test_lib:restore_known_hosts(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) -> + rename_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(), + rename_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(doc) -> + ["Test ssh API"]; + +all(suite) -> + [exec, exec_compressed, shell, daemon_allready_started, + server_password_option, server_userpassword_option, known_hosts]. + +%% Test cases starts here. +%%-------------------------------------------------------------------- + +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), + Host = ssh_test_lib:hostname(), + Port = ssh_test_lib:inet_port(), + {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + {ok, ConnectionRef} = + ssh: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), + Host = ssh_test_lib:hostname(), + Port = ssh_test_lib:inet_port(), + {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, + {compression, zlib}, + {failfun, fun ssh_test_lib:failfun/2}]), + + {ok, ConnectionRef} = + ssh: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), + Port = ssh_test_lib:inet_port(), + {ok, _Pid} = ssh:daemon(Port, [{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_allready_started(doc) -> + ["Test that get correct error message if you try to start a daemon", + "on an adress that allready runs a daemon see also seq10667" ]; + +daemon_allready_started(suite) -> + []; + +daemon_allready_started(Config) when is_list(Config) -> + SystemDir = ?config(data_dir, Config), + Port = ssh_test_lib:inet_port(), + {ok, Pid} = ssh:daemon(Port, [{system_dir, SystemDir}, + {failfun, fun ssh_test_lib:failfun/2}]), + {error, eaddrinuse} = ssh: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 + Port = ssh_test_lib:inet_port(), + {ok, Pid} = + ssh:daemon(Port, [{system_dir, SysDir}, + {password, "morot"}]), + Host = ssh_test_lib:hostname(), + + {ok, ConnectionRef} = + ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), + {error, Reason} = + ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "foo"}, + {user_interaction, false}, + {user_dir, UserDir}]), + + test_server:format("Test of wrong pasword: 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 + Port = ssh_test_lib:inet_port(), + {ok, Pid} = + ssh:daemon(Port, [{system_dir, SysDir}, + {user_passwords, [{"vego", "morot"}]}]), + Host = ssh_test_lib:hostname(), + + {ok, ConnectionRef} = + ssh:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "morot"}, + {user_interaction, false}, + {user_dir, UserDir}]), + ssh:close(ConnectionRef), + + {error, Reason0} = + ssh: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:connect(Host, Port, [{silently_accept_hosts, true}, + {user, "vego"}, + {password, "foo"}, + {user_interaction, false}, + {user_dir, UserDir}]), + test_server:format("Test of wrong Pasword. " + "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), + Port = ssh_test_lib:inet_port(), + + {ok, Pid} = ssh:daemon(Port, [{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), + Host = ssh_test_lib:hostname(), + {ok, ConnectionRef} = + ssh: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, + {ok, Hostname} = inet:gethostname(), + [HostAndIp, Alg, _KeyData] = string:tokens(Line, " "), + [Hostname, _Ip] = string:tokens(HostAndIp, ","), + "ssh-" ++ _ = Alg, + ssh:stop_daemon(Pid). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +rename_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. diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_basic_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin diff --git a/lib/ssh/test/ssh_host_dsa_key b/lib/ssh/test/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl new file mode 100644 index 0000000000..eb7fbd6998 --- /dev/null +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -0,0 +1,531 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% + +%% +-module(ssh_sftp_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("test_server.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) -> + crypto:start(), + ssh:start(), + Dir = ?config(priv_dir, Config), + ssh_test_lib:save_known_hosts(Dir), + %% More like copy_id_keys!!! + {ok, _} = ssh_test_lib:get_id_keys(Dir), + Config. +%%-------------------------------------------------------------------- +%% 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), + ssh_test_lib:restore_known_hosts(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 -> + {ok, _Sftpd} = + ssh:daemon(?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(doc) -> + ["Test the sftp client"]; + +all(suite) -> + [ + 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 + ]. + +%% 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_sftp_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl new file mode 100644 index 0000000000..4ce6bd45b3 --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_SUITE.erl @@ -0,0 +1,914 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% + +%% +-module(ssh_sftpd_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("test_server.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) -> + ssh:stop(), + crypto:start(), + Config. + +%%-------------------------------------------------------------------- +%% 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}]}]), + + Host = ssh_test_lib:hostname(), + {ok, Cm} = ssh:connect(Host, ?SFPD_PORT, + [{silently_accept_hosts, true}, + {user, ?USER}, {password, ?PASSWD}]), + {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 = <> , + + Size = 1 + size(Data), + + ssh_connection:send(Cm, Channel, << ?UINT32(Size), + ?SSH_FXP_INIT, Data/binary >>), + + {ok, <>, _} + = 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(doc) -> + ["Test the sftp server"]; + +all(suite) -> + [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]. + +%% 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, <>, _} = + open_file(FileName, Cm, Channel, ReqId, + ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES, + ?SSH_FXF_OPEN_EXISTING), + + {ok, <>, _} = close(Handle, ReqId, + Cm, Channel), + NewReqId = ReqId + 1, + {ok, <>, _} = + close(Handle, ReqId, Cm, Channel), + + NewReqId1 = NewReqId + 1, + %% {ok, <>, _} = + {ok, <>, _} = + 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, <>, _} = + open_dir(PrivDir, Cm, Channel, ReqId), + + {ok, <>, _} = close(Handle, ReqId, + Cm, Channel), + + NewReqId = 1, + case open_dir(FileName, Cm, Channel, NewReqId) of + {ok, <>, _} -> + %% Only if server is using vsn > 5. + ok; + {ok, <>, _} -> + 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, <>, _} = + open_file(FileName, Cm, Channel, ReqId, + ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES, + ?SSH_FXF_OPEN_EXISTING), + + NewReqId = 1, + + {ok, <>, _} = + 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, <>, _} = + 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, <>, _} = + 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, <>, _} + = 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, <>, _} = + remove(FileName, Cm, Channel, ReqId), + + NewReqId = 1, + %% {ok, <>, _} = + + {ok, <>, _} = + 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, <>, _} = + rename(FileName, NewFileName, Cm, Channel, ReqId, 6, 0), + + NewReqId = ReqId + 1, + + {ok, <>, _} = + rename(NewFileName, FileName, Cm, Channel, NewReqId, 6, + ?SSH_FXP_RENAME_OVERWRITE), + + NewReqId1 = NewReqId + 1, + file:copy(FileName, NewFileName), + + %% No owerwrite + {ok, <>, _} = + rename(FileName, NewFileName, Cm, Channel, NewReqId1, 6, + ?SSH_FXP_RENAME_NATIVE), + + NewReqId2 = NewReqId1 + 1, + + {ok, <>, _} = + 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, <>, _} = mkdir(DirName, Cm, Channel, ReqId), + + NewReqId = 1, + {ok, <>, _} = mkdir(DirName, Cm, Channel, NewReqId), + + NewReqId1 = 2, + {ok, <>, _} = rmdir(DirName, Cm, Channel, NewReqId1), + + NewReqId2 = 3, + {ok, <>, _} = 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, <>, _} + = 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, <>, _} = + create_link(LinkFileName, FileName, Cm, Channel, ReqId), + + NewReqId = 1, + {ok, <>, _} + = 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) -> + <> = 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), + + <> = 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, <>, _} = + 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, <>, _} = + 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, <>, _} = + 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, <>, _} = + 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, <>, _} + = real_path("/..", Cm, Channel, ReqId), + + <<"/">> = Root, + + {ok, <>, _} + = 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 <> of + <> -> + {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, <>), + reply(Cm, Channel). + + + +close(Handle, ReqId, Cm , Channel) -> + Data = list_to_binary([?uint32(ReqId), Handle]), + + Size = 1 + size(Data), + + ssh_connection:send(Cm, Channel, <>), + + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + case reply(Cm, Channel) of + {ok, <>, _} -> + test_server:format("Count: ~p Listing: ~p~n", + [Count, binary_to_list(Listing)]), + read_dir(Handle, Cm, Channel, ReqId); + {ok, <>, _} -> + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + 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, <>), + reply(Cm, Channel). + +retrive_attributes(FileName, Cm, Channel, ReqId) -> + + Attr = ?SSH_FILEXFER_ATTR_SIZE, + + {ok, <>, _} + = retrive_attributes_file(FileName, Attr, + Cm, Channel, ReqId), + + NewReqId = ReqId + 1, + {ok, <>, _} + = retrive_attributes_file_or_link(FileName, + Attr, Cm, Channel, NewReqId), + + NewReqId1 = NewReqId + 1, + {ok, <>, _} = + open_file(FileName, Cm, Channel, NewReqId1, + ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES, + ?SSH_FXF_OPEN_EXISTING), + + NewReqId2 = NewReqId1 + 1, + {ok, <>, _} + = 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, <>), + 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, <>), + 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, <>, _} = + open_file(FileName, Cm, Channel, ReqId, + ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES, + ?SSH_FXF_OPEN_EXISTING), + + NewReqId = 1, + + {ok, <>, _} = + 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/ssh_host_dsa_key b/lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin 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..6ae5e73dcb --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl @@ -0,0 +1,304 @@ +%% +%% %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% +%% + +%% +-module(ssh_sftpd_erlclient_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include("test_server.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) -> + ssh:stop(), + crypto:start(), + 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}), + + Config. + +%%-------------------------------------------------------------------- +%% 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}]}, + {system_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}]}, + {system_dir, DataDir}, + {subsystems, [Spec]}]; + "list_dir_limited" -> + Spec = + ssh_sftpd:subsystem_spec([{max_files,1}]), + [{user_passwords,[{?USER, ?PASSWD}]}, + {system_dir, DataDir}, + {subsystems, [Spec]}]; + + _ -> + [{user_passwords,[{?USER, ?PASSWD}]}, + {system_dir, DataDir}] + end, + + {ok, Sftpd} = ssh:daemon(any, ?SSHD_PORT, Options), + + Host = ssh_test_lib:hostname(), + {ok, ChannelPid, Connection} = + ssh_sftp:start_channel(Host, ?SSHD_PORT, + [{silently_accept_hosts, true}, + {user, ?USER}, {password, ?PASSWD}, {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(doc) -> + ["Test ssh_sftpd"]; + +all(suite) -> + [close_file_OTP_6350, quit_OTP_6349, file_cb_OTP_6356, + root_dir, list_dir_limited]. + +%% 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}, + {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_host_dsa_key b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin 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..2eb19cec22 --- /dev/null +++ b/lib/ssh/test/ssh_test_lib.erl @@ -0,0 +1,189 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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% +%% + +%% +%%---------------------------------------------------------------------- +-module(ssh_test_lib). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +start_shell(Port, IOServer) -> + spawn_link(?MODULE, init_shell, [Port, IOServer]). + +init_shell(Port, IOServer) -> + Host = ssh_test_lib:hostname(), + UserDir = ssh_test_lib: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 = ssh_test_lib:receive_exec_result(Data), + expected = ssh_test_lib:receive_exec_result(Eof), + expected = ssh_test_lib: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. + +save_known_hosts(PrivDir) -> + Src = ssh_file:file_name(user, "known_hosts", []), + Dst = filename:join(PrivDir, "kh_save"), + Ok = file:copy(Src, Dst), + io:format("save ~p -> ~p : ~p", [Src, Dst, Ok]). + +restore_known_hosts(_PrivDir) -> + %% Race condition. + ok. +%% Src = filename:join(PrivDir, "kh_save"), +%% Dst = ssh_file:file_name(user, "known_hosts", []), +%% D1 = file:delete(Dst), +%% C = file:copy(Src, Dst), +%% D2 = file:delete(Src), +%% io:format("restore ~p -> ~p : ~p ~p ~p\n", [Src, Dst, D1, C, D2]). + +get_user_dir() -> + case os:type() of + {win32, _} -> + [{user_dir, filename:join([os:getenv("HOME"), ".ssh"])}]; + _ -> + [] + end. 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..1b7a9b23ed --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl @@ -0,0 +1,463 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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% +%% + +%% +-module(ssh_to_openssh_SUITE). + +-include("test_server.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) -> + crypto:start(), + Config. + +%%-------------------------------------------------------------------- +%% 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(doc) -> + ["Test ssh API"]; + +all(suite) -> + 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. + +%% 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) -> + Host = ssh_test_lib:hostname(), + {ok, ConnectionRef} = + ssh:connect(Host, ?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) -> + Host = ssh_test_lib:hostname(), + {ok, ConnectionRef} = + ssh:connect(Host, ?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) -> + SytemDir = ?config(data_dir, Config), + Host = ssh_test_lib:hostname(), + Port = ssh_test_lib:inet_port(), + + {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, + {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) -> + SytemDir = ?config(data_dir, Config), + Host = ssh_test_lib:hostname(), + Port = ssh_test_lib:inet_port(), + {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, + {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) -> + Host = ssh_test_lib:hostname(), + {ok, ConnectionRef} = + ssh:connect(Host, ?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), + Host = ssh_test_lib:hostname(), + + case ssh_test_lib:copyfile(SrcDir, UserDir, "id_rsa") of + {ok, _} -> + {ok, ConnectionRef} = + ssh:connect(Host, ?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), + Host = ssh_test_lib:hostname(), + case ssh_test_lib:copyfile(SrcDir, UserDir, "id_dsa") of + {ok, _} -> + {ok, ConnectionRef} = + ssh:connect(Host, ?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) -> + SytemDir = ?config(data_dir, Config), + Host = ssh_test_lib:hostname(), + Port = ssh_test_lib:inet_port(), + {ok, Pid} = ssh:daemon(Port, [{system_dir, SytemDir}, + {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), + Host = ssh_test_lib:hostname(), + + {error, Reason0} = + ssh:connect(Host, ?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:connect(Host, ?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. diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key new file mode 100644 index 0000000000..58f0a65cba --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQC3s+bZJWOQnRXkzKLPPfaQOouLuLgrbM4Ac63QZOnJeRVas3c1 +jBk0Isp506RrKzhEop8z9OiKfqRteVntjjkcILwsQ/1veWgojdP/jHYl6pbJm6AQ +ETM7GvkpgRDTd4Bf/rbrhABczl1NatnJhMsES8n2zNiiAVRP0woVmMNnkQIVANUe +uFb3EPdFwPEjilQ5jANHQc7pAoGBAJSzGD9KW4AZYB0FTt/2rwB5VjayKudi8ZO0 +nTyVoDLz40yvWerL/PJMbAnMnbY7zuN/Y9cqnMJOdBkHPvOpLQVls/d/x5CHZxcq +mn3n+Jplr5tlKugpUCkvgNALH2o/DMrPh1DIiPqrH3Y0W8iKcG+zF9Z7FXbCswC5 +2TTFtuwNAoGAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVS +QGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2C +ZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4cCFFmAnw67 ++basD1iibtNHs9Edfdkm +-----END DSA PRIVATE KEY----- diff --git a/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key.pub new file mode 100644 index 0000000000..d83487fc50 --- /dev/null +++ b/lib/ssh/test/ssh_to_openssh_SUITE_data/ssh_host_dsa_key.pub @@ -0,0 +1 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBALez5tklY5CdFeTMos899pA6i4u4uCtszgBzrdBk6cl5FVqzdzWMGTQiynnTpGsrOESinzP06Ip+pG15We2OORwgvCxD/W95aCiN0/+MdiXqlsmboBARMzsa+SmBENN3gF/+tuuEAFzOXU1q2cmEywRLyfbM2KIBVE/TChWYw2eRAAAAFQDVHrhW9xD3RcDxI4pUOYwDR0HO6QAAAIEAlLMYP0pbgBlgHQVO3/avAHlWNrIq52Lxk7SdPJWgMvPjTK9Z6sv88kxsCcydtjvO439j1yqcwk50GQc+86ktBWWz93/HkIdnFyqafef4mmWvm2Uq6ClQKS+A0Asfaj8Mys+HUMiI+qsfdjRbyIpwb7MX1nsVdsKzALnZNMW27A0AAACAfEIAb3mLjtFfiF/tsZb4/DGHdWSb6Ir0hFkoBUZ9ymBO70wlfZVSQGs240kZtOMpAOpJL1Dy8oH6PUQ+JyacwZIo8fdq19/Kwm6CPrpaEhzErmMvwT2CZJYZ+HOk55ljLkVCiyG7MzEj2+odLKym9yoQsbsJolHzIRpkLk45y4c= jakob@balin -- cgit v1.2.3