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