aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/html/SSH_protocols.pngbin0 -> 15381 bytes
-rw-r--r--lib/ssh/doc/man6/.gitignore0
-rw-r--r--lib/ssh/doc/src/Makefile35
-rw-r--r--lib/ssh/doc/src/SSH_protocols.pngbin0 -> 15381 bytes
-rw-r--r--lib/ssh/doc/src/book.xml7
-rw-r--r--lib/ssh/doc/src/fascicules.xml5
-rw-r--r--lib/ssh/doc/src/introduction.xml54
-rw-r--r--lib/ssh/doc/src/notes.xml52
-rw-r--r--lib/ssh/doc/src/ref_man.xml15
-rw-r--r--lib/ssh/doc/src/ssh.xml211
-rw-r--r--lib/ssh/doc/src/ssh_app.xml100
-rw-r--r--lib/ssh/doc/src/ssh_channel.xml349
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml124
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml260
-rw-r--r--lib/ssh/doc/src/ssh_protocol.xml150
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml90
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml65
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml51
-rw-r--r--lib/ssh/doc/src/user_guide.gifbin1581 -> 0 bytes
-rw-r--r--lib/ssh/doc/src/usersguide.xml37
-rw-r--r--lib/ssh/doc/src/using_ssh.xml298
-rw-r--r--lib/ssh/src/Makefile4
-rw-r--r--lib/ssh/src/ssh.app.src6
-rw-r--r--lib/ssh/src/ssh.appup.src18
-rw-r--r--lib/ssh/src/ssh.erl133
-rw-r--r--lib/ssh/src/ssh_auth.erl87
-rw-r--r--lib/ssh/src/ssh_auth.hrl6
-rw-r--r--lib/ssh/src/ssh_channel.erl37
-rw-r--r--lib/ssh/src/ssh_cli.erl3
-rw-r--r--lib/ssh/src/ssh_client_key.erl34
-rw-r--r--lib/ssh/src/ssh_client_key_api.erl35
-rw-r--r--lib/ssh/src/ssh_connection.erl76
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl50
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl174
-rw-r--r--lib/ssh/src/ssh_file.erl29
-rw-r--r--lib/ssh/src/ssh_io.erl63
-rw-r--r--lib/ssh/src/ssh_key_api.erl45
-rw-r--r--lib/ssh/src/ssh_server_key.erl33
-rw-r--r--lib/ssh/src/ssh_server_key_api.erl30
-rw-r--r--lib/ssh/src/ssh_sftpd.erl89
-rw-r--r--lib/ssh/src/ssh_subsystem.erl47
-rw-r--r--lib/ssh/src/ssh_transport.erl6
-rw-r--r--lib/ssh/src/ssh_xfer.erl5
-rw-r--r--lib/ssh/src/ssh_xfer.hrl12
-rw-r--r--lib/ssh/test/Makefile4
-rw-r--r--lib/ssh/test/ssh.spec.vxworks3
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl331
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl313
-rw-r--r--lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key15
-rw-r--r--lib/ssh/test/ssh_echo_server.erl71
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl288
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl294
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl165
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_sftpd_file_alt.erl2
-rw-r--r--lib/ssh/test/ssh_test_lib.erl19
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl176
-rw-r--r--lib/ssh/vsn.mk2
57 files changed, 3050 insertions, 1558 deletions
diff --git a/lib/ssh/doc/html/SSH_protocols.png b/lib/ssh/doc/html/SSH_protocols.png
new file mode 100644
index 0000000000..145c96c4cd
--- /dev/null
+++ b/lib/ssh/doc/html/SSH_protocols.png
Binary files differ
diff --git a/lib/ssh/doc/man6/.gitignore b/lib/ssh/doc/man6/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ssh/doc/man6/.gitignore
diff --git a/lib/ssh/doc/src/Makefile b/lib/ssh/doc/src/Makefile
index da99c4ea0f..0e79d9979f 100644
--- a/lib/ssh/doc/src/Makefile
+++ b/lib/ssh/doc/src/Makefile
@@ -37,21 +37,30 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = ref_man.xml
-XML_REF3_FILES = \
- ssh.xml \
+XML_REF3_FILES = ssh.xml \
ssh_channel.xml \
- ssh_connection.xml\
+ ssh_connection.xml \
+ ssh_client_key_api.xml \
+ ssh_server_key_api.xml \
ssh_sftp.xml \
ssh_sftpd.xml \
-XML_PART_FILES = part_notes.xml
-XML_CHAPTER_FILES = notes.xml
+XML_REF6_FILES = ssh_app.xml
+
+XML_PART_FILES = part_notes.xml \
+ usersguide.xml
+XML_CHAPTER_FILES = notes.xml \
+ introduction.xml \
+ ssh_protocol.xml \
+ using_ssh.xml
BOOK_FILES = book.xml
-XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \
+XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES)\
$(XML_PART_FILES) $(XML_CHAPTER_FILES)
+IMAGE_FILES = SSH_protocols.png
+
# ----------------------------------------------------
HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
@@ -62,10 +71,12 @@ EXTRA_FILES = \
$(DEFAULT_GIF_FILES) \
$(DEFAULT_HTML_FILES) \
$(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \
$(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html)
MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
+MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6)
HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
@@ -80,16 +91,19 @@ DVIPS_FLAGS +=
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-$(HTMLDIR)/%.gif: %.gif
+$(HTMLDIR)/%.png: %.png
$(INSTALL_DATA) $< $@
docs: pdf html man
$(TOP_PDF_FILE): $(XML_FILES)
+images: $(IMAGE_FILES:%=$(HTMLDIR)/%)
+
pdf: $(TOP_PDF_FILE)
-html: $(HTML_REF_MAN_FILE)
+html: images $(HTML_REF_MAN_FILE)
+
clean clean_docs:
rm -rf $(HTMLDIR)/*
@@ -97,7 +111,7 @@ clean clean_docs:
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
rm -f errs core *~
-man: $(MAN3_FILES)
+man: $(MAN3_FILES) $(MAN6_FILES)
debug opt:
@@ -117,5 +131,8 @@ release_docs_spec: docs
$(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)"
$(INSTALL_DIR) "$(RELEASE_PATH)/man/man3"
$(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3"
+ $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6"
+ $(INSTALL_DATA) $(MAN6_FILES) "$(RELEASE_PATH)/man/man6"
+
release_spec:
diff --git a/lib/ssh/doc/src/SSH_protocols.png b/lib/ssh/doc/src/SSH_protocols.png
new file mode 100644
index 0000000000..145c96c4cd
--- /dev/null
+++ b/lib/ssh/doc/src/SSH_protocols.png
Binary files differ
diff --git a/lib/ssh/doc/src/book.xml b/lib/ssh/doc/src/book.xml
index fcec1d6f70..3c2375f96d 100644
--- a/lib/ssh/doc/src/book.xml
+++ b/lib/ssh/doc/src/book.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE book SYSTEM "book.dtd">
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>2005</year><year>2010</year>
+ <year>2005</year><year>2012</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -34,6 +34,9 @@
<preamble>
<contents level="2"></contents>
</preamble>
+ <parts lift="yes">
+ <xi:include href="usersguide.xml"/>
+ </parts>
<applications>
<xi:include href="ref_man.xml"/>
</applications>
diff --git a/lib/ssh/doc/src/fascicules.xml b/lib/ssh/doc/src/fascicules.xml
index 43090b4aed..069d9002e0 100644
--- a/lib/ssh/doc/src/fascicules.xml
+++ b/lib/ssh/doc/src/fascicules.xml
@@ -1,7 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
<fascicules>
+ <fascicule file="usersguide" href="usersguide_frame.html" entry="no">
+ User's Guide
+ </fascicule>
<fascicule file="ref_man" href="ref_man_frame.html" entry="yes">
Reference Manual
</fascicule>
diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml
new file mode 100644
index 0000000000..aac8de0f76
--- /dev/null
+++ b/lib/ssh/doc/src/introduction.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Introduction</title>
+ <prepared>OTP team</prepared>
+ <file>introduction.xml</file>
+ </header>
+
+ <section>
+ <title>Purpose</title>
+
+ <p>Secure Shell (SSH) is a protocol for secure remote login and
+ other secure network services over an insecure network. SSH
+ provides a single, full-duplex, byte-oriented connection between
+ client and server. The protocol also provides privacy, integrity,
+ server authentication and man-in-the-middle protection.</p>
+
+ <p>The Erlang SSH application is an implementation of the SSH
+ protocol in Erlang which offers API functions to write customized
+ SSH clients and servers as well as making the Erlang shell
+ available via SSH. Also included in the SSH application are an
+ SFTP (SSH File Transfer Protocol) client <seealso
+ marker="ssh_sftp">ssh_sftp</seealso> and server <seealso
+ marker="ssh_sftp">ssh_sftpd</seealso>.</p>
+ </section>
+
+ <section>
+ <title>Prerequisites</title>
+ <p>It is assumed that the reader is familiar with the concepts of <seealso marker="doc/design_principals:users_guide">OTP</seealso>
+ and has a basic understanding of <url href="http://en.wikipedia.org/wiki/Public-key_cryptography">public keys</url>.</p>
+ </section>
+
+</chapter>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 36010da07a..e58d4e2c36 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -29,6 +29,50 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 2.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSH quiet mode</p>
+ <p>
+ A new option to ssh:connect/3,4, quiet_mode. If true, the
+ client will not print out anything on authorization.</p>
+ <p>
+ Own Id: OTP-10429 Aux Id: kunagi-273 [184] </p>
+ </item>
+ <item>
+ <p>
+ Restrict which key algorithms to use</p>
+ <p>
+ A new option to ssh:connect/3,4 is introduced,
+ public_key_algs, where you can restrict which key
+ algorithms to use and in which order to try them.</p>
+ <p>
+ Own Id: OTP-10498 Aux Id: kunagi-289 [200] </p>
+ </item>
+ <item>
+ <p>
+ Confidentiality of client password</p>
+ <p>
+ Unsets clients password after authentication.</p>
+ <p>
+ Own Id: OTP-10511 Aux Id: kunagi-292 [203] </p>
+ </item>
+ <item>
+ <p>
+ Fixed user interaction for SSH</p>
+ <p>
+ It's now available to accept hosts and input password</p>
+ <p>
+ Own Id: OTP-10513 Aux Id: kunagi-293 [204] </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 2.1.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -736,7 +780,7 @@
<list>
<item>
<p>
- Ssh timeouts will now behave as expected e.i. defaults to
+ Ssh timeouts will now behave as expected i.e. defaults to
infinity only the user of the ssh application can know of
a reasonable timeout value for their application.</p>
<p>
@@ -789,7 +833,7 @@
caused the ssh daemon to close the connections to all
currently logged in users if one user logged out. Another
problem related to the supervision tree caused the closing
- down of clients to leak processes e.i. all processes was
+ down of clients to leak processes i.e. all processes was
not shutdown correctly.</p>
<p>
Own Id: OTP-7676</p>
@@ -881,7 +925,7 @@
slightly changes the options to the API function
ssh:daemon/[1,2,3] deprecating all no longer documented
options. Note that the new API enforces the "logical way"
- of using the old API e.i. making the subsystem process
+ of using the old API i.e. making the subsystem process
part of the ssh applications supervisor tree, so missuses
of the old API are not compatible with the new API.</p>
<p>
diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml
index 9ab56b28ec..88203b5034 100644
--- a/lib/ssh/doc/src/ref_man.xml
+++ b/lib/ssh/doc/src/ref_man.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE application SYSTEM "application.dtd">
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2004</year><year>2010</year>
+ <year>2004</year><year>2012</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,19 +22,22 @@
</legalnotice>
<title>SSH Reference Manual</title>
- <prepared>Jakob Cederlund</prepared>
- <docno></docno>
+ <prepared>OTP</prepared>
<date>2007-10-06</date>
<rev>%VSN%</rev>
- <file>application.sgml</file>
+ <file>ref_man.xml</file>
</header>
<description>
<p>The SSH application is an erlang implementation of the
- secure shell protocol.</p>
+ secure shell protocol (SSH) as defined by RFC 4250 - 4254</p>
+
</description>
+ <xi:include href="ssh_app.xml"/>
<xi:include href="ssh.xml"/>
<xi:include href="ssh_channel.xml"/>
<xi:include href="ssh_connection.xml"/>
+ <xi:include href="ssh_client_key_api.xml"/>
+ <xi:include href="ssh_server_key_api.xml"/>
<xi:include href="ssh_sftp.xml"/>
<xi:include href="ssh_sftpd.xml"/>
</application>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index b84b3a3dcb..a0afb5056e 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -22,13 +22,7 @@
</legalnotice>
<title>ssh</title>
- <prepared>Ingela Anderton Andin</prepared>
- <responsible>H&aring;kan Mattsson</responsible>
- <docno></docno>
- <approved>H&aring;kan Mattsson</approved>
- <checked></checked>
<date>2007-10-06</date>
- <rev>PA1</rev>
</header>
<module>ssh</module>
<modulesummary>Main API of the SSH application</modulesummary>
@@ -40,80 +34,85 @@
<title>SSH</title>
<list type="bulleted">
- <item>ssh requires the crypto and public_key applications.</item>
- <item>Supported SSH-version is 2.0 </item>
- <item>Currently supports only a minimum of mac and encryption algorithms i.e.
- hmac-sha1, and aes128-cb and 3des-cbc.</item>
+ <item>SSH requires the crypto and public_key applications.</item>
+ <item>Supported SSH version is 2.0 </item>
+ <item>Supported MAC algorithms: hmac-sha1</item>
+ <item>Supported encryption algorithms: aes128-cb and 3des-cbc</item>
</list>
</section>
<section>
- <title>COMMON DATA TYPES </title>
+ <title>DATA TYPES </title>
<p>Type definitions that are used more than once in
- this module:</p>
+ this module and/or abstractions to indicate the intended use of the data
+ type:</p>
<p><c>boolean() = true | false </c></p>
- <p><c>string() = list of ASCII characters</c></p>
+ <p><c>string() = [byte()]</c></p>
<p><c>ssh_daemon_ref() - opaque to the user
returned by ssh:daemon/[1,2,3]</c></p>
<p><c>ssh_connection_ref() - opaque to the user
returned by ssh:connect/3</c></p>
<p><c>ip_address() - {N1,N2,N3,N4} % IPv4 |
{K1,K2,K3,K4,K5,K6,K7,K8} % IPv6</c></p>
- <p><c>subsystem_spec() = {subsystem_name(), {channel_callback(), channel_init_args()}} </c></p>
+ <p><c>subsystem_spec() = {subsystem_name(),
+ {channel_callback(), channel_init_args()}} </c></p>
<p><c>subsystem_name() = string() </c></p>
<p><c>channel_callback() = atom() - Name of the erlang module
implementing the subsystem using the ssh_channel behavior see</c>
<seealso marker="ssh_channel">ssh_channel(3)</seealso></p>
<p><c>channel_init_args() = list()</c></p>
</section>
-
+
<funcs>
<func>
<name>close(ConnectionRef) -> ok </name>
- <fsummary>Closes a ssh connection</fsummary>
+ <fsummary>Closes an SSH connection</fsummary>
<type>
<v>ConnectionRef = ssh_connection_ref()</v>
</type>
- <desc><p>Closes a ssh connection.</p>
+ <desc><p>Closes an SSH connection.</p>
</desc>
</func>
<func>
<name>connect(Host, Port, Options) -> </name>
- <name>connect(Host, Port, Options, Timeout) -> {ok, ssh_connection_ref()}
- | {error, Reason}</name>
+ <name>connect(Host, Port, Options, Timeout) -> {ok,
+ ssh_connection_ref()} | {error, Reason}</name>
<fsummary>Connect to an ssh server.</fsummary>
<type>
<v>Host = string()</v>
<v>Port = integer()</v>
- <d>The default is <c><![CDATA[22]]></c>, the registered port for SSH.</d>
+ <d>The default is <c><![CDATA[22]]></c>, the assigned well known port
+ number for SSH.</d>
<v>Options = [{Option, Value}]</v>
<v>Timeout = infinity | integer(milliseconds)</v>
</type>
<desc>
- <p>Connects to an SSH server. No channel is started this is done
+ <p>Connects to an SSH server. No channel is started. This is done
by calling ssh_connect:session_channel/2.</p>
<p>Options are:</p>
<taglist>
<tag><c><![CDATA[{user_dir, string()}]]></c></tag>
<item>
- <p>Sets the user directory e.i. the directory containing
+ <p>Sets the user directory i.e. the directory containing
ssh configuration files for the user such as
- <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa, id_dsa]]></c> and
- <c><![CDATA[authorized_key]]></c>. Defaults to the directory normally
- referred to as <c><![CDATA[~/.ssh]]></c> </p>
+ <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa,
+ id_dsa]]></c> and
+ <c><![CDATA[authorized_key]]></c>. Defaults to the
+ directory normally referred to as
+ <c><![CDATA[~/.ssh]]></c> </p>
</item>
<tag><c><![CDATA[{dsa_pass_phrase, string()}]]></c></tag>
<item>
- <p>If the user dsa key is protected by a pass phrase it can be
+ <p>If the user dsa key is protected by a passphrase it can be
supplied with this option.
</p>
</item>
<tag><c><![CDATA[{rsa_pass_phrase, string()}]]></c></tag>
<item>
- <p>If the user rsa key is protected by a pass phrase it can be
+ <p>If the user rsa key is protected by a passphrase it can be
supplied with this option.
</p>
</item>
@@ -135,20 +134,26 @@
password. Do note that it may not always be desirable to use
those options from a security point of view.</p>
</item>
- <tag><c><![CDATA[{public_key_alg, ssh_rsa | ssh_dsa}]]></c></tag>
+ <tag><c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></tag>
<item>
<p>Sets the preferred public key algorithm to use for user
- authentication. If the the preferred algorithm fails of
+ authentication. If the the preferred algorithm fails for
some reason, the other algorithm is tried. The default is
to try <c><![CDATA[ssh_rsa]]></c> first.</p>
</item>
+ <tag><c><![CDATA[{pref_public_key_algs, list()}]]></c></tag>
+ <item>
+ <p>List of public key algorithms to try to use, 'ssh-rsa' and 'ssh-dss' available.
+ Will override <c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></p>
+ </item>
<tag><c><![CDATA[{connect_timeout, timeout()}]]></c></tag>
<item>
- <p>Sets a timeout on the transport layer connection. Defaults to infinity.</p>
+ <p>Sets a timeout on the transport layer
+ connection. Defaults to <c>infinity</c>.</p>
</item>
- <tag><c><![CDATA[{user, String}]]></c></tag>
+ <tag><c><![CDATA[{user, string()}]]></c></tag>
<item>
- <p>Provide a user name. If this option is not given, ssh
+ <p>Provides a user name. If this option is not given, ssh
reads from the environment (<c><![CDATA[LOGNAME]]></c> or
<c><![CDATA[USER]]></c> on unix,
<c><![CDATA[USERNAME]]></c> on Windows).</p>
@@ -160,37 +165,36 @@
password if the password authentication method is
attempted.</p>
</item>
- <tag><c><![CDATA[{user_auth, Fun/3}]]></c></tag>
- <item>
- <p>Provide a fun for password authentication. The fun
- will be called as <c><![CDATA[fun(User, Password, Opts)]]></c> and
- should return <c><![CDATA[true]]></c> or <c><![CDATA[false]]></c>.</p>
+ <tag><c><![CDATA[{key_cb, atom()}]]></c></tag>
+ <item>
+ <p>Module implementing the behaviour <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>.
+ Can be used to customize the handling of public keys.
+ </p>
</item>
- <tag><c><![CDATA[{key_cb, atom() = KeyCallbackModule}]]></c></tag>
+ <tag><c><![CDATA[{quiet_mode, atom() = boolean()}]]></c></tag>
<item>
- <p>Provide a special call-back module for key handling.
- The call-back module should be modeled after the
- <c><![CDATA[ssh_file]]></c> module. The functions that must
- be exported are:
- <c><![CDATA[private_host_rsa_key/2]]></c>,
- <c><![CDATA[private_host_dsa_key/2]]></c>,
- <c><![CDATA[lookup_host_key/3]]></c> and
- <c><![CDATA[add_host_key/3]]></c>. This is considered
- somewhat experimental and will be better documented later on.</p>
+ <p>If true, the client will not print out anything on authorization.</p>
</item>
<tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag>
<item>
- <p>Allow an existing file-descriptor to be used
+ <p>Allow an existing file descriptor to be used
(simply passed on to the transport protocol).</p></item>
- <tag><c><![CDATA[{ip_v6_disabled, boolean()}]]></c></tag>
+ <tag><c><![CDATA[{ipv6_disabled, boolean()}]]></c></tag>
+ <item>
+ <p>Determines if SSH shall use IPv6 or not.</p>
+ </item>
+ <tag><c><![CDATA[{rekey_limit, integer()}]]></c></tag>
<item>
- <p>Determines if SSH shall use IPv6 or not.</p></item>
+ <p>Provide, in bytes, when rekeying should be initiated,
+ defaults to one time each GB and one time per hour.</p>
+ </item>
</taglist>
</desc>
</func>
<func>
- <name>connection_info(ConnectionRef, [Option]) ->[{Option, Value}] </name>
+ <name>connection_info(ConnectionRef, [Option]) ->[{Option,
+ Value}] </name>
<fsummary> Retrieves information about a connection. </fsummary>
<type>
<v>Option = client_version | server_version | peer</v>
@@ -205,7 +209,8 @@
<func>
<name>daemon(Port) -> </name>
<name>daemon(Port, Options) -> </name>
- <name>daemon(HostAddress, Port, Options) -> ssh_daemon_ref()</name>
+ <name>daemon(HostAddress, Port, Options) -> {ok,
+ ssh_daemon_ref()} | {error, atom()}</name>
<fsummary>Starts a server listening for SSH connections
on the given port.</fsummary>
<type>
@@ -216,30 +221,32 @@
<v>Value = term()</v>
</type>
<desc>
- <p>Starts a server listening for SSH connections on the given port.</p>
-
- <p>Options are:</p>
+ <p>Starts a server listening for SSH connections on the given
+ port.</p>
+ <p>Options are:</p>
<taglist>
<tag><c><![CDATA[{subsystems, [subsystem_spec()]]]></c></tag>
<item>
- Provides specifications for handling of subsystems. The
- "sftp" subsystem-spec can be retrieved by calling
- ssh_sftpd:subsystem_spec/1. If the subsystems option in not present
- the value of <c>[ssh_sftpd:subsystem_spec([])]</c> will be used.
- It is of course possible to set the option to the empty list
- if you do not want the daemon to run any subsystems at all.
+ Provides specifications for handling of subsystems. The
+ "sftp" subsystem spec can be retrieved by calling
+ ssh_sftpd:subsystem_spec/1. If the subsystems option in
+ not present the value of
+ <c>[ssh_sftpd:subsystem_spec([])]</c> will be used. It is
+ of course possible to set the option to the empty list if
+ you do not want the daemon to run any subsystems at all.
</item>
- <tag><c><![CDATA[{shell, {Module, Function, Args} | fun(string() = User) - > pid() |
- fun(string() = User, ip_address() = PeerAddr) -> pid()}]]></c></tag>
+ <tag><c><![CDATA[{shell, {Module, Function, Args} |
+ fun(string() = User) - > pid() | fun(string() = User,
+ ip_address() = PeerAddr) -> pid()}]]></c></tag>
<item>
- Defines the read-eval-print loop used when a shell is requested
- by the client. Example use the
- erlang shell: <c><![CDATA[{shell, start, []}]]></c> which is
- the default behavior.
+ Defines the read-eval-print loop used when a shell is
+ requested by the client. Default is to use the erlang shell:
+ <c><![CDATA[{shell, start, []}]]></c>
</item>
- <tag><c><![CDATA[{ssh_cli,{channel_callback(), channel_init_args()}}]]></c></tag>
+ <tag><c><![CDATA[{ssh_cli,{channel_callback(),
+ channel_init_args()}}]]></c></tag>
<item>
- Provide your own cli implementation, e.i. a channel callback
+ Provides your own cli implementation, i.e. a channel callback
module that implements a shell and command execution. Note
that you may customize the shell read-eval-print loop using the
option <c>shell</c> which is much less work than implementing
@@ -247,27 +254,30 @@
</item>
<tag><c><![CDATA[{user_dir, String}]]></c></tag>
<item>
- <p>Sets the user directory e.i. the directory containing
+ <p>Sets the user directory i.e. the directory containing
ssh configuration files for the user such as
- <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa, id_dsa]]></c> and
- <c><![CDATA[authorized_key]]></c>. Defaults to the directory normally
- referred to as <c><![CDATA[~/.ssh]]></c> </p>
+ <c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa,
+ id_dsa]]></c> and
+ <c><![CDATA[authorized_key]]></c>. Defaults to the
+ directory normally referred to as
+ <c><![CDATA[~/.ssh]]></c> </p>
</item>
<tag><c><![CDATA[{system_dir, string()}]]></c></tag>
<item>
- <p>Sets the system directory, containing the host files
- that identifies the host for ssh. The default is
- <c><![CDATA[/etc/ssh]]></c>, note that SSH normally
- requires the host files there to be readable only by
- root.</p>
+ <p>Sets the system directory, containing the host key files
+ that identifies the host keys for ssh. The default is
+ <c><![CDATA[/etc/ssh]]></c>, note that for security reasons
+ this directory is normally only accessible by the root user.</p>
</item>
<tag><c><![CDATA[{auth_methods, string()}]]></c></tag>
<item>
- <p>Comma separated string that determines which authentication methodes that the server
- should support and in what order they will be tried. Defaults to
- <c><![CDATA["publickey,keyboard_interactive,password"]]></c></p>
+ <p>Comma separated string that determines which
+ authentication methodes that the server should support and
+ in what order they will be tried. Defaults to
+ <c><![CDATA["publickey,keyboard-interactive,password"]]></c></p>
</item>
- <tag><c><![CDATA[{user_passwords, [{string() = User, string() = Password}]}]]></c></tag>
+ <tag><c><![CDATA[{user_passwords, [{string() = User,
+ string() = Password}]}]]></c></tag>
<item>
<p>Provide passwords for password authentication.They will
be used when someone tries to connect to the server and
@@ -281,13 +291,19 @@
user. From a security perspective this option makes
the server very vulnerable.</p>
</item>
- <tag><c><![CDATA[{pwdfun, fun/2}]]></c></tag>
+ <tag><c><![CDATA[{pwdfun, fun(User::string(), password::string() -> boolean()}]]></c></tag>
<item>
<p>Provide a function for password validation. This is called
with user and password as strings, and should return
<c><![CDATA[true]]></c> if the password is valid and
<c><![CDATA[false]]></c> otherwise.</p>
</item>
+ <tag><c><![CDATA[{key_cb, atom()}]]></c></tag>
+ <item>
+ <p>Module implementing the behaviour <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ Can be used to customize the handling of public keys.
+ </p>
+ </item>
<tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag>
<item>
<p>Allow an existing file-descriptor to be used
@@ -296,6 +312,18 @@
<item>
<p>Determines if SSH shall use IPv6 or not (only used when
HostAddress is set to any).</p></item>
+ <tag><c><![CDATA[{failfun, fun()}]]></c></tag>
+ <item>
+ <p>Provide a fun() to implement your own logging when a user fails to authenticate.</p>
+ </item>
+ <tag><c><![CDATA[{connectfun, fun()}]]></c></tag>
+ <item>
+ <p>Provide a fun() to implement your own logging when a user authenticates to the server.</p>
+ </item>
+ <tag><c><![CDATA[{disconnectfun, fun()}]]></c></tag>
+ <item>
+ <p>Provide a fun() to implement your own logging when a user disconnects from the server.</p>
+ </item>
</taglist>
</desc>
</func>
@@ -311,9 +339,9 @@
<v> Options - see ssh:connect/3</v>
</type>
<desc>
- <p>Starts an interactive shell to an SSH server on the
+ <p>Starts an interactive shell via an SSH server on the
given <c>Host</c>. The function waits for user input,
- and will not return until the remote shell is ended (e.g. on
+ and will not return until the remote shell is ended (i.e.
exit from the shell).
</p>
</desc>
@@ -322,25 +350,24 @@
<func>
<name>start() -> </name>
<name>start(Type) -> ok | {error, Reason}</name>
- <fsummary>Starts the Ssh application. </fsummary>
+ <fsummary>Starts the SSH application. </fsummary>
<type>
<v>Type = permanent | transient | temporary</v>
<v>Reason = term() </v>
</type>
<desc>
- <p>Starts the Ssh application. Default type
- is temporary. See also
- <seealso marker="kernel:application">application(3)</seealso>
- Requires that the crypto application has been started.
+ <p>Utility function that starts crypto, public_key and the SSH
+ application. Defult type is temporary.
+ See also <seealso marker="kernel:application">application(3)</seealso>
</p>
</desc>
</func>
<func>
<name>stop() -> ok </name>
- <fsummary>Stops the Ssh application.</fsummary>
+ <fsummary>Stops the SSH application.</fsummary>
<desc>
- <p>Stops the Ssh application. See also
+ <p>Stops the SSH application. See also
<seealso marker="kernel:application">application(3)</seealso></p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml
new file mode 100644
index 0000000000..c01f44936a
--- /dev/null
+++ b/lib/ssh/doc/src/ssh_app.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE appref SYSTEM "appref.dtd">
+
+<appref>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>SSH</title>
+ <file>ssh_app.xml</file>
+ </header>
+ <app>SSH</app>
+ <appsummary>The ssh application implements the SSH (Secure Shell) protocol and
+ provides an SFTP (SSH File Transfer Protocol) client and server. </appsummary>
+
+ <section>
+ <title>DEPENDENCIES</title>
+ <p>The ssh application uses the Erlang applications public_key and
+ crypto to handle public keys and encryption, hence these
+ applications needs to be loaded for the ssh application to work. In
+ an embedded environment that means they need to be started with
+ application:start/[1,2] before the ssh application is started.
+ </p>
+ </section>
+
+ <section>
+ <title>CONFIGURATION</title>
+
+ <p>The ssh application does not currently have an application
+ specific configuration file as described in application(3),
+ however it will by default use the following configuration files
+ from openssh: known_hosts, authorized_keys, authorized_keys2,
+ id_dsa and id_rsa, ssh_host_dsa_key and ssh_host_rsa_key. By
+ default Erlang SSH will look for id_dsa, id_rsa, known_hosts
+ and authorized_keys in ~/.ssh, and the host key files in /etc/ssh
+ . These locations may be changed by the options user_dir and
+ system_dir. Public key handling may also be customized by
+ providing a callback module implementing the behaviors
+ <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso> and
+ <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ </p>
+
+ <section>
+ <title>PUBLIC KEYS</title>
+ <p>
+ id_dsa and id_rsa are the users private key files, note that
+ the public key is part of the private key so the ssh
+ application will not use the id_&lt;*>.pub files. These are
+ for the users convenience when he/she needs to convey their
+ public key.
+ </p>
+ </section>
+
+ <section>
+ <title>KNOW HOSTS</title>
+ <p>The known_hosts file contains a list of approved servers and
+ their public keys. Once a server is listed, it can be verified
+ without user interaction.
+ </p>
+ </section>
+
+ <section>
+ <title>AUTHORIZED KEYS</title>
+ <p>The authorized key file keeps track of the user's authorized
+ public keys. The most common use of this file is to let users
+ log in without entering their password which is supported by the
+ Erlang SSH daemon.
+ </p>
+ </section>
+
+ <section>
+ <title>HOST KEYS</title>
+ <p>Currently rsa and dsa host keys are supported and are
+ expected to be found in files named ssh_host_rsa_key and
+ ssh_host_dsa_key.
+ </p>
+ </section>
+ </section>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p>application(3)</p>
+ </section>
+
+</appref>
diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml
index c2b7aa94a5..f0083ae8d1 100644
--- a/lib/ssh/doc/src/ssh_channel.xml
+++ b/lib/ssh/doc/src/ssh_channel.xml
@@ -1,11 +1,11 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
<header>
<copyright>
<year>2009</year>
- <year>2009</year>
+ <year>2012</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -22,31 +22,35 @@
The Initial Developer of the Original Code is Ericsson AB.
</legalnotice>
-
<title>ssh_channel</title>
- <prepared>Ingela Anderton Andin</prepared>
- <responsible></responsible>
- <docno></docno>
- <approved></approved>
- <checked></checked>
- <date></date>
- <rev></rev>
</header>
<module>ssh_channel</module>
- <modulesummary>Generic Ssh Channel Behavior
+ <modulesummary>-behaviour(ssh_channel).
</modulesummary>
<description>
- <p>Ssh services are implemented as channels that are multiplexed
- over an ssh connection and communicates via the ssh connection
- protocol. This module provides a callback API that takes care of
- generic channel aspects such as flow control and close messages
- and lets the callback functions take care of the service specific
- parts.
+ <p>SSH services (clients and servers) are implemented as channels
+ that are multiplexed over an SSH connection and communicates via
+ the <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH
+ Connection Protocol </url>. This module provides a callback API
+ that takes care of generic channel aspects such as flow control
+ and close messages and lets the callback functions take care of
+ the service (application) specific parts. This behavior also ensures
+ that the channel process honors the principal of an OTP-process so
+ that it can be part of a supervisor tree. This is a requirement of
+ channel processes implementing a subsystem that will be added to
+ the SSH applications supervisor tree.
</p>
+
+ <note> When implementing a SSH subsystem use the
+ <c>-behaviour(ssh_subsystem).</c> instead of <c>-behaviour(ssh_channel).</c>
+ as the only relevant callback functions for subsystems are
+ init/1, handle_ssh_msg/2, handle_msg/2 and terminate/2, so the ssh_subsystem
+ behaviour is limited version of the ssh_channel behaviour.
+ </note>
</description>
<section>
- <title>COMMON DATA TYPES </title>
+ <title>DATA TYPES </title>
<p>Type definitions that are used more than once in this module
and/or abstractions to indicate the intended use of the data
@@ -56,10 +60,10 @@
<p><c>string() = list of ASCII characters</c></p>
<p><c>timeout() = infinity | integer() - in milliseconds.</c></p>
<p><c>ssh_connection_ref() - opaque to the user returned by
- ssh:connect/3 or sent to a ssh channel process</c></p>
+ ssh:connect/3 or sent to an SSH channel process</c></p>
<p><c>ssh_channel_id() = integer() </c></p>
<p><c>ssh_data_type_code() = 1 ("stderr") | 0 ("normal") are
- currently valid values see RFC 4254 section 5.2.</c></p>
+ currently valid values see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 5.2.</c></p>
</section>
<funcs>
@@ -74,13 +78,15 @@
<v>Timeout = timeout() </v>
<v>Reply = term() </v>
<v>Reason = closed | timeout </v>
+
</type>
<desc>
<p>Makes a synchronous call to the channel process by sending
a message and waiting until a reply arrives or a timeout
- occurs. The channel will call
- <c>CallbackModule:handle_call/3</c> to handle the message.
- If the channel process does not exist <c>{error, closed}</c> is returned.
+ occurs. The channel will call <seealso mark =
+ "#Module:handle_call-3">Module:handle_call/3</seealso>
+ to handle the message. If the channel process does not exist
+ <c>{error, closed}</c> is returned.
</p>
</desc>
</func>
@@ -98,26 +104,27 @@
<p>Sends an asynchronous message to the channel process and
returns ok immediately, ignoring if the destination node or
channel process does not exist. The channel will call
- <c>CallbackModule:handle_cast/2</c> to handle the message.
+ <seealso mark = "#Module:handle_cast-3">Module:handle_cast/2</seealso>
+ to handle the message.
</p>
</desc>
</func>
<func>
<name>enter_loop(State) -> _ </name>
- <fsummary> Makes an existing process into a ssh_channel process. </fsummary>
+ <fsummary> Makes an existing process an ssh_channel process. </fsummary>
<type>
- <v> State = term() - as returned by ssh_channel:init/1</v>
+ <v> State = term() - as returned by <seealso mark = "#init-1">ssh_channel:init/1</seealso></v>
</type>
<desc>
- <p> Makes an existing process into a <c>ssh_channel</c>
+ <p> Makes an existing process an <c>ssh_channel</c>
process. Does not return, instead the calling process will
- enter the <c>ssh_channel</c> process receive loop and become a
+ enter the <c>ssh_channel</c> process receive loop and become an
<c>ssh_channel process.</c> The process must have been started using
one of the start functions in proc_lib, see <seealso
marker="stdlib:proc_lib">proc_lib(3)</seealso>. The
user is responsible for any initialization of the process
- and needs to call ssh_channel:init/1.
+ and needs to call <seealso mark = "#init-1">ssh_channel:init/1</seealso>
</p>
</desc>
</func>
@@ -126,7 +133,10 @@
<name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
<fsummary> Initiates a ssh_channel process.</fsummary>
<type>
- <v> Options = [{Option, Value}]</v>
+ <v>Options = [{Option, Value}]</v>
+ <v>State = term()</v>
+ <v>Timeout = timeout() </v>
+ <v>Reason = term() </v>
</type>
<desc>
<p>
@@ -134,24 +144,27 @@
</p>
<taglist>
<tag><c><![CDATA[{channel_cb, atom()}]]></c></tag>
- <item>The module that implements the channel behavior.</item>
+ <item>The module that implements the channel behaviour.</item>
<tag><c><![CDATA[{init_args(), list()}]]></c></tag>
- <item> The list of arguments to the callback modules
+ <item> The list of arguments to the callback module's
init function.</item>
<tag><c><![CDATA[{cm, connection_ref()}]]></c></tag>
- <item> Reference to the ssh connection.</item>
+ <item> Reference to the ssh connection as returned by <seealso
+ marker="ssh#connect-3">ssh:connect/3</seealso></item>
<tag><c><![CDATA[{channel_id, channel_id()}]]></c></tag>
<item> Id of the ssh channel.</item>
</taglist>
- <note><p>This function is normally not called by the user, it is
- only needed if for some reason the channel process needs
- to be started with help of <c>proc_lib</c> instead calling
- <c>ssh_channel:start/4</c> or <c>ssh_channel:start_link/4</c> </p>
+ <note><p>This function is normally not called by the
+ user. The user only needs to call if for some reason the
+ channel process needs to be started with help of
+ <c>proc_lib</c> instead of calling
+ <c>ssh_channel:start/4</c> or
+ <c>ssh_channel:start_link/4</c> </p>
</note>
</desc>
</func>
@@ -167,12 +180,12 @@
<p>This function can be used by a channel to explicitly send a
reply to a client that called <c>call/[2,3]</c> when the reply
cannot be defined in the return value of
- <c>CallbackModule:handle_call/3</c>.</p>
+ <seealso marker ="#Module:handle_call-3">Module:handle_call/3</seealso>.</p>
<p><c>Client</c> must be the <c>From</c> argument provided to
the callback function <c>handle_call/3</c>.
<c>Reply</c> is an arbitrary term,
- which will be given back to the client as the return value of
- <c>ssh_channel:call/[2,3].</c></p>
+ which will be given back to the client as the return value of
+ <seealso marker="#call-2">ssh_channel:call/[2,3].</seealso>></p>
</desc>
</func>
@@ -180,11 +193,12 @@
<name>start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name>
<name>start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) ->
{ok, ChannelRef} | {error, Reason}</name>
- <fsummary> Starts a processes that handles a ssh channel. </fsummary>
+ <fsummary> Starts a processes that handles a SSH channel. </fsummary>
<type>
<v>SshConnection = ssh_connection_ref()</v>
<v>ChannelId = ssh_channel_id() </v>
- <d> As returned by ssh_connection:session_channel/[2,4]</d>
+ <d> As returned by cannot be defined in the return value of
+ <seealso marker ="ssh_connection#session_channel/2">ssh_connection:session_channel/[2,4]</seealso></d>
<v>ChannelCb = atom()</v>
<d> The name of the module implementing the service specific parts
of the channel.</d>
@@ -193,10 +207,10 @@
<v>ChannelRef = pid()</v>
</type>
<desc>
- <p>Starts a processes that handles a ssh channel. Will be
- called internally by the ssh daemon or explicitly by the ssh
- client implementations. A channel process traps exit signals
- by default.
+ <p>Starts a processes that handles an SSH channel. It will be
+ called internally by the SSH daemon or explicitly by the SSH
+ client implementations. The behavior will set the
+ <c>trap_exit</c> flag to true.
</p>
</desc>
</func>
@@ -204,72 +218,61 @@
</funcs>
<section>
- <title>CALLBACK FUNCTIONS</title>
-
- <p>The functions init/1, terminate/2, handle_ssh_msg/2 and
- handle_msg/2 are the functions that are required to provide the
- implementation for a server side channel, such as a ssh subsystem
- channel that can be plugged into the erlang ssh daemon see
- <seealso marker="ssh">ssh:daemon/[2, 3]</seealso>. The
- handle_call/3, handle_cast/2 code_change/3 and enter_loop/1
- functions are only relevant when implementing a client side
- channel.</p>
- </section>
-
- <section>
<marker id="cb_timeouts"></marker>
<title> CALLBACK TIMEOUTS</title>
- <p> If an integer timeout value is provided in a return value of
- one of the callback functions, a timeout will occur unless a
- message is received within <c>Timeout</c> milliseconds. A timeout
- is represented by the atom <c>timeout</c> which should be handled
- by the <seealso marker="#handle_msg">handle_msg/2</seealso>
- callback function. The atom infinity can be used to wait
- indefinitely, this is the default value. </p>
+
+ <p>The timeout values that may be returned by the callback functions
+ has the same semantics as in a <seealso marker="stdlib#gen_server">gen_server</seealso>
+ If the timeout occurs <seealso marker="#handle_msg">handle_msg/2</seealso>
+ will be called as <c>handle_msg(timeout, State). </c></p>
</section>
<funcs>
<func>
- <name>CallbackModule:code_change(OldVsn, State, Extra) -> {ok,
+ <name>Module:code_change(OldVsn, State, Extra) -> {ok,
NewState}</name>
<fsummary> Converts process state when code is changed.</fsummary>
<type>
- <v> Converts process state when code is changed.</v>
+ <v>OldVsn = term()</v>
+ <d>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
+ in the case of a downgrade, <c>OldVsn</c> is
+ <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
+ attribute(s) of the old version of the callback module
+ <c>Module</c>. If no such attribute is defined, the version is
+ the checksum of the BEAM file.</d>
+ <v>State = term()</v>
+ <d>The internal state of the channel.</d>
+ <v>Extra = term()</v>
+ <d>Passed as-is from the <c>{advanced,Extra}</c>
+ part of the update instruction.</d>
</type>
<desc>
- <p>This function is called by a client side channel when it
- should update its internal state during a release
- upgrade/downgrade, i.e. when the instruction
- <c>{update,Module,Change,...}</c> where
- <c>Change={advanced,Extra}</c> is given in the <c>appup</c>
- file. See <seealso
- marker="doc/design_principles:release_handling#instr">OTP
- Design Principles</seealso> for more information. Any new
- connection will benefit from a server side upgrade but
- already started connections on the server side will not be
- affected.
- </p>
+ <p> Converts process state when code is changed.</p>
+
+ <p>This function is called by a client side channel when it
+ should update its internal state during a release
+ upgrade/downgrade, i.e. when the instruction
+ <c>{update,Module,Change,...}</c> where
+ <c>Change={advanced,Extra}</c> is given in the <c>appup</c>
+ file. See <seealso marker="doc/design_principles:release_handling#instr">OTP
+ Design Principles</seealso> for more information.
+ </p>
- <note><p>If there are long lived ssh connections and more
- than one upgrade in a short time this may cause the old
- connections to fail as only two versions of the code may
- be loaded simultaneously.</p></note>
+ <note><p>Soft upgrade according to the OTP release concept
+ is not straight forward for the server side, as subsystem
+ channel processes are spawned by the SSH application and
+ hence added to its supervisor tree. It could be possible to
+ upgrade the subsystem channels, when upgrading the user
+ application, if the callback functions can handle two
+ versions of the state, but this function can not be used in
+ the normal way.</p>
+ </note>
- <p>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
- in the case of a downgrade, <c>OldVsn</c> is
- <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c>
- attribute(s) of the old version of the callback module
- <c>Module</c>. If no such attribute is defined, the version
- is the checksum of the BEAM file.</p>
- <p><c>State</c> is the internal state of the channel.</p>
- <p><c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c>
- part of the update instruction.</p>
- <p>The function should return the updated internal state.</p>
</desc>
</func>
<func>
- <name>CallbackModule:init(Args) -> {ok, State} | {ok, State, Timeout} |
+ <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
<fsummary> Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
@@ -277,7 +280,6 @@
<v> Args = term() </v>
<d> Last argument to ssh_channel:start_link/4.</d>
<v> State = term() </v>
- <v>Timeout = timeout() </v>
<v> Reason = term() </v>
</type>
<desc>
@@ -290,7 +292,7 @@
</func>
<func>
- <name>CallbackModule:handle_call(Msg, From, State) -> Result</name>
+ <name>Module:handle_call(Msg, From, State) -> Result</name>
<fsummary> Handles messages sent by calling
<c>ssh_channel:call/[2,3]</c></fsummary>
<type>
@@ -298,17 +300,16 @@
<v>From = opaque to the user should be used as argument to
ssh_channel:reply/2</v>
<v>State = term()</v>
- <v>Result = {reply, Reply, NewState} | {reply, Reply, NewState, Timeout}
- | {noreply, NewState} | {noreply , NewState, Timeout}
+ <v>Result = {reply, Reply, NewState} | {reply, Reply, NewState, timeout()}
+ | {noreply, NewState} | {noreply , NewState, timeout()}
| {stop, Reason, Reply, NewState} | {stop, Reason, NewState} </v>
<v>Reply = term() - will be the return value of ssh_channel:call/[2,3]</v>
- <v>Timeout = timeout() </v>
- <v>NewState = term() - a possible updated version of State</v>
+ <v>NewState = term()</v>
<v>Reason = term()</v>
</type>
<desc>
<p>Handles messages sent by calling
- <c>ssh_channel:call/[2,3]</c>
+ <seealso marker="#call-2">ssh_channel:call/[2,3]</seealso>
</p>
<p>For more detailed information on timeouts see the section
<seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p>
@@ -316,16 +317,15 @@
</func>
<func>
- <name>CallbackModule:handle_cast(Msg, State) -> Result</name>
+ <name>Module:handle_cast(Msg, State) -> Result</name>
<fsummary> Handles messages sent by calling
<c>ssh_channel:cact/2</c></fsummary>
<type>
<v>Msg = term()</v>
<v>State = term()</v>
- <v>Result = {noreply, NewState} | {noreply, NewState, Timeout}
+ <v>Result = {noreply, NewState} | {noreply, NewState, timeout()}
| {stop, Reason, NewState}</v>
- <v>NewState = term() - a possible updated version of State</v>
- <v>Timeout = timeout() </v>
+ <v>NewState = term() </v>
<v>Reason = term()</v>
</type>
<desc>
@@ -339,7 +339,7 @@
</func>
<func>
- <name>CallbackModule:handle_msg(Msg, State) -> {ok, State} |
+ <name>Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
<fsummary> Handle other messages than ssh connection protocol,
@@ -359,136 +359,37 @@
<taglist>
<tag><c><![CDATA[{ssh_channel_up, ssh_channel_id(),
ssh_connection_ref()}]]></c></tag>
- <item>This is the first messages that will be received
- by the channel, it is sent just before
- the ssh_channel:init/1 function returns successfully.
- This is especially useful if the server wants
- to send a message to the client without first receiving
- a message from the client. If the message is not useful
- for your particular problem just ignore it by immediately
- returning {ok, State}.
+ <item>This is the first messages that will be received by
+ the channel, it is sent just before the <seealso
+ marker="#init-2">ssh_channel:init/1</seealso> function
+ returns successfully. This is especially useful if the
+ server wants to send a message to the client without first
+ receiving a message from it. If the message is not
+ useful for your particular scenario just ignore it by
+ immediately returning {ok, State}.
</item>
</taglist>
</desc>
</func>
<func>
- <name>CallbackModule:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
+ <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ssh_channel_id(), State}</name>
<fsummary> Handles ssh connection protocol messages. </fsummary>
<type>
- <v>Msg = {ssh_cm, ssh_connection_ref(), SshMsg}</v>
- <v> SshMsg = tuple() - see message list below</v>
+ <v>Msg = <seealso marker="ssh_connection"> ssh_connection:event() </seealso> </v>
<v>State = term()</v>
</type>
<desc>
<p> Handles ssh connection protocol messages that may need
service specific attention.
</p>
-
- <p> All channels should handle the following messages. For
- channels implementing subsystems the handle_ssh_msg-callback
- will not be called for any other messages. </p>
-
- <taglist>
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {data, ssh_channel_id(),
- ssh_data_type_code(), binary() = Data}}]]></c></tag>
- <item> Data has arrived on the channel. When the callback
- for this message returns the channel behavior will adjust
- the ssh flow control window.</item>
-
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {eof,
- ssh_channel_id()}}]]></c></tag>
- <item>Indicteas that the other side will not send any more
- data.</item>
-
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {signal,
- ssh_channel_id(), ssh_signal()}} ]]></c></tag>
- <item>A signal can be delivered to the remote
- process/service using the following message. Some systems
- may not implement signals, in which case they should ignore
- this message.</item>
-
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(),
- {exit_signal, ssh_channel_id(), string() = exit_signal,
- string() = ErrorMsg, string() =
- LanguageString}}]]></c></tag>
- <item>A remote execution may terminate violently due to a
- signal then this message may be received. For details on valid string
- values see RFC 4254 section 6.10</item>
-
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {exit_status,
- ssh_channel_id(), integer() = ExitStatus}}]]></c></tag>
- <item> When the command running at the other end terminates,
- the following message can be sent to return the exit status
- of the command. A zero 'exit_status' usually means that the
- command terminated successfully.</item>
- </taglist>
-
- <p> Channels implementing a shell and command execution on the server side
- should also handle the following messages. </p>
-
- <taglist>
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {env, ssh_channel_id(),
- boolean() = WantReply, string() = Var, string() = Value}}]]></c></tag>
- <item> Environment variables may be passed to the
- shell/command to be started later. Note that before the
- callback returns it should call the function
- ssh_connection:reply_request/4 with the boolean value of <c>
- WantReply</c> as the second argument.
- </item>
-
- <tag><c><![CDATA[{ssh_cm, ConnectionRef, {exec, ssh_channel_id(),
- boolean() = WantReply, string() = Cmd}}]]></c></tag>
- <item> This message will request that the server start the
- execution of the given command. Note that before the
- callback returns it should call the function
- ssh_connection:reply_request/4 with the boolean value of <c>
- WantReply</c> as the second argument.</item>
-
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {pty, ssh_channel_id(),
- boolean() = WantReply, {string() = Terminal, integer() = CharWidth,
- integer() = RowHeight, integer() = PixelWidth, integer() = PixelHight,
- [{atom() | integer() = Opcode,
- integer() = Value}] = TerminalModes}}}]]></c></tag>
- <item>A pseudo-terminal has been requested for the
- session. Terminal is the value of the TERM environment
- variable value (e.g., vt100). Zero dimension parameters must
- be ignored. The character/row dimensions override the pixel
- dimensions (when nonzero). Pixel dimensions refer to the
- drawable area of the window. The <c>Opcode</c> in the
- <c>TerminalModes</c> list is the mnemonic name, represented
- as an lowercase erlang atom, defined in RFC 4254 section 8,
- or the opcode if the mnemonic name is not listed in the
- RFC. Example <c>OP code: 53, mnemonic name ECHO erlang atom:
- echo</c>. Note that before the callback returns it should
- call the function ssh_connection:reply_request/4 with the
- boolean value of <c> WantReply</c> as the second
- argument.</item>
-
- <tag><c><![CDATA[{ssh_cm, ConnectionRef, {shell, boolean() =
- WantReply}}]]></c></tag>
- <item> This message will request that the user's default
- shell be started at the other end. Note that before the
- callback returns it should call the function
- ssh_connection:reply_request/4 with the value of <c>
- WantReply</c> as the second argument.
- </item>
-
- <tag><c><![CDATA[ {ssh_cm, ssh_connection_ref(), {window_change,
- ssh_channel_id(), integer() = CharWidth, integer() = RowHeight,
- integer() = PixWidth, integer() = PixHeight}}]]></c></tag>
- <item> When the window (terminal) size changes on the client
- side, it MAY send a message to the other side to inform it
- of the new dimensions.</item>
- </taglist>
<p> The following message is completely taken care of by the
ssh channel behavior</p>
<taglist>
- <tag><c><![CDATA[{ssh_cm, ssh_connection_ref(), {closed,
- ssh_channel_id()}}]]></c></tag>
+ <tag><c><![CDATA[{closed, ssh_channel_id()}]]></c></tag>
<item> The channel behavior will send a close message to the
other side if such a message has not already been sent and
then terminate the channel with reason normal.</item>
@@ -497,7 +398,7 @@
</func>
<func>
- <name>CallbackModule:terminate(Reason, State) -> _</name>
+ <name>Module:terminate(Reason, State) -> _</name>
<fsummary> </fsummary>
<type>
<v>Reason = term()</v>
@@ -505,12 +406,12 @@
</type>
<desc>
<p>This function is called by a channel process when it is
- about to terminate. Before this function is called ssh_connection:close/2
- will be called if it has not been called earlier.
- This function should be the opposite of <c>CallbackModule:init/1</c>
- and do any necessary cleaning up. When it returns, the
- channel process terminates with reason <c>Reason</c>. The return value is
- ignored.
+ about to terminate. Before this function is called <seealso
+ marker="ssh_connection#close-2"> ssh_connection:close/2
+ </seealso> will be called if it has not been called earlier.
+ This function should do any necessary cleaning
+ up. When it returns, the channel process terminates with
+ reason <c>Reason</c>. The return value is ignored.
</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
new file mode 100644
index 0000000000..abc1070e78
--- /dev/null
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+ <title>ssh_client_key_api</title>
+ </header>
+ <module>ssh_client_key_api</module>
+ <modulesummary>
+ -behaviour(ssh_client_key_api).
+ </modulesummary>
+ <description>
+ <p> Behavior describing the API for an SSH client's public key handling.
+ By implementing the callbacks defined.
+ in this behavior it is possible to customize the SSH client's public key
+ handling. By default the SSH application implements this behavior
+ with help of the standard openssh files, see <seealso marker="SSH_app"> ssh(6)</seealso>. </p>
+ </description>
+
+ <section>
+ <title>DATA TYPES </title>
+
+ <p>Type definitions that are used more than once in this module
+ and/or abstractions to indicate the intended use of the data
+ type:</p>
+
+ <p> boolean() = true | false</p>
+ <p> string() = [byte()] </p>
+ <p> public_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p>
+ <p> private_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p>
+ <p> public_key_algorithm() = 'ssh-rsa'| 'ssh-dss' | atom()</p>
+
+ </section>
+
+ <funcs>
+ <func>
+ <name>Module:add_host_key(HostNames, Key, ConnectOptions) -> ok | {error, Reason}</name>
+ <fsummary>Adds a host key to the set of trusted host keys</fsummary>
+ <type>
+ <v>HostNames = string()</v>
+ <d>Description of the host that owns the <c>PublicKey</c></d>
+
+ <v>Key = public_key() </v>
+ <d> Normally an RSA or DSA public key but handling of other public keys can be added</d>
+
+ <v>ConnectOptions = proplists:proplist() </v>
+ <d>Options provided to <seealso marker="ssh#daemon">ssh:connect/[3,4]</seealso></d>
+ <v>Reason = term() </v>
+ </type>
+ <desc>
+ <p> Adds a host key to the set of trusted host keys</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name>
+ <fsummary>Checks if a host key is trusted</fsummary>
+ <type>
+ <v>Key = public_key() </v>
+ <d> Normally an RSA or DSA public key but handling of other public keys can be added</d>
+
+ <v>Host = string()</v>
+ <d>Description of the host</d>
+
+ <v>Algorithm = public_key_algorithm()</v>
+ <d> Host key algorithm. Should support 'ssh-rsa'| 'ssh-dss' but additional algorithms
+ can be handled.</d>
+
+ <v> ConnectOptions = proplists:proplist() </v>
+ <d>Options provided to <seealso marker="ssh#daemon">ssh:connect/[3,4]</seealso></d>
+
+ <v> Result = boolean()</v>
+ </type>
+ <desc>
+ <p>Checks if a host key is trusted</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>Module:user_key(Algorithm, ConnectOptions) ->
+ {ok, PrivateKey} | {error, Reason}</name>
+ <fsummary>Fetches the users "public key" matching the <c>Algorithm</c>.</fsummary>
+ <type>
+ <v>Algorithm = public_key_algorithm()</v>
+ <d> Host key algorithm. Should support 'ssh-rsa'| 'ssh-dss' but additional algorithms
+ can be handled.</d>
+
+ <v> ConnectOptions = proplists:proplist() </v>
+ <d>Options provided to <seealso marker="ssh#daemon">ssh:connect/[3,4]</seealso></d>
+
+ <v> PrivateKey = private_key()</v>
+ <d> The private key of the user matching the <c>Algorithm</c></d>
+
+ <v>Reason = term() </v>
+ </type>
+
+ <desc>
+ <p>Fetches the users "public key" matching the <c>Algorithm</c>.
+ <note>The private key contains the public key</note>
+ </p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 9942306b93..c66622307f 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -1,11 +1,11 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
<header>
<copyright>
<year>2008</year>
- <year>2011</year>
+ <year>2012</year>
<holder>Ericsson AB, All Rights Reserved</holder>
</copyright>
<legalnotice>
@@ -24,69 +24,178 @@
</legalnotice>
<title>ssh_connection</title>
- <prepared>Ingela Anderton Andin</prepared>
- <responsible></responsible>
- <docno></docno>
- <approved></approved>
- <checked></checked>
<date></date>
- <rev></rev>
</header>
<module>ssh_connection</module>
- <modulesummary>This module provides an API to the ssh connection protocol.
+ <modulesummary>This module provides API functions to send <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH Connection Protocol </url>
+ events to the other side of an SSH channel.
</modulesummary>
+
<description>
- <p>This module provides an API to the ssh connection protocol.
- Not all features of the connection protocol are officially supported yet.
- Only the ones supported are documented here.</p>
+ <p>The SSH Connection Protocol is used by clients and servers
+ (i.e. SSH channels) to communicate over the SSH connection. The
+ API functions in this module sends SSH Connection Protocol events
+ that are received as messages by the remote channel.
+ In the case that the receiving channel is an Erlang process the
+ message will be on the following format
+ <c><![CDATA[{ssh_cm, ssh_connection_ref(), ssh_event_msg()}]]></c>. If the <seealso
+ marker="ssh_channel">ssh_channel</seealso> behavior is used to
+ implement the channel process these will be handled by
+ <seealso
+ marker="ssh_channel#CallbackModule:handled_ssh_msg-2">handle_ssh_msg/2 </seealso>.</p>
</description>
- <section>
- <title>COMMON DATA TYPES </title>
+ <section>
+ <title>DATA TYPES </title>
+
<p>Type definitions that are used more than once in this module and/or
abstractions to indicate the intended use of the data type:</p>
-
+
<p><c>boolean() = true | false </c></p>
<p><c>string() = list of ASCII characters</c></p>
<p><c>timeout() = infinity | integer() - in milliseconds.</c></p>
<p><c>ssh_connection_ref() - opaque to the user returned by
- ssh:connect/3 or sent to a ssh channel processes</c></p>
+ ssh:connect/3 or sent to an SSH channel processes</c></p>
<p><c>ssh_channel_id() = integer() </c></p>
<p><c>ssh_data_type_code() = 1 ("stderr") | 0 ("normal") are
- currently valid values see RFC 4254 section 5.2.</c></p>
+ currently valid values see</c> <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 5.2.</p>
<p><c>ssh_request_status() = success | failure</c></p>
- </section>
+ <p><c>event() = {ssh_cm, ssh_connection_ref(), ssh_event_msg()} </c></p>
+ <p><c>ssh_event_msg() = data_events() | status_events() | terminal_events() </c></p>
+
+ <taglist>
+ <tag><b>data_events()</b></tag>
+ <item>
+ <taglist>
+ <tag><c><![CDATA[{data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}]]></c></tag>
+ <item> Data has arrived on the channel. This event is sent as
+ result of calling <seealso marker="#send-3"> ssh_connection:send/[3,4,5] </seealso></item>
+
+ <tag><c><![CDATA[{eof, ssh_channel_id()}]]></c></tag>
+ <item>Indicates that the other side will not send any more
+ data. This event is sent as result of calling <seealso
+ marker="#send_eof-2"> ssh_connection:send_eof/2</seealso>
+ </item>
+ </taglist>
+ </item>
+
+ <tag><b>status_events()</b></tag>
+ <item>
+
+ <taglist>
+ <tag><c><![CDATA[{signal, ssh_channel_id(), ssh_signal()}]]></c></tag>
+ <item>A signal can be delivered to the remote process/service
+ using the following message. Some systems will not support
+ signals, in which case they should ignore this message. There is
+ currently no funtion to generate this event as the signals
+ refered to are on OS-level and not something generated by an
+ Erlang program.</item>
+
+ <tag><c><![CDATA[{exit_signal, ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg,
+ string() = LanguageString}]]></c></tag>
+
+ <item>A remote execution may terminate violently due to a signal
+ then this message may be received. For details on valid string
+ values see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> section 6.10. Special case of the signals
+ mentioned above.</item>
+
+ <tag><c><![CDATA[{exit_status, ssh_channel_id(), integer() = ExitStatus}]]></c></tag>
+ <item> When the command running at the other end terminates, the
+ following message can be sent to return the exit status of the
+ command. A zero 'exit_status' usually means that the command
+ terminated successfully. This event is sent as result of calling
+ <seealso marker="#exit_status">
+ ssh_connection:exit_status/3</seealso></item>
+
+ <tag><c><![CDATA[{closed, ssh_channel_id()}]]></c></tag>
+ <item> This event is sent as result of calling
+ <seealso marker="#close">ssh_connection:close/2</seealso> Both the handling of this
+ event and sending of it will be taken care of by the
+ <seealso marker="ssh_channel">ssh_channel</seealso> behavior.</item>
+
+ </taglist>
+ </item>
- <section>
- <title>MESSAGES SENT TO CHANNEL PROCESSES</title>
+ <tag><b>terminal_events()</b></tag>
+
+ <item>
+ <p> Channels implementing a shell and command execution on the
+ server side should handle the following messages that may be sent by client channel processes. </p>
+
+ <p><note>Events that includes a <c> WantReply</c> expects the event handling
+ process to call <seealso marker="#reply_request">ssh_connection:reply_request/4</seealso>
+ with the boolean value of <c> WantReply</c> as the second
+ argument. </note> </p>
+
+ <taglist>
+ <tag><c><![CDATA[{env, ssh_channel_id(), boolean() = WantReply,
+ string() = Var, string() = Value}]]></c></tag>
+ <item> Environment variables may be passed to the shell/command
+ to be started later. This event is sent as result of calling <seealso
+ marker="#setenv"> ssh_connection:setenv/5</seealso>
+ </item>
+
+ <tag><c><![CDATA[{pty, ssh_channel_id(),
+ boolean() = WantReply, {string() = Terminal, integer() = CharWidth,
+ integer() = RowHeight, integer() = PixelWidth, integer() = PixelHight,
+ [{atom() | integer() = Opcode,
+ integer() = Value}] = TerminalModes}}]]></c></tag>
+ <item>A pseudo-terminal has been requested for the
+ session. Terminal is the value of the TERM environment
+ variable value (e.g., vt100). Zero dimension parameters must
+ be ignored. The character/row dimensions override the pixel
+ dimensions (when nonzero). Pixel dimensions refer to the
+ drawable area of the window. The <c>Opcode</c> in the
+ <c>TerminalModes</c> list is the mnemonic name, represented
+ as an lowercase erlang atom, defined in
+ <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 8,
+ or the opcode if the mnemonic name is not listed in the
+ RFC. Example <c>OP code: 53, mnemonic name ECHO erlang atom:
+ echo</c>. There is currently no API function to generate this
+ event.</item>
+
+ <tag><c><![CDATA[{shell, boolean() = WantReply}]]></c></tag>
+ <item> This message will request that the user's default shell
+ be started at the other end. This event is sent as result of calling <seealso
+ marker="#shell"> ssh_connection:shell/2</seealso>
+ </item>
+
+ <tag><c><![CDATA[{window_change, ssh_channel_id(), integer() = CharWidth,
+ integer() = RowHeight, integer() = PixWidth, integer() = PixHeight}]]></c></tag>
+ <item> When the window (terminal) size changes on the client
+ side, it MAY send a message to the server side to inform it of
+ the new dimensions. There is currently no API function to generate this
+ event.</item>
- <p>As a result of the ssh connection protocol messages on the form
- <c><![CDATA[{ssh_cm, ssh_connection_ref(), term()}]]></c>
- will be sent to a channel process. The term will contain
- information regarding the ssh connection protocol event,
- for details see the ssh channel behavior callback <seealso
- marker="ssh_channel">handle_ssh_msg/2 </seealso> </p>
- </section>
-
- <funcs>
+ <tag><c><![CDATA[{exec, ssh_channel_id(),
+ boolean() = WantReply, string() = Cmd}]]></c></tag>
+ <item> This message will request that the server starts
+ execution of the given command. This event is sent as result of calling <seealso
+ marker="#exec">ssh_connection:exec/4 </seealso>
+ </item>
+ </taglist>
+ </item>
+ </taglist>
+ </section>
+
+ <funcs>
<func>
<name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
- <fsummary>Adjusts the ssh flowcontrol window. </fsummary>
+ <fsummary>Adjusts the SSH flowcontrol window. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
<v> ChannelId = ssh_channel_id() </v>
<v> NumOfBytes = integer()</v>
</type>
<desc>
- <p>Adjusts the ssh flowcontrol window. </p>
+ <p>Adjusts the SSH flowcontrol window. This shall be done by both client and server side channel processes.</p>
- <note><p>This will be taken care of by the ssh_channel
- behavior when the callback <seealso marker="ssh_channel">
- handle_ssh_msg/2 </seealso> has returned after processing a
- {ssh_cm, ssh_connection_ref(), {data, ssh_channel_id(),
- ssh_data_type_code(), binary()}}
- message, and should normally not be called explicitly.</p></note>
+ <note><p>Channels implemented with the <seealso marker="ssh_channel"> ssh_channel
+ behavior</seealso> will normaly not need to call this function as flow control
+ will be handled by the behavior. The behavior will adjust the window every time
+ the callback <seealso marker="ssh_channel#handled_ssh_msg-2">
+ handle_ssh_msg/2 </seealso> has returned after processing channel data</p> </note>
</desc>
</func>
@@ -98,20 +207,19 @@
<v> ChannelId = ssh_channel_id()</v>
</type>
<desc>
- <p>Sends a close message on the channel <c>ChannelId</c>
+ <p>A server or client channel process can choose to close their session by sending a close event.
</p>
- <note><p>This function will be called by the ssh channel
+ <note><p>This function will be called by the ssh_channel
behavior when the channel is terminated see <seealso
- marker="ssh_channel"> ssh_channel(3) </seealso> and should
- normally not be called explicitly.</p></note>
+ marker="ssh_channel"> ssh_channel(3) </seealso> so channels implemented with the
+ behavior should not call this function explicitly.</p></note>
</desc>
</func>
<func>
<name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() </name>
- <fsummary>Will request that the server start the
- execution of the given command. </fsummary>
+ <fsummary>Request that the server start the execution of the given command. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
<v> ChannelId = ssh_channel_id()</v>
@@ -119,44 +227,39 @@
<v>Timeout = timeout() </v>
</type>
<desc>
- <p>Will request that the server start the execution of the
- given command, the result will be received as:</p>
+ <p>Should be called by a client channel process to request that the server starts execution of the
+ given command, the result will be several messages according to the following pattern. Note
+ that the last message will be a channel close message, as the exec request is a one time
+ execution that closes the channel when it is done.</p>
<taglist>
- <tag><c> N X {ssh_cm,
- ssh_connection_ref(), {data, ssh_channel_id(), ssh_data_type_code(),
- binary() = Data}} </c></tag>
+ <tag><c> N x {ssh_cm, ssh_connection_ref(),
+ {data, ssh_channel_id(), ssh_data_type_code(), binary() = Data}} </c></tag>
<item>The result of executing the command may be only one line
or thousands of lines depending on the command.</item>
- <tag><c> 1 X {ssh_cm, ssh_connection_ref(), {eof, ssh_channel_id()}}</c></tag>
+ <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {eof, ssh_channel_id()}}</c></tag>
<item>Indicates that no more data will be sent.</item>
- <tag><c>0 or 1 X {ssh_cm,
+ <tag><c>0 or 1 x {ssh_cm,
ssh_connection_ref(), {exit_signal,
ssh_channel_id(), string() = ExitSignal, string() = ErrorMsg, string() = LanguageString}}</c></tag>
<item>Not all systems send signals. For details on valid string
values see RFC 4254 section 6.10 </item>
- <tag><c>0 or 1 X {ssh_cm, ssh_connection_ref(), {exit_status,
+ <tag><c>0 or 1 x {ssh_cm, ssh_connection_ref(), {exit_status,
ssh_channel_id(), integer() = ExitStatus}}</c></tag>
<item>It is recommended by the <c>ssh connection protocol</c> that this
message shall be sent, but that may not always be the case.</item>
- <tag><c> 1 X {ssh_cm, ssh_connection_ref(),
+ <tag><c> 1 x {ssh_cm, ssh_connection_ref(),
{closed, ssh_channel_id()}}</c></tag>
<item>Indicates that the ssh channel started for the
execution of the command has now been shutdown.</item>
</taglist>
-
- <p> These message should be handled by the
- client. The <seealso marker="ssh_channel">ssh channel
- behavior</seealso> can be used when writing a client.
- </p>
</desc>
</func>
-
<func>
<name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
<fsummary>Sends the exit status of a command to the client.</fsummary>
@@ -166,12 +269,12 @@
<v> Status = integer()</v>
</type>
<desc>
- <p>Sends the exit status of a command to the client.</p>
+ <p>Should be called by a server channel process to sends the exit status of a command to the client.</p>
</desc>
- </func>
+ </func>
<func>
- <name>reply_request(ConnectionRef, WantReply, Status, CannelId) -> ok</name>
+ <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
<fsummary>Send status replies to requests that want such replies. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -183,10 +286,9 @@
<p>Sends status replies to requests where the requester has
stated that they want a status report e.i .<c> WantReply = true</c>,
if <c> WantReply</c> is false calling this function will be a
- "noop". Should be called after handling an ssh connection
+ "noop". Should be called while handling an ssh connection
protocol message containing a <c>WantReply</c> boolean
- value. See the ssh_channel behavior callback <seealso
- marker="ssh_channel"> handle_ssh_msg/2 </seealso>
+ value.
</p>
</desc>
</func>
@@ -196,7 +298,7 @@
<name>send(ConnectionRef, ChannelId, Data, Timeout) -></name>
<name>send(ConnectionRef, ChannelId, Type, Data) -></name>
<name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
- ok | {error, timeout}</name>
+ ok | {error, timeout} | {error, closed}</name>
<fsummary>Sends channel data </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -206,13 +308,13 @@
<v> Timeout = timeout()</v>
</type>
<desc>
- <p>Sends channel data.
+ <p>Should be called by client- and server channel processes to send data to each other.
</p>
</desc>
</func>
<func>
- <name>send_eof(ConnectionRef, ChannelId) -> ok </name>
+ <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
<fsummary>Sends eof on the channel <c>ChannelId</c>. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
@@ -228,9 +330,7 @@
<name>session_channel(ConnectionRef, Timeout) -> </name>
<name>session_channel(ConnectionRef, InitialWindowSize,
MaxPacketSize, Timeout) -> {ok, ssh_channel_id()} | {error, Reason}</name>
- <fsummary>Opens a channel for a ssh session. A session is a
- remote execution of a program. The program may be a shell, an
- application, a system command, or some built-in subsystem. </fsummary>
+ <fsummary>Opens a channel for a ssh session. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref()</v>
<v> InitialWindowSize = integer() </v>
@@ -239,9 +339,8 @@
<v> Reason = term() </v>
</type>
<desc>
- <p>Opens a channel for a ssh session. A session is a
- remote execution of a program. The program may be a shell, an
- application, a system command, or some built-in subsystem.
+ <p>Opens a channel for an SSH session. The channel id returned from this function
+ is the id used as input to the other funtions in this module.
</p>
</desc>
</func>
@@ -258,8 +357,8 @@
<v> Timeout = timeout()</v>
</type>
<desc>
- <p> Environment variables may be passed to the shell/command to be
- started later.
+ <p> Environment variables may be passed before starting the
+ shell/command. Should be called by a client channel processes.
</p>
</desc>
</func>
@@ -267,17 +366,16 @@
<func>
<name>shell(ConnectionRef, ChannelId) -> ssh_request_status()
</name>
- <fsummary> Will request that the user's default shell (typically
- defined in /etc/passwd in UNIX systems) be started at the other
+ <fsummary> Requests that the user's default shell (typically
+ defined in /etc/passwd in UNIX systems) shall be executed at the server
end. </fsummary>
<type>
<v> ConnectionRef = ssh_connection_ref() </v>
<v> ChannelId = ssh_channel_id()</v>
</type>
<desc>
- <p> Will request that the user's default shell (typically
- defined in /etc/passwd in UNIX systems) be started at the
- other end.
+ <p> Should be called by a client channel process to request that the user's default shell (typically
+ defined in /etc/passwd in UNIX systems) shall be executed at the server end.
</p>
</desc>
</func>
@@ -292,7 +390,7 @@
<v> Timeout = timeout()</v>
</type>
<desc>
- <p> Sends a request to execute a predefined subsystem.
+ <p> Should be called by a client channel process for requesting to execute a predefined subsystem on the server.
</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_protocol.xml b/lib/ssh/doc/src/ssh_protocol.xml
new file mode 100644
index 0000000000..6a253c43eb
--- /dev/null
+++ b/lib/ssh/doc/src/ssh_protocol.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+<!-- %EricssonCopyright% -->
+<chapter>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ The program may be used and/or copied only with the written permission from
+ Ericsson AB, or in accordance with the terms and conditions stipulated in
+ the agreement/contract under which the program has been supplied.
+ </legalnotice>
+ <title>Secure Shell (SSH)</title>
+ <prepared>OTP</prepared>
+ <date></date>
+ <rev>%VSN%</rev>
+ <file>ssh_protocol.xml</file>
+ </header>
+
+ <section>
+ <title>SSH Protocol Overview</title>
+
+ <p> Conceptually the SSH protocol can be partitioned into four
+ layers:</p>
+
+ <image file="SSH_protocols.png">
+ <icaption>SSH Protocol Architecture</icaption>
+ </image>
+
+ <section>
+ <title>Transport Protocol</title>
+
+ <p> The SSH Transport Protocol is a secure, low level transport.
+ It provides strong encryption, cryptographic host
+ authentication and integrity protection. Currently, only a
+ minimum of MAC- (message authentication code, a short piece of
+ information used to authenticate a message) and encryption
+ algorithms are supported see <seealso marker="ssh">ssh(3)</seealso>
+ </p>
+ </section>
+
+ <section>
+ <title>Authentication Protocol</title>
+
+ <p>The SSH authentication protocol is a general-purpose user
+ authentication protocol run over the SSH transport
+ protocol. Erlang SSH supports user authentication using public
+ key technology (RSA and DSA, X509-certificates are currently not
+ supported). It is also possible to use a so called keyboard
+ interactive authentication. This method is suitable for
+ interactive authentication methods that do not need any special
+ software support on the client side. Instead, all authentication
+ data should be entered via the keyboard. It is also possible
+ to use a pure password based authentication scheme, note that in
+ this case the the plain text password will be encrypted before sent
+ over the network. There are several configuration options for
+ authentication handling available in
+ <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>
+ and <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>
+ It is also possible to customize the public key handling
+ by implementing the behaviours <seealso
+ marker="ssh_client_key_api">ssh_client_key_api</seealso> and
+ <seealso
+ marker="ssh_server_key_api">ssh_server_key_api</seealso>
+ </p>
+ </section>
+
+ <section>
+ <title>Connection Protocol</title>
+
+ <p>The SSH Connection Protocol provides application-support
+ services over the transport pipe, such as channel multiplexing,
+ flow control, remote program execution, signal propagation,
+ connection forwarding, etc. Functions for handling the SSH
+ Connection Protocol can be found in the module <seealso
+ marker="ssh_connection">ssh_connection</seealso>.
+ </p>
+ </section>
+
+ <section>
+ <title>Channels</title>
+
+ <p>All terminal sessions, forwarded connections etc., are
+ channels. Multiple channels are multiplexed into a single
+ connection, and all channels are flow-controlled. Typically an
+ SSH client will open a channel, send data/commands, receive
+ data/"control information" and when it is done close the
+ channel. The <seealso
+ marker="ssh_channel">ssh_channel</seealso> behaviour makes it easy to
+ write your own SSH client/server processes that use flow
+ control. It handles generic parts of SSH channel management and
+ lets you focus on the application logic.
+ </p>
+
+ <p>Channels comes in three flavors</p>
+
+ <list type="bulleted">
+ <item><em>Subsystem</em> - named services that can be run as
+ part of an SSH server such as SFTP <seealso
+ marker="ssh_sftpd">ssh_sftpd</seealso>, that is built in to the
+ SSH daemon (server) by default but may be disabled. The Erlang SSH
+ daemon may be configured to run any Erlang
+ implemented SSH subsystem.
+ </item>
+ <item><em>Shell</em> - interactive shell. By default the
+ Erlang daemon will run the Erlang shell. It is
+ possible to customize the shell by providing your own
+ read-eval-print loop. It is also possible, but much more work,
+ to provide your own CLI (Command Line Interface) implementation.
+ </item>
+ <item><em>Exec</em> - one-time remote execution (like
+ SCP). See <seealso
+ marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso></item>
+ </list>
+ </section>
+
+ <p>Channels are flow controlled. No data may be sent to a channel
+ peer until a message is received to indicate that window space is
+ available. The 'initial window size' specifies how many bytes of
+ channel data that can be sent to the channel peer without adjusting the
+ window.
+ </p>
+
+ <p>
+ For more detailed information about the SSH protocol, see the
+ following RFCs:
+ </p>
+
+ <list type="bulleted">
+ <item><url href="http://www.ietf.org/rfc/rfc4250.txt">RFC 4250</url> -
+ Protocol Assigned Numbers.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4251.txt">RFC 4251</url> -
+ Protocol Architecture.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4252.txt">RFC 4252</url> -
+ Authentication Protocol.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4253.txt">RFC 4253</url> -
+ Transport Layer Protocol.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> -
+ Connection Protocol.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4255.txt">RFC 4255</url> -
+ Key Fingerprints.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4344.txt">RFC 4344</url> -
+ Transport Layer Encryption Modes.</item>
+ <item><url href="http://www.ietf.org/rfc/rfc4716.txt">RFC 4716</url> -
+ Public Key File Format.</item>
+ </list>
+ </section>
+</chapter>
diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
new file mode 100644
index 0000000000..78ff105387
--- /dev/null
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+ <title>ssh_server_key_api</title>
+ </header>
+ <module>ssh_server_key_api</module>
+ <modulesummary>
+ -behaviour(ssh_server_key_api).
+ </modulesummary>
+ <description>
+ <p> Behaviour describing the API for an SSH server's public key handling.By implementing the callbacks defined
+ in this behavior it is possible to customize the SSH server's public key
+ handling. By default the SSH application implements this behavior
+ with help of the standard openssh files, see <seealso marker="SSH_app"> ssh(6)</seealso>.</p>
+ </description>
+
+ <section>
+ <title>DATA TYPES </title>
+
+ <p>Type definitions that are used more than once in this module
+ and/or abstractions to indicate the intended use of the data
+ type:</p>
+
+ <p> boolean() = true | false</p>
+ <p> string() = [byte()]</p>
+ <p> public_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p>
+ <p> private_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p>
+ <p> public_key_algorithm() = 'ssh-rsa'| 'ssh-dss' | atom()</p>
+ </section>
+
+ <funcs>
+ <func>
+ <name>Module:host_key(Algorithm, DaemonOptions) ->
+ {ok, Key} | {error, Reason}</name>
+ <fsummary>Fetches the hosts private key </fsummary>
+ <type>
+ <v>Algorithm = public_key_algorithm()</v>
+ <d> Host key algorithm. Should support 'ssh-rsa'| 'ssh-dss' but additional algorithms
+ can be handled.</d>
+ <v> DaemonOptions = proplists:proplist() </v>
+ <d>Options provided to <seealso marker="ssh#daemon">ssh:daemon/[2,3]</seealso></d>
+ <v> Key = private_key()</v>
+ <d> The private key of the host matching the <c>Algorithm</c></d>
+ <v>Reason = term() </v>
+ </type>
+ <desc>
+ <p>Fetches the hosts private key</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>Module:is_auth_key(Key, User, DaemonOptions) -> Result</name>
+ <fsummary> Checks if the user key is authorized</fsummary>
+ <type>
+ <v> Key = public_key() </v>
+ <d> Normally an RSA or DSA public key but handling of other public keys can be added</d>
+ <v> User = string()</v>
+ <d> The user owning the public key</d>
+ <v> DaemonOptions = proplists:proplist() </v>
+ <d> Options provided to <seealso marker="ssh#daemon">ssh:daemon/[2,3]</seealso></d>
+ <v> Result = boolean()</v>
+ </type>
+ <desc>
+ <p> Checks if the user key is authorized </p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index c1f75461b1..0d61e57edb 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
<header>
<copyright>
- <year>2005</year><year>2010</year>
+ <year>2005</year><year>2012</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,25 +22,20 @@
</legalnotice>
<title>ssh_sftp</title>
- <prepared>Jakob Cederlund</prepared>
- <responsible></responsible>
- <docno>1</docno>
- <approved></approved>
- <checked></checked>
+ <prepared>OTP</prepared>
<date>2005-09-22</date>
- <rev>PA1</rev>
<file>ssh_sftp.sgml</file>
</header>
<module>ssh_sftp</module>
<modulesummary>SFTP client.</modulesummary>
<description>
<p>This module implements an SFTP (SSH FTP) client. SFTP is a
- secure, encrypted file transfer service available for
- SSH.</p>
+ secure, encrypted file transfer service available for
+ SSH.</p>
</description>
<section>
- <title>COMMON DATA TYPES </title>
+ <title>DATA TYPES </title>
<p>Type definitions that are used more than once in this module
and/or abstractions to indicate the intended use of the data type:
</p>
@@ -51,8 +46,8 @@
<section>
<title>TIMEOUTS </title>
- <p>If the request functions for the sftp channel return {error, timeout}
- it does not mean that the request did not reach the server and was
+ <p>If the request functions for the SFTP channel return {error, timeout}
+ it does not guarantee that the request did not reach the server and was
not performed, it only means that we did not receive an answer from the
server within the time that was expected.</p>
</section>
@@ -64,7 +59,7 @@
<name>start_channel(Host, Options) -></name>
<name>start_channel(Host, Port, Options) -> {ok, Pid} | {ok, Pid, ConnectionRef} |
{error, Reason}</name>
- <fsummary>Starts a sftp client</fsummary>
+ <fsummary>Starts a SFTP client</fsummary>
<type>
<v>Host = string()</v>
<v>ConnectionRef = ssh_connection_ref()</v>
@@ -73,11 +68,11 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>If not provided, setups a ssh connection in this case a
- connection reference will be returned too. A ssh channel
- process is started to handle the communication with the SFTP
- server, the returned pid for this process should be used as
- input to all other API functions in this module.</p>
+ <p>If no connection reference is provided, a connection is set
+ up and the new connection is returned. An SSH channel process
+ is started to handle the communication with the SFTP server.
+ The returned pid for this process should be used as input to
+ all other API functions in this module.</p>
<p>Options are:</p>
<taglist>
@@ -95,13 +90,13 @@
<func>
<name>stop_channel(ChannelPid) -> ok</name>
- <fsummary>Stops the sftp client channel.</fsummary>
+ <fsummary>Stops the SFTP client channel.</fsummary>
<type>
<v>ChannelPid = pid()</v>
</type>
<desc>
- <p>Stops a sftp channel. If the ssh connection should be closed
- call <seealso marker="ssh">ssh:close/1</seealso>.</p>
+ <p>Stops an SFTP channel. Does not close the SSH connetion.
+ Use <seealso marker="ssh">ssh:close/1</seealso> to close it.</p>
</desc>
</func>
@@ -133,8 +128,9 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Writes a file to the server, like <c><![CDATA[file:write_file/2]]></c>.
- The file is created if it's not there.</p>
+ <p>Writes a file to the server, like
+ <c><![CDATA[file:write_file/2]]></c>. The file is created if
+ it does not exist or is owerwritten if it does.</p>
</desc>
</func>
<func>
@@ -169,7 +165,7 @@
</type>
<desc>
<p>Opens a file on the server, and returns a handle that
- is used for reading or writing.</p>
+ can be used for reading or writing.</p>
</desc>
</func>
<func>
@@ -184,7 +180,7 @@
</type>
<desc>
<p>Opens a handle to a directory on the server, the handle
- is used for reading directory contents.</p>
+ can be used for reading directory contents.</p>
</desc>
</func>
<func>
@@ -218,7 +214,7 @@
</type>
<desc>
<p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by
- <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, or <c><![CDATA[eof]]></c>, or
+ <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or
<c><![CDATA[{error, Reason}]]></c>. If the file is opened with <c><![CDATA[binary]]></c>,
<c><![CDATA[Data]]></c> is a binary, otherwise it is a string.</p>
<p>If the file is read past eof, only the remaining bytes
@@ -267,9 +263,9 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Write <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>.
+ <p>Writes<c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>.
The file should be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c>
- flag. Returns <c><![CDATA[ok]]></c> if successful and <c><![CDATA[{error, Reason}]]></c>
+ flag. Returns <c><![CDATA[ok]]></c> if successful or S<c><![CDATA[{error, Reason}]]></c>
otherwise.</p>
<p>Typical error reasons are:</p>
<taglist>
@@ -317,14 +313,14 @@
<v>ChannelPid = pid()</v>
<v>Handle = term()</v>
<v>Location = Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v>
- <v>Offset = int()</v>
+ <v>Offset = integer()</v>
<v>Timeout = timeout()</v>
<v>NewPosition = integer()</v>
<v>Reason = term()</v>
</type>
<desc>
<p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>.
- Returns <c><![CDATA[{ok, NewPosition]]></c> (as an absolute offset) if
+ Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if
successful, otherwise <c><![CDATA[{error, Reason}]]></c>. <c><![CDATA[Location]]></c> is
one of the following:</p>
<taglist>
@@ -413,7 +409,7 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Read the link target from the symbolic link specified
+ <p>Reads the link target from the symbolic link specified
by <c><![CDATA[name]]></c>, like <c><![CDATA[file:read_link/1]]></c>.</p>
</desc>
</func>
@@ -490,8 +486,9 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. The directory
- should be empty.</p>
+ <p>Deletes a directory specified by <c><![CDATA[Name]]></c>.
+ Note that the directory must be empty before it can be successfully deleted
+ </p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index b3d64e72b4..3666bc7692 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
<header>
<copyright>
- <year>2005</year><year>2010</year>
+ <year>2005</year><year>2012</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -22,23 +22,17 @@
</legalnotice>
<title>ssh_sftpd</title>
- <prepared>Ingela Anderton Andin</prepared>
- <responsible></responsible>
- <docno>1</docno>
- <approved></approved>
- <checked></checked>
<date>2005-09-22</date>
- <rev>PA1</rev>
<file>ssh_sftpd.sgml</file>
</header>
<module>ssh_sftpd</module>
- <modulesummary>Specifies a channel process to handle a sftp subsystem.</modulesummary>
+ <modulesummary>Specifies the channel process to handle an sftp subsystem.</modulesummary>
<description>
<p>Specifies a channel process to handle a sftp subsystem.</p>
</description>
<section>
- <title>COMMON DATA TYPES </title>
+ <title>DATA TYPES </title>
<p><c>subsystem_spec() = {subsystem_name(), {channel_callback(), channel_init_args()}} </c></p>
<p><c>subsystem_name() = "sftp"</c></p>
<p><c>channel_callback() = atom()</c> - Name of the erlang module implementing the
@@ -65,27 +59,32 @@
</item>
<tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag>
<item>
- <p>Determines which module to call for communicating with
- the file server. Default value is <c>ssh_sftpd_file</c> that uses the
- file and filelib API:s to access the standard OTP file
- server. This option may be used to plug in the use of
- other file servers.</p>
- </item>
- <tag><c><![CDATA[{max_files, Integer}]]></c></tag>
- <item>
- <p>The default value is <c>0</c>, which means that there is no upper limit.
- If supplied, the number of filenames returned to the sftp client per <c>READDIR</c>
- request, is limited to at most the given value.</p>
- </item>
+ <p>Determines which module to call for accessing
+ the file server. The default value is <c>ssh_sftpd_file</c> that uses the
+ <seealso marker="kernel#file">file</seealso> and <seealso marker="kernel#filelib">filelib</seealso> API:s to access the standard OTP file
+ server. This option may be used to plug in
+ other file servers.</p>
+ </item>
+ <tag><c><![CDATA[{max_files, Integer}]]></c></tag>
+ <item>
+ <p>The default value is <c>0</c>, which means that there is no upper limit.
+ If supplied, the number of filenames returned to the sftp client per <c>READDIR</c>
+ request is limited to at most the given value.</p>
+ </item>
<tag><c><![CDATA[{root, String}]]></c></tag>
<item>
<p>Sets the sftp root directory. The user will then not be
- able to see any files above this root. If for instance
- the root is set to <c>/tmp</c> the user will see this
- directory as <c>/</c> and if the user does cd <c>/etc</c>
- the user will end up in <c>/tmp/etc</c>.
+ able to see any files above this root. If for instance
+ the root is set to <c>/tmp</c> the user will see this
+ directory as <c>/</c> and if the user does cd <c>/etc</c>
+ the user will end up in <c>/tmp/etc</c>.
</p>
</item>
+ <tag><c><![CDATA[{sftpd_vsn, integer()}]]></c></tag>
+ <item>
+ <p>Sets the sftp version to use, defaults to 5. Version 6 is under
+ development and limited.</p>
+ </item>
</taglist>
</desc>
</func>
diff --git a/lib/ssh/doc/src/user_guide.gif b/lib/ssh/doc/src/user_guide.gif
deleted file mode 100644
index e6275a803d..0000000000
--- a/lib/ssh/doc/src/user_guide.gif
+++ /dev/null
Binary files differ
diff --git a/lib/ssh/doc/src/usersguide.xml b/lib/ssh/doc/src/usersguide.xml
new file mode 100644
index 0000000000..c818003090
--- /dev/null
+++ b/lib/ssh/doc/src/usersguide.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2012</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>SSH User's Guide</title>
+ <prepared>OTP Team</prepared>
+ <date>2012-10-11</date>
+ <file>usersguide.xml</file>
+ </header>
+ <description>
+ <p>The <em>SSH</em> application implements the SSH (Secure Shell) protocol and
+ provides an SFTP (Secret File Transfer Protocol) client and server.
+ </p>
+ </description>
+ <xi:include href="introduction.xml"/>
+ <xi:include href="ssh_protocol.xml"/>
+ <xi:include href="using_ssh.xml"/>
+</part>
diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml
new file mode 100644
index 0000000000..87b811d591
--- /dev/null
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="iso-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2012</year>
+ <year>2013</year>
+ <holder>Ericsson AB. All Rights Reserved.</holder>
+ </copyright>
+ <legalnotice>
+ 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.
+
+ </legalnotice>
+
+ <title>Getting started</title>
+ <file>using_ssh.xml</file>
+ </header>
+
+ <section>
+ <title> General information</title>
+ <p>The examples in the following sections use the utility function
+ <seealso marker="ssh#start"> ssh:start/0 </seealso> that starts
+ all needed applications (crypto, public_key and ssh). All examples
+ are run in an Erlang shell, or in a bash shell using openssh to
+ illustrate how the erlang ssh application can be used. The
+ exampels are run as the user otptest on a local network where the
+ user is authorized to login in over ssh to the host "tarlop". If
+ nothing else is stated it is persumed that the otptest user has an
+ entry in tarlop's authorized_keys file (may log in via ssh without
+ entering a password). Also tarlop is a known host in the user
+ otptest's known_hosts file so that host verification can be done
+ without user interaction.
+ </p>
+ </section>
+
+ <section>
+ <title>Using the Erlang SSH Terminal Client</title>
+
+ <p>The user otptest, that has bash as default shell, uses the
+ ssh:shell/1 client to connect to the openssh daemon running on a
+ host called tarlop. Note that currently this client is very simple
+ and you should not be expected to be as fancy as the openssh
+ client.</p>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2> {ok, S} = ssh:shell("tarlop").
+ >pwd
+ /home/otptest
+ >exit
+ logout
+ 3>
+ </code>
+ </section>
+
+ <section>
+ <title>Running an Erlang SSH Daemon </title>
+
+ <p> The option system_dir must be a directory containing a host
+ key file and it defaults to /etc/ssh. For details see section
+ Configuration Files in <seealso
+ marker="ssh_app">ssh(6)</seealso>.
+ </p>
+
+ <note><p>Normally the /etc/ssh directory is only readable by root. </p>
+ </note>
+
+ <p> The option user_dir defaults to the users ~/.ssh directory</p>
+
+ <p>In the following example we generate new keys and host keys as
+ to be able to run the example without having root privilages</p>
+
+ <code>
+ $bash> ssh-keygen -t rsa -f /tmp/ssh_daemon/ssh_host_rsa_key
+ [...]
+ $bash> ssh-keygen -t rsa -f /tmp/otptest_user/.ssh/id_rsa
+ [...]
+ </code>
+
+ <p>Create the file /tmp/otptest_user/.ssh/authrized_keys and add the content
+ of /tmp/otptest_user/.ssh/id_rsa.pub Now we can do</p>
+
+ <code type="erl">
+ 1> ssh:start().
+ ok
+ 2> {ok, Sshd} = ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
+ {user_dir, "/tmp/otptest_user/.ssh"}]).
+ {ok,&lt;0.54.0>}
+ 3>
+ </code>
+
+ <p>Use the openssh client from a shell to connect to the Erlang ssh daemon.</p>
+
+ <code>
+ $bash> ssh tarlop -p 8989 -i /tmp/otptest_user/.ssh/id_rsa\
+ -o UserKnownHostsFile=/tmp/otptest_user/.ssh/known_hosts
+ The authenticity of host 'tarlop' can't be established.
+ RSA key fingerprint is 14:81:80:50:b1:1f:57:dd:93:a8:2d:2f:dd:90:ae:a8.
+ Are you sure you want to continue connecting (yes/no)? yes
+ Warning: Permanently added 'tarlop' (RSA) to the list of known hosts.
+ Eshell V5.10 (abort with ^G)
+ 1>
+ </code>
+
+ <p>There are two ways of shutting down an SSH daemon</p>
+
+ <p>1: Stops the listener, but leaves existing connections started by the listener up and running.</p>
+
+ <code type="erl">
+ 3> ssh:stop_listener(Sshd).
+ ok
+ 4>
+ </code>
+
+ <p>2: Stops the listener and all connections started by the listener.</p>
+
+ <code type="erl">
+ 3> ssh:stop_daemon(Sshd)
+ ok
+ 4>
+ </code>
+
+ </section>
+
+ <section>
+ <title>One Time Execution</title>
+
+ <p>In the following example the Erlang shell is the client process
+ that receives the channel replies. <note> If you run this example
+ in your environment you may get fewer or more messages back as
+ this depends on the OS and shell on the machine running the ssh
+ daemon. See also <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>
+ </note>
+ </p>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2> {ok, ConnectionRef} = ssh:connect("tarlop", 22, []).
+ {ok,&lt;0.57.0>}
+ 3>{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
+ {ok,0}
+ 4> success = ssh_connection:exec(ConnectionRef, ChannelId, "pwd", infinity).
+ 5> flush().
+ Shell got {ssh_cm,&lt;0.57.0>,{data,0,0,&lt;&lt;"/home/otptest\n">>}}
+ Shell got {ssh_cm,&lt;0.57.0>,{eof,0}}
+ Shell got {ssh_cm,&lt;0.57.0>,{exit_status,0,0}}
+ Shell got {ssh_cm,&lt;0.57.0>,{closed,0}}
+ ok
+ 6>
+ </code>
+
+ <p>Note only the channel is closed the connection is still up and can handle other channels</p>
+
+ <code type="erl" >
+ 6> {ok, NewChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
+ {ok,1}
+ ...
+ </code>
+ </section>
+
+ <section>
+ <title>SFTP (SSH File Transport Protocol) server</title>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
+ {user_dir, "/tmp/otptest_user/.ssh"},
+ {subsystems, [ssh_sftpd:subsystem_spec([{cwd, "/tmp/sftp/example"}])]}]).
+ {ok,&lt;0.54.0>}
+ 3>
+ </code>
+
+ <p> Run the openssh sftp client</p>
+
+ <code type="erl">
+ $bash> sftp -oPort=8989 -o IdentityFile=/tmp/otptest_user/.ssh/id_rsa\
+ -o UserKnownHostsFile=/tmp/otptest_user/.ssh/known_hosts tarlop
+ Connecting to tarlop...
+ sftp> pwd
+ Remote working directory: /tmp/sftp/example
+ sftp>
+ </code>
+ </section>
+
+ <section>
+ <title>SFTP (SSH File Transport Protocol) client</title>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2> {ok, ChannelPid, Connection} = ssh_sftp:start_channel("tarlop", []).
+ {ok,&lt;0.57.0>,&lt;0.51.0>}
+ 3> ssh_sftp:read_file(ChannelPid, "/home/otptest/test.txt").
+ {ok,&lt;&lt;"This is a test file\n">>}
+ </code>
+ </section>
+
+ <section>
+ <title>Creating a subsystem</title>
+
+ <p>A very small SSH subsystem that echos N bytes could be implemented like this.
+ See also <seealso marker="ssh_channel"> ssh_channel(3)</seealso> </p>
+
+ <code type="erl" >
+-module(ssh_echo_server).
+-behaviour(ssh_subsystem).
+-record(state, {
+ n,
+ id,
+ cm
+ }).
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([N]) ->
+ {ok, #state{n = N}}.
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}}.
+
+handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State) ->
+ M = N - size(Data),
+ case M > 0 of
+ true ->
+ ssh_connection:send(CM, ChannelId, Data),
+ {ok, State#state{n = M}};
+ false ->
+ &lt;&lt;SendData:N/binary, _/binary>> = Data,
+ ssh_connection:send(CM, ChannelId, SendData),
+ ssh_connection:send_eof(CM, ChannelId),
+ {stop, ChannelId, State}
+ end;
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 1, Data}}, State) ->
+ error_logger:format(standard_error, " ~p~n", [binary_to_list(Data)]),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}},
+ State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) ->
+ {stop, ChannelId, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+ </code>
+
+ <p>And run like this on the host tarlop with the keys generated in section 3.3</p>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2> ssh:daemon(8989, [{system_dir, "/tmp/ssh_daemon"},
+ {user_dir, "/tmp/otptest_user/.ssh"}
+ {subsystems, [{"echo_n", {ssh_echo_server, [10]}}]}]).
+ {ok,&lt;0.54.0>}
+ 3>
+ </code>
+
+ <code type="erl" >
+ 1> ssh:start().
+ ok
+ 2>{ok, ConnectionRef} = ssh:connect("tarlop", 8989, [{user_dir, "/tmp/otptest_user/.ssh"}]).
+ {ok,&lt;0.57.0>}
+ 3>{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity).
+ 4> success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity).
+ 5> ok = ssh_connection:send(ConnectionRef, ChannelId, "0123456789", infinity).
+ 6> flush().
+ {ssh_msg, &lt;0.57.0>, {data, 0, 1, "0123456789"}}
+ {ssh_msg, &lt;0.57.0>, {eof, 0}}
+ {ssh_msg, &lt;0.57.0>, {closed, 0}}
+ 7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).
+ </code>
+
+</section>
+
+</chapter>
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index b8eecd3fa2..323f0af191 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -41,7 +41,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN)
BEHAVIOUR_MODULES= \
ssh_sftpd_file_api \
ssh_channel \
- ssh_key_api
+ ssh_subsystem \
+ ssh_client_key_api \
+ ssh_server_key_api
MODULES= \
ssh \
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 316c09eb06..a0ba7cf7d9 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -10,6 +10,7 @@
ssh_auth,
ssh_bits,
ssh_cli,
+ ssh_client_key_api,
ssh_channel,
ssh_channel_sup,
ssh_connection,
@@ -21,13 +22,14 @@
sshd_sup,
ssh_file,
ssh_io,
- ssh_key_api,
ssh_math,
ssh_no_io,
+ ssh_server_key_api,
ssh_sftp,
ssh_sftpd,
ssh_sftpd_file,
ssh_sftpd_file_api,
+ ssh_subsystem,
ssh_subsystem_sup,
ssh_sup,
ssh_system_sup,
@@ -35,7 +37,7 @@
ssh_userreg,
ssh_xfer]},
{registered, []},
- {applications, [kernel, stdlib, crypto]},
+ {applications, [kernel, stdlib, crypto, public_key]},
{env, []},
{mod, {ssh_app, []}}]}.
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index d08dbafc32..826a11f1f4 100644
--- a/lib/ssh/src/ssh.appup.src
+++ b/lib/ssh/src/ssh.appup.src
@@ -19,24 +19,14 @@
{"%VSN%",
[
- {<<"2.1">>, [{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []},
- {load_module, ssh_connection_manager, soft_purge, soft_purge, []},
- {load_module, ssh_auth, soft_purge, soft_purge, []},
- {load_module, ssh_channel, soft_purge, soft_purge, []},
- {load_module, ssh_file, soft_purge, soft_purge, []}]},
- {load_module, ssh, soft_purge, soft_purge, []}]},
+ {<<"2.1.1">>, [{restart_application, ssh}]},
+ {<<"2.1">>, [{restart_application, ssh}]},
{<<"2.0\\.*">>, [{restart_application, ssh}]},
{<<"1\\.*">>, [{restart_application, ssh}]}
],
[
- {<<"2.1">>,[{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []},
- {load_module, ssh_connection_manager, soft_purge, soft_purge, []},
- {load_module, ssh_auth, soft_purge, soft_purge, []},
- {load_module, ssh_channel, soft_purge, soft_purge, []},
- {load_module, ssh_file, soft_purge, soft_purge, []}]},
- {load_module, ssh, soft_purge, soft_purge, []}]},
+ {<<"2.1.1">>, [{restart_application, ssh}]},
+ {<<"2.1">>,[{restart_application, ssh}]},
{<<"2.0\\.*">>, [{restart_application, ssh}]},
{<<"1\\.*">>, [{restart_application, ssh}]}
]
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 3395f73884..3ef26b1678 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -41,19 +41,23 @@
%%
%% Type = permanent | transient | temporary
%%
-%% Description: Starts the inets application. Default type
+%% Description: Starts the ssh application. Default type
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
+ application:start(crypto),
+ application:start(public_key),
application:start(ssh).
start(Type) ->
+ application:start(crypto, Type),
+ application:start(public_key, Type),
application:start(ssh, Type).
%%--------------------------------------------------------------------
%% Function: stop() -> ok
%%
-%% Description: Stops the inets application.
+%% Description: Stops the ssh application.
%%--------------------------------------------------------------------
stop() ->
application:stop(ssh).
@@ -76,10 +80,10 @@ connect(Host, Port, Options, Timeout) ->
{error, _Reason} = Error ->
Error;
{SocketOptions, SshOptions} ->
- DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false),
+ DisableIpv6 = proplists:get_value(ipv6_disabled, SshOptions, false),
Inet = inetopt(DisableIpv6),
do_connect(Host, Port, [Inet | SocketOptions],
- [{host, Host} | SshOptions], Timeout, DisableIpv6)
+ [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6)
end.
do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) ->
@@ -91,30 +95,39 @@ do_connect(Host, Port, SocketOptions, SshOptions, Timeout, DisableIpv6) ->
{ok, ConnectionSup} ->
{ok, Manager} =
ssh_connection_sup:connection_manager(ConnectionSup),
- receive
- {Manager, is_connected} ->
- {ok, Manager};
- %% When the connection fails
- %% ssh_connection_sup:connection_manager
- %% might return undefined as the connection manager
- %% could allready have terminated, so we will not
- %% match the Manager in this case
- {_, not_connected, {error, econnrefused}} when DisableIpv6 == false ->
- do_connect(Host, Port, proplists:delete(inet6, SocketOptions),
- SshOptions, Timeout, true);
- {_, not_connected, {error, Reason}} ->
- {error, Reason};
- {_, not_connected, Other} ->
- {error, Other}
- after Timeout ->
- ssh_connection_manager:stop(Manager),
- {error, timeout}
- end
+ msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout)
catch
exit:{noproc, _} ->
{error, ssh_not_started}
end.
-
+msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout) ->
+ receive
+ {Manager, is_connected} ->
+ {ok, Manager};
+ %% When the connection fails
+ %% ssh_connection_sup:connection_manager
+ %% might return undefined as the connection manager
+ %% could allready have terminated, so we will not
+ %% match the Manager in this case
+ {_, not_connected, {error, econnrefused}} when DisableIpv6 == false ->
+ do_connect(Host, Port, proplists:delete(inet6, SocketOptions),
+ SshOptions, Timeout, true);
+ {_, not_connected, {error, Reason}} ->
+ {error, Reason};
+ {_, not_connected, Other} ->
+ {error, Other};
+ {From, user_password} ->
+ Pass = io:get_password(),
+ From ! Pass,
+ msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout);
+ {From, question} ->
+ Answer = io:get_line(""),
+ From ! Answer,
+ msg_loop(Manager, DisableIpv6, Host, Port, SocketOptions, SshOptions, Timeout)
+ after Timeout ->
+ ssh_connection_manager:stop(Manager),
+ {error, timeout}
+ end.
%%--------------------------------------------------------------------
%% Function: close(ConnectionRef) -> ok
%%
@@ -160,7 +173,7 @@ daemon(HostAddr, Port, Options0) ->
_ ->
Options0
end,
- DisableIpv6 = proplists:get_value(ip_v6_disabled, Options0, false),
+ DisableIpv6 = proplists:get_value(ipv6_disabled, Options0, false),
{Host, Inet, Options} = case HostAddr of
any ->
{ok, Host0} = inet:gethostname(),
@@ -237,6 +250,13 @@ shell(Host, Port, Options) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+fix_idle_time(SshOptions) ->
+ case proplists:get_value(idle_time, SshOptions) of
+ undefined ->
+ [{idle_time, infinity}|SshOptions];
+ _ ->
+ SshOptions
+ end.
start_daemon(Host, Port, Options, Inet) ->
case handle_options(Options) of
{error, _Reason} = Error ->
@@ -248,7 +268,7 @@ start_daemon(Host, Port, Options, Inet) ->
do_start_daemon(Host, Port, Options, SocketOptions) ->
case ssh_system_sup:system_supervisor(Host, Port) of
undefined ->
- %% TODO: It would proably make more sense to call the
+ %% It would proably make more sense to call the
%% address option host but that is a too big change at the
%% monent. The name is a legacy name!
try sshd_sup:start_child([{address, Host},
@@ -309,8 +329,6 @@ handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{user_auth, _} = Opt | Rest],SocketOptions, SshOptions ) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{key_cb, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{role, _} = Opt | Rest], SocketOptions, SshOptions) ->
@@ -328,7 +346,10 @@ handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{failfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{ip_v6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) ->
+%%Backwards compatibility should not be underscore between ip and v6 in API
+handle_option([{ip_v6_disabled, Value} | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option({ipv6_disabled, Value}) | SshOptions]);
+handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
@@ -342,6 +363,14 @@ handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions).
@@ -355,8 +384,19 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when Value == true; Valu
Opt;
handle_ssh_option({user_interaction, Value} = Opt) when Value == true; Value == false ->
Opt;
-handle_ssh_option({public_key_alg, Value} = Opt) when Value == ssh_rsa; Value == ssh_dsa ->
+handle_ssh_option({public_key_alg, ssh_dsa}) ->
+ {public_key_alg, 'ssh-dss'};
+handle_ssh_option({public_key_alg, ssh_rsa}) ->
+ {public_key_alg, 'ssh-rsa'};
+handle_ssh_option({public_key_alg, Value} = Opt) when Value == 'ssh-rsa'; Value == 'ssh-dss' ->
Opt;
+handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 ->
+ case handle_pref_algs(Value, []) of
+ true ->
+ Opt;
+ _ ->
+ throw({error, {eoptions, Opt}})
+ end;
handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity ->
Opt;
handle_ssh_option({user, Value} = Opt) when is_list(Value) ->
@@ -371,8 +411,6 @@ handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)->
Opt;
handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value) ->
Opt;
-handle_ssh_option({user_auth, Value} = Opt) when is_function(Value) ->
- Opt;
handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) ->
Opt;
handle_ssh_option({compression, Value} = Opt) when is_atom(Value) ->
@@ -391,7 +429,8 @@ handle_ssh_option({disconnectfun , Value} = Opt) when is_function(Value) ->
Opt;
handle_ssh_option({failfun, Value} = Opt) when is_function(Value) ->
Opt;
-handle_ssh_option({ip_v6_disabled, Value} = Opt) when Value == true;
+
+handle_ssh_option({ipv6_disabled, Value} = Opt) when Value == true;
Value == false ->
Opt;
handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol),
@@ -407,6 +446,13 @@ handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module),
Opt;
handle_ssh_option({shell, Value} = Opt) when is_function(Value) ->
Opt;
+handle_ssh_option({quiet_mode, Value} = Opt) when Value == true;
+ Value == false ->
+ Opt;
+handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 ->
+ Opt;
+handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) ->
+ Opt;
handle_ssh_option(Opt) ->
throw({error, {eoptions, Opt}}).
@@ -415,7 +461,7 @@ handle_inet_option({active, _} = Opt) ->
"and activ is handled internaly user is not allowd"
"to specify this option"}});
handle_inet_option({inet, _} = Opt) ->
- throw({error, {{eoptions, Opt},"Is set internaly use ip_v6_disabled to"
+ throw({error, {{eoptions, Opt},"Is set internaly use ipv6_disabled to"
" enforce iv4 in the server, client will fallback to ipv4 if"
" it can not use ipv6"}});
handle_inet_option({reuseaddr, _} = Opt) ->
@@ -424,12 +470,27 @@ handle_inet_option({reuseaddr, _} = Opt) ->
%% Option verified by inet
handle_inet_option(Opt) ->
Opt.
-
+%% Check preferred algs
+handle_pref_algs([], Acc) ->
+ {true, lists:reverse(Acc)};
+handle_pref_algs([H|T], Acc) ->
+ case H of
+ ssh_dsa ->
+ handle_pref_algs(T, ['ssh-dss'| Acc]);
+ ssh_rsa ->
+ handle_pref_algs(T, ['ssh-rsa'| Acc]);
+ 'ssh-dss' ->
+ handle_pref_algs(T, ['ssh-dss'| Acc]);
+ 'ssh-rsa' ->
+ handle_pref_algs(T, ['ssh-rsa'| Acc]);
+ _ ->
+ false
+ end.
%% Has IPv6 been disabled?
inetopt(true) ->
inet;
inetopt(false) ->
- case gen_tcp:listen(0, [inet6, {ip, loopback}]) of
+ case gen_tcp:listen(0, [inet6]) of
{ok, Dummyport} ->
gen_tcp:close(Dummyport),
inet6;
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index aa452a8e09..cb0c7751f0 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -48,17 +48,18 @@ publickey_msg([Alg, #ssh{user = User,
case KeyCb:user_key(Alg, Opts) of
{ok, Key} ->
+ StrAlgo = algorithm_string(Alg),
PubKeyBlob = encode_public_key(Key),
SigData = build_sig_data(SessionId,
- User, Service, PubKeyBlob, Alg),
+ User, Service, PubKeyBlob, StrAlgo),
Sig = ssh_transport:sign(SigData, Hash, Key),
- SigBlob = list_to_binary([?string(Alg), ?binary(Sig)]),
+ SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]),
ssh_transport:ssh_packet(
#ssh_msg_userauth_request{user = User,
service = Service,
method = "publickey",
data = [?TRUE,
- ?string(Alg),
+ ?string(StrAlgo),
?binary(PubKeyBlob),
?binary(SigBlob)]},
Ssh);
@@ -71,7 +72,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb,
ssh_bits:install_messages(userauth_passwd_messages()),
Password = case proplists:get_value(password, Opts) of
undefined ->
- user_interaction(IoCb);
+ user_interaction(IoCb, Ssh);
PW ->
PW
end,
@@ -89,10 +90,10 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb,
Ssh)
end.
-user_interaction(ssh_no_io) ->
+user_interaction(ssh_no_io, _) ->
not_ok;
-user_interaction(IoCb) ->
- IoCb:read_password("ssh password: ").
+user_interaction(IoCb, Ssh) ->
+ IoCb:read_password("ssh password: ", Ssh).
%% See RFC 4256 for info on keyboard-interactive
@@ -118,15 +119,36 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
service = "ssh-connection",
method = "none",
data = <<>>},
- FirstAlg = algorithm(proplists:get_value(public_key_alg, Opts,
- ?PREFERRED_PK_ALG)),
- SecondAlg = other_alg(FirstAlg),
- AllowUserInt = proplists:get_value(user_interaction, Opts, true),
- Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"});
+ case proplists:get_value(pref_public_key_algs, Opts, false) of
+ false ->
+ FirstAlg = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG),
+ SecondAlg = other_alg(FirstAlg),
+ AllowUserInt = proplists:get_value(user_interaction, Opts, true),
+ Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt),
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ userauth_preference = Prefs,
+ userauth_methods = none,
+ service = "ssh-connection"});
+ Algs ->
+ FirstAlg = lists:nth(1, Algs),
+ case length(Algs) =:= 2 of
+ true ->
+ SecondAlg = other_alg(FirstAlg),
+ AllowUserInt = proplists:get_value(user_interaction, Opts, true),
+ Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt),
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ userauth_preference = Prefs,
+ userauth_methods = none,
+ service = "ssh-connection"});
+ _ ->
+ AllowUserInt = proplists:get_value(user_interaction, Opts, true),
+ Prefs = method_preference(FirstAlg, AllowUserInt),
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ userauth_preference = Prefs,
+ userauth_methods = none,
+ service = "ssh-connection"})
+ end
+ end;
{error, no_user} ->
ErrStr = "Could not determine the users name",
throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
@@ -287,6 +309,15 @@ method_preference(Alg1, Alg2, false) ->
{"publickey", ?MODULE, publickey_msg,[Alg2]},
{"password", ?MODULE, password_msg, []}
].
+method_preference(Alg1, true) ->
+ [{"publickey", ?MODULE, publickey_msg, [Alg1]},
+ {"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ];
+method_preference(Alg1, false) ->
+ [{"publickey", ?MODULE, publickey_msg, [Alg1]},
+ {"password", ?MODULE, password_msg, []}
+ ].
user_name(Opts) ->
Env = case os:type() of
@@ -327,7 +358,7 @@ verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
{ok, Key} = decode_public_key_v2(KeyBlob, Alg),
KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
- case KeyCb:is_auth_key(Key, User, Alg, Opts) of
+ case KeyCb:is_auth_key(Key, User, Opts) of
true ->
PlainText = build_sig_data(SessionId, User,
Service, KeyBlob, Alg),
@@ -350,9 +381,9 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) ->
?binary(KeyBlob)],
list_to_binary(Sig).
-algorithm(ssh_rsa) ->
+algorithm_string('ssh-rsa') ->
"ssh-rsa";
-algorithm(ssh_dsa) ->
+algorithm_string('ssh-dss') ->
"ssh-dss".
decode_keyboard_interactive_prompts(NumPrompts, Data) ->
@@ -370,11 +401,11 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) ->
%% Special case/fallback for just one prompt
%% (assumed to be the password prompt)
case proplists:get_value(password, Opts) of
- undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos);
+ undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts);
PW -> [PW]
end;
undefined ->
- keyboard_interact(IoCb, Name, Instr, PromptInfos);
+ keyboard_interact(IoCb, Name, Instr, PromptInfos, Opts);
KbdInteractFun ->
Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end,
PromptInfos),
@@ -388,15 +419,15 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) ->
end
end.
-keyboard_interact(IoCb, Name, Instr, Prompts) ->
+keyboard_interact(IoCb, Name, Instr, Prompts, Opts) ->
if Name /= "" -> IoCb:format("~s", [Name]);
true -> ok
end,
if Instr /= "" -> IoCb:format("~s", [Instr]);
true -> ok
end,
- lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt);
- ({Prompt, false}) -> IoCb:read_password(Prompt)
+ lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts);
+ ({Prompt, false}) -> IoCb:read_password(Prompt, Opts)
end,
Prompts).
@@ -426,10 +457,10 @@ userauth_pk_messages() ->
binary]} % key blob
].
-other_alg("ssh-rsa") ->
- "ssh-dss";
-other_alg("ssh-dss") ->
- "ssh-rsa".
+other_alg('ssh-rsa') ->
+ 'ssh-dss';
+other_alg('ssh-dss') ->
+ 'ssh-rsa'.
decode_public_key_v2(K_S, "ssh-rsa") ->
case ssh_bits:decode(K_S,[string,mpint,mpint]) of
["ssh-rsa", E, N] ->
diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl
index 7d7bad4436..6cd8e6bf14 100644
--- a/lib/ssh/src/ssh_auth.hrl
+++ b/lib/ssh/src/ssh_auth.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2012. 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
@@ -21,9 +21,9 @@
%%% Description: Ssh User Authentication Protocol
--define(SUPPORTED_AUTH_METHODS, "publickey,keyboard_interactive,password").
+-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
--define(PREFERRED_PK_ALG, ssh_rsa).
+-define(PREFERRED_PK_ALG, 'ssh-rsa').
-define(SSH_MSG_USERAUTH_REQUEST, 50).
-define(SSH_MSG_USERAUTH_FAILURE, 51).
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
index 1938858420..4e8f8538c2 100644
--- a/lib/ssh/src/ssh_channel.erl
+++ b/lib/ssh/src/ssh_channel.erl
@@ -23,14 +23,35 @@
-include("ssh_connect.hrl").
-%%% Optional callbacks handle_call/3, handle_cast/2, handle_msg/2,
-%%% code_change/3
-%% Should be further specified later
--callback init(Options::list()) ->
- {ok, State::term()} | {ok, State::term(), Timeout::timeout()} |
- {stop, Reason ::term()}.
-
--callback terminate(term(), term()) -> term().
+-callback init(Args :: term()) ->
+ {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} |
+ {stop, Reason :: term()} | ignore.
+-callback handle_call(Request :: term(), From :: {pid(), Tag :: term()},
+ State :: term()) ->
+ {reply, Reply :: term(), NewState :: term()} |
+ {reply, Reply :: term(), NewState :: term(), timeout() | hibernate} |
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate} |
+ {stop, Reason :: term(), Reply :: term(), NewState :: term()} |
+ {stop, Reason :: term(), NewState :: term()}.
+-callback handle_cast(Request :: term(), State :: term()) ->
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate} |
+ {stop, Reason :: term(), NewState :: term()}.
+
+-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} |
+ term()),
+ State :: term()) ->
+ term().
+-callback code_change(OldVsn :: (term() | {down, term()}), State :: term(),
+ Extra :: term()) ->
+ {ok, NewState :: term()} | {error, Reason :: term()}.
+
+-callback handle_msg(Msg ::term(), State :: term()) ->
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate} |
+ {stop, Reason :: term(), NewState :: term()}.
+
-callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()},
State::term()) -> {ok, State::term()} |
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 781e01b9d1..c8c610f8ef 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -81,7 +81,8 @@ handle_ssh_msg({ssh_cm, ConnectionManager,
height = not_zero(Height, 24),
pixel_width = PixWidth,
pixel_height = PixHeight,
- modes = Modes}},
+ modes = Modes},
+ buf = empty_buf()},
set_echo(State),
ssh_connection:reply_request(ConnectionManager, WantReply,
success, ChannelId),
diff --git a/lib/ssh/src/ssh_client_key.erl b/lib/ssh/src/ssh_client_key.erl
new file mode 100644
index 0000000000..2c48884dc2
--- /dev/null
+++ b/lib/ssh/src/ssh_client_key.erl
@@ -0,0 +1,34 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2012. 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_client_key).
+
+-include_lib("public_key/include/public_key.hrl").
+-include("ssh.hrl").
+
+-callback is_host_key(Key :: public_key(), Host :: string(),
+ Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: proplists:proplist()) ->
+ boolean().
+
+-callback user_key(Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: list()) ->
+ {ok, PrivateKey :: term()} | {error, string()}.
+
+
+-callback add_host_key(Host :: string(), PublicKey :: term(), Options :: list()) ->
+ ok | {error, Error::term()}.
diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl
new file mode 100644
index 0000000000..eed0b85f47
--- /dev/null
+++ b/lib/ssh/src/ssh_client_key_api.erl
@@ -0,0 +1,35 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2012. 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_client_key_api).
+
+-include_lib("public_key/include/public_key.hrl").
+-include("ssh.hrl").
+
+-callback is_host_key(PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term() , Host :: string(),
+ Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), ConnectOptions :: proplists:proplist()) ->
+ boolean().
+
+-callback user_key(Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), ConnectOptions :: proplists:proplists()) ->
+ {ok, PrivateKey :: #'RSAPrivateKey'{}| #'DSAPrivateKey'{} | term()} | {error, string()}.
+
+
+-callback add_host_key(Host :: string(), PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term(),
+ Options :: list()) ->
+ ok | {error, Error::term()}.
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index c2a7c63cbe..9424cdd423 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -177,7 +177,7 @@ close(ConnectionManager, ChannelId) ->
%% Description: Send status replies to requests that want such replies.
%%--------------------------------------------------------------------
reply_request(ConnectionManager, true, Status, ChannelId) ->
- ConnectionManager ! {ssh_cm, self(), {Status, ChannelId}},
+ ssh_connection_manager:reply_request(ConnectionManager, Status, ChannelId),
ok;
reply_request(_,false, _, _) ->
ok.
@@ -318,21 +318,22 @@ channel_data(ChannelId, DataType, Data,
From) ->
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} = Channel0 ->
- {SendList, Channel} = update_send_window(Channel0, DataType,
+ #channel{remote_id = Id, sent_close = false} = Channel0 ->
+ {SendList, Channel} = update_send_window(Channel0#channel{flow_control = From}, DataType,
Data, Connection),
Replies =
lists:map(fun({SendDataType, SendData}) ->
- {connection_reply, ConnectionPid,
- channel_data_msg(Id,
- SendDataType,
- SendData)}
+ {connection_reply, ConnectionPid,
+ channel_data_msg(Id,
+ SendDataType,
+ SendData)}
end, SendList),
FlowCtrlMsgs = flow_control(Replies,
- Channel#channel{flow_control = From},
+ Channel,
Cache),
{{replies, Replies ++ FlowCtrlMsgs}, Connection};
- undefined ->
+ _ ->
+ gen_server:reply(From, {error, closed}),
{noreply, Connection}
end.
@@ -386,20 +387,30 @@ handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
ConnectionPid, _) ->
case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{sent_close = Closed, remote_id = RemoteId} = Channel ->
+ #channel{sent_close = Closed, remote_id = RemoteId, flow_control = FlowControl} = Channel ->
ssh_channel:cache_delete(Cache, ChannelId),
{CloseMsg, Connection} =
reply_msg(Channel, Connection0, {closed, ChannelId}),
+
+ ConnReplyMsgs =
case Closed of
- true ->
- {{replies, [CloseMsg]}, Connection};
+ true -> [];
false ->
RemoteCloseMsg = channel_close_msg(RemoteId),
- {{replies,
- [{connection_reply,
- ConnectionPid, RemoteCloseMsg},
- CloseMsg]}, Connection}
- end;
+ [{connection_reply, ConnectionPid, RemoteCloseMsg}]
+ end,
+
+ %% if there was a send() in progress, make it fail
+ SendReplyMsgs =
+ case FlowControl of
+ undefined -> [];
+ From ->
+ [{flow_control, From, {error, closed}}]
+ end,
+
+ Replies = ConnReplyMsgs ++ [CloseMsg] ++ SendReplyMsgs,
+ {{replies, Replies}, Connection};
+
undefined ->
{{replies, []}, Connection0}
end;
@@ -441,7 +452,7 @@ handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
{SendList, Channel} = %% TODO: Datatype 0 ?
update_send_window(Channel0#channel{send_window_size = Size + Add},
- 0, <<>>, Connection),
+ 0, undefined, Connection),
Replies = lists:map(fun({Type, Data}) ->
{connection_reply, ConnectionPid,
@@ -1073,14 +1084,15 @@ request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
false ->
{{channel_data, ChannelPid, Reply}, Connection}
end.
+update_send_window(Channel, _, undefined,
+ #connection{channel_cache = Cache}) ->
+ do_update_send_window(Channel, Channel#channel.send_buf, Cache);
-update_send_window(Channel0, DataType, Data,
- #connection{channel_cache = Cache}) ->
- Buf0 = if Data == <<>> ->
- Channel0#channel.send_buf;
- true ->
- Channel0#channel.send_buf ++ [{DataType, Data}]
- end,
+update_send_window(Channel, DataType, Data,
+ #connection{channel_cache = Cache}) ->
+ do_update_send_window(Channel, Channel#channel.send_buf ++ [{DataType, Data}], Cache).
+
+do_update_send_window(Channel0, Buf0, Cache) ->
{Buf1, NewSz, Buf2} = get_window(Buf0,
Channel0#channel.send_packet_size,
Channel0#channel.send_window_size),
@@ -1125,13 +1137,13 @@ flow_control(Channel, Cache) ->
flow_control([], Channel, Cache) ->
ssh_channel:cache_update(Cache, Channel),
[];
-flow_control([_|_], #channel{flow_control = From} = Channel, Cache) ->
- case From of
- undefined ->
- [];
- _ ->
- [{flow_control, Cache, Channel, From, ok}]
- end.
+
+flow_control([_|_], #channel{flow_control = From,
+ send_buf = []} = Channel, Cache) when From =/= undefined ->
+ [{flow_control, Cache, Channel, From, ok}];
+flow_control(_,_,_) ->
+ [].
+
encode_pty_opts(Opts) ->
Bin = list_to_binary(encode_pty_opts2(Opts)),
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 5b3d1b8a1b..88b45111ff 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -35,7 +35,8 @@
-export([start_link/4, send/2, renegotiate/1, send_event/2,
connection_info/3,
- peer_address/1]).
+ peer_address/1,
+ renegotiate_data/1]).
%% gen_fsm callbacks
-export([hello/2, kexinit/2, key_exchange/2, new_keys/2,
@@ -85,6 +86,8 @@ send(ConnectionHandler, Data) ->
renegotiate(ConnectionHandler) ->
send_all_state_event(ConnectionHandler, renegotiate).
+renegotiate_data(ConnectionHandler) ->
+ send_all_state_event(ConnectionHandler, data_size).
connection_info(ConnectionHandler, From, Options) ->
send_all_state_event(ConnectionHandler, {info, From, Options}).
@@ -500,7 +503,22 @@ handle_event(renegotiate, StateName, State) ->
handle_event({info, From, Options}, StateName, #state{ssh_params = Ssh} = State) ->
spawn(?MODULE, ssh_info_handler, [Options, Ssh, From]),
{next_state, StateName, State};
-
+handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) ->
+ Sent = inet:getstat(State#state.socket, [send_oct]),
+ MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000),
+ case Sent >= MaxSent of
+ true ->
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
+ send_msg(SshPacket, State),
+ {next_state, connected,
+ next_packet(State#state{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg,
+ renegotiate = true})};
+ _ ->
+ {next_state, connected, next_packet(State)}
+ end;
+handle_event(data_size, StateName, State) ->
+ {next_state, StateName, State};
handle_event({unknown, Data}, StateName, State) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
send_msg(Msg, State),
@@ -718,8 +736,18 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) ->
available_host_keys = supported_host_keys(Role, KeyCb, Options)
}.
-supported_host_keys(client, _, _) ->
- ["ssh-rsa", "ssh-dss"];
+supported_host_keys(client, _, Options) ->
+ try
+ case extract_algs(proplists:get_value(pref_public_key_algs, Options, false), []) of
+ false ->
+ ["ssh-rsa", "ssh-dss"];
+ Algs ->
+ Algs
+ end
+ catch
+ exit:Reason ->
+ {stop, {shutdown, Reason}}
+ end;
supported_host_keys(server, KeyCb, Options) ->
lists:foldl(fun(Type, Acc) ->
case available_host_key(KeyCb, Type, Options) of
@@ -731,7 +759,19 @@ supported_host_keys(server, KeyCb, Options) ->
end, [],
%% Prefered alg last so no need to reverse
["ssh-dss", "ssh-rsa"]).
-
+extract_algs(false, _) ->
+ false;
+extract_algs([],[]) ->
+ false;
+extract_algs([], NewList) ->
+ lists:reverse(NewList);
+extract_algs([H|T], NewList) ->
+ case H of
+ 'ssh-dss' ->
+ extract_algs(T, ["ssh-dss"|NewList]);
+ 'ssh-rsa' ->
+ extract_algs(T, ["ssh-rsa"|NewList])
+ end.
available_host_key(KeyCb, "ssh-dss"= Alg, Opts) ->
case KeyCb:host_key('ssh-dss', Opts) of
{ok, _} ->
diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl
index e53cd4f4f7..94a9ed505f 100644
--- a/lib/ssh/src/ssh_connection_manager.erl
+++ b/lib/ssh/src/ssh_connection_manager.erl
@@ -40,7 +40,7 @@
close/2, stop/1, send/5,
send_eof/2]).
--export([open_channel/6, request/6, request/7, global_request/4, event/2,
+-export([open_channel/6, reply_request/3, request/6, request/7, global_request/4, event/2,
cast/2]).
%% Internal application API and spawn
@@ -62,6 +62,7 @@
latest_channel_id = 0,
opts,
channel_args,
+ idle_timer_ref, % timerref
connected
}).
@@ -95,6 +96,9 @@ request(ConnectionManager, ChannelId, Type, true, Data, Timeout) ->
request(ConnectionManager, ChannelId, Type, false, Data, _) ->
cast(ConnectionManager, {request, ChannelId, Type, Data}).
+reply_request(ConnectionManager, Status, ChannelId) ->
+ cast(ConnectionManager, {reply_request, Status, ChannelId}).
+
global_request(ConnectionManager, Type, true = Reply, Data) ->
case call(ConnectionManager,
{global_request, self(), Type, Reply, Data}) of
@@ -121,7 +125,8 @@ info(ConnectionManager, ChannelProcess) ->
%% or amount of data sent counter!
renegotiate(ConnectionManager) ->
cast(ConnectionManager, renegotiate).
-
+renegotiate_data(ConnectionManager) ->
+ cast(ConnectionManager, renegotiate_data).
connection_info(ConnectionManager, Options) ->
call(ConnectionManager, {connection_info, Options}).
@@ -163,7 +168,7 @@ send(ConnectionManager, ChannelId, Type, Data, Timeout) ->
call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout).
send_eof(ConnectionManager, ChannelId) ->
- cast(ConnectionManager, {eof, ChannelId}).
+ call(ConnectionManager, {eof, ChannelId}).
%%====================================================================
%% gen_server callbacks
@@ -200,6 +205,8 @@ init([client, Opts]) ->
ChannelPid = proplists:get_value(channel_pid, Opts),
self() !
{start_connection, client, [Parent, Address, Port, SocketOpts, Options]},
+ TimerRef = get_idle_time(Options),
+
{ok, #state{role = client,
client = ChannelPid,
connection_state = #connection{channel_cache = Cache,
@@ -208,6 +215,7 @@ init([client, Opts]) ->
connection_supervisor = Parent,
requests = []},
opts = Opts,
+ idle_timer_ref = TimerRef,
connected = false}}.
%%--------------------------------------------------------------------
@@ -227,6 +235,13 @@ handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) ->
%% channel is sent later when reply arrives from the connection
%% handler.
lists:foreach(fun send_msg/1, Replies),
+ SshOpts = proplists:get_value(ssh_opts, State0#state.opts),
+ case proplists:get_value(idle_time, SshOpts) of
+ infinity ->
+ ok;
+ _IdleTime ->
+ erlang:send_after(5000, self(), {check_cache, [], []})
+ end,
{noreply, State};
handle_call({request, ChannelId, Type, Data}, From, State0) ->
@@ -295,6 +310,18 @@ handle_call({data, ChannelId, Type, Data}, From,
channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From,
State);
+handle_call({eof, ChannelId}, _From,
+ #state{connection = Pid, connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id, sent_close = false} ->
+ send_msg({connection_reply, Pid,
+ ssh_connection:channel_eof_msg(Id)}),
+ {reply, ok, State};
+ _ ->
+ {reply, {error,closed}, State}
+ end;
+
handle_call({connection_info, Options}, From,
#state{connection = Connection} = State) ->
ssh_connection_handler:connection_info(Connection, From, Options),
@@ -343,7 +370,7 @@ handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data},
recv_packet_size = MaxPacketSize},
ssh_channel:cache_update(Cache, Channel),
State = add_request(true, ChannelId, From, State1),
- {noreply, State};
+ {noreply, remove_timer_ref(State)};
handle_call({send_window, ChannelId}, _From,
#state{connection_state =
@@ -388,6 +415,13 @@ handle_call({close, ChannelId}, _,
send_msg({connection_reply, Pid,
ssh_connection:channel_close_msg(Id)}),
ssh_channel:cache_update(Cache, Channel#channel{sent_close = true}),
+ SshOpts = proplists:get_value(ssh_opts, State#state.opts),
+ case proplists:get_value(idle_time, SshOpts) of
+ infinity ->
+ ok;
+ _IdleTime ->
+ erlang:send_after(5000, self(), {check_cache, [], []})
+ end,
{reply, ok, State};
undefined ->
{reply, ok, State}
@@ -431,6 +465,16 @@ handle_cast({request, ChannelId, Type, Data}, State0) ->
lists:foreach(fun send_msg/1, Replies),
{noreply, State};
+handle_cast({reply_request, Status, ChannelId}, #state{connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ State = case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = RemoteId} ->
+ cm_message({Status, RemoteId}, State0);
+ undefined ->
+ State0
+ end,
+ {noreply, State};
+
handle_cast({global_request, _, _, _, _} = Request, State0) ->
State = handle_global_request(Request, State0),
{noreply, State};
@@ -438,7 +482,9 @@ handle_cast({global_request, _, _, _, _} = Request, State0) ->
handle_cast(renegotiate, #state{connection = Pid} = State) ->
ssh_connection_handler:renegotiate(Pid),
{noreply, State};
-
+handle_cast(renegotiate_data, #state{connection = Pid} = State) ->
+ ssh_connection_handler:renegotiate_data(Pid),
+ {noreply, State};
handle_cast({adjust_window, ChannelId, Bytes},
#state{connection = Pid, connection_state =
#connection{channel_cache = Cache}} = State) ->
@@ -453,18 +499,6 @@ handle_cast({adjust_window, ChannelId, Bytes},
end,
{noreply, State};
-handle_cast({eof, ChannelId},
- #state{connection = Pid, connection_state =
- #connection{channel_cache = Cache}} = State) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = Id} ->
- send_msg({connection_reply, Pid,
- ssh_connection:channel_eof_msg(Id)}),
- {noreply, State};
- undefined ->
- {noreply, State}
- end;
-
handle_cast({success, ChannelId}, #state{connection = Pid} = State) ->
Msg = ssh_connection:channel_success_msg(ChannelId),
send_msg({connection_reply, Pid, Msg}),
@@ -489,6 +523,8 @@ handle_info({start_connection, server,
Exec = proplists:get_value(exec, Options),
CliSpec = proplists:get_value(ssh_cli, Options, {ssh_cli, [Shell]}),
ssh_connection_handler:send_event(Connection, socket_control),
+ erlang:send_after(3600000, self(), rekey),
+ erlang:send_after(60000, self(), rekey_data),
{noreply, State#state{connection = Connection,
connection_state =
CState#connection{address = Address,
@@ -505,12 +541,17 @@ handle_info({start_connection, client,
case (catch ssh_transport:connect(Parent, Address,
Port, SocketOpts, Options)) of
{ok, Connection} ->
+ erlang:send_after(60000, self(), rekey_data),
+ erlang:send_after(3600000, self(), rekey),
{noreply, State#state{connection = Connection}};
Reason ->
Pid ! {self(), not_connected, Reason},
{stop, {shutdown, normal}, State}
end;
-
+handle_info({check_cache, _ , _},
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ {noreply, check_cache(State, Cache)};
handle_info({ssh_cm, _Sender, Msg}, State0) ->
%% Backwards compatibility!
State = cm_message(Msg, State0),
@@ -523,7 +564,7 @@ handle_info({same_user, _}, State) ->
handle_info(ssh_connected, #state{role = client, client = Pid}
= State) ->
Pid ! {self(), is_connected},
- {noreply, State#state{connected = true}};
+ {noreply, State#state{connected = true, opts = handle_password(State#state.opts)}};
handle_info(ssh_connected, #state{role = server} = State) ->
{noreply, State#state{connected = true}};
@@ -534,8 +575,56 @@ handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, State) ->
%%% So that terminate will be run when supervisor is shutdown
handle_info({'EXIT', _Sup, Reason}, State) ->
- {stop, Reason, State}.
-
+ {stop, Reason, State};
+handle_info(rekey, State) ->
+ renegotiate(self()),
+ erlang:send_after(3600000, self(), rekey),
+ {noreply, State};
+handle_info(rekey_data, State) ->
+ renegotiate_data(self()),
+ erlang:send_after(60000, self(), rekey_data),
+ {noreply, State}.
+handle_password(Opts) ->
+ handle_rsa_password(handle_dsa_password(handle_normal_password(Opts))).
+handle_normal_password(Opts) ->
+ case proplists:get_value(ssh_opts, Opts, false) of
+ false ->
+ Opts;
+ SshOpts ->
+ case proplists:get_value(password, SshOpts, false) of
+ false ->
+ Opts;
+ _Password ->
+ NewOpts = [{password, undefined}|lists:keydelete(password, 1, SshOpts)],
+ [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)]
+ end
+ end.
+handle_dsa_password(Opts) ->
+ case proplists:get_value(ssh_opts, Opts, false) of
+ false ->
+ Opts;
+ SshOpts ->
+ case proplists:get_value(dsa_pass_phrase, SshOpts, false) of
+ false ->
+ Opts;
+ _Password ->
+ NewOpts = [{dsa_pass_phrase, undefined}|lists:keydelete(dsa_pass_phrase, 1, SshOpts)],
+ [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)]
+ end
+ end.
+handle_rsa_password(Opts) ->
+ case proplists:get_value(ssh_opts, Opts, false) of
+ false ->
+ Opts;
+ SshOpts ->
+ case proplists:get_value(rsa_pass_phrase, SshOpts, false) of
+ false ->
+ Opts;
+ _Password ->
+ NewOpts = [{rsa_pass_phrase, undefined}|lists:keydelete(rsa_pass_phrase, 1, SshOpts)],
+ [{ssh_opts, NewOpts}|lists:keydelete(ssh_opts, 1, Opts)]
+ end
+ end.
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
@@ -567,6 +656,45 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+get_idle_time(SshOptions) ->
+ case proplists:get_value(idle_time, SshOptions) of
+ infinity ->
+ infinity;
+ _IdleTime -> %% We dont want to set the timeout on first connect
+ undefined
+ end.
+check_cache(State, Cache) ->
+ %% Check the number of entries in Cache
+ case proplists:get_value(size, ets:info(Cache)) of
+ 0 ->
+ Opts = proplists:get_value(ssh_opts, State#state.opts),
+ case proplists:get_value(idle_time, Opts) of
+ infinity ->
+ State;
+ undefined ->
+ State;
+ Time ->
+ case State#state.idle_timer_ref of
+ undefined ->
+ TimerRef = erlang:send_after(Time, self(), {'EXIT', [], "Timeout"}),
+ State#state{idle_timer_ref=TimerRef};
+ _ ->
+ State
+ end
+ end;
+ _ ->
+ State
+ end.
+remove_timer_ref(State) ->
+ case State#state.idle_timer_ref of
+ infinity -> %% If the timer is not activated
+ State;
+ undefined -> %% If we already has cancelled the timer
+ State;
+ TimerRef -> %% Timer is active
+ erlang:cancel_timer(TimerRef),
+ State#state{idle_timer_ref = undefined}
+ end.
channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) ->
case ssh_connection:channel_data(Id, Type, Data, Connection0,
ConnectionPid, From) of
@@ -614,6 +742,8 @@ do_send_msg({connection_reply, Pid, Data}) ->
ssh_connection_handler:send(Pid, Msg);
do_send_msg({flow_control, Cache, Channel, From, Msg}) ->
ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
+ gen_server:reply(From, Msg);
+do_send_msg({flow_control, From, Msg}) ->
gen_server:reply(From, Msg).
handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From,
@@ -662,7 +792,7 @@ handle_channel_down(ChannelPid, #state{connection_state =
(_,Acc) ->
Acc
end, [], Cache),
- {{replies, []}, State}.
+ {{replies, []}, check_cache(State, Cache)}.
update_sys(Cache, Channel, Type, ChannelPid) ->
ssh_channel:cache_update(Cache,
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index a6b82a7a13..f115a32710 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -23,7 +23,8 @@
-module(ssh_file).
--behaviour(ssh_key_api).
+-behaviour(ssh_server_key_api).
+-behaviour(ssh_client_key_api).
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").
@@ -34,7 +35,7 @@
user_key/2,
is_host_key/4,
add_host_key/3,
- is_auth_key/4]).
+ is_auth_key/3]).
-define(PERM_700, 8#700).
@@ -53,8 +54,8 @@ host_key(Algorithm, Opts) ->
decode(File, Password).
-is_auth_key(Key, User, Alg, Opts) ->
- case lookup_user_key(Key, User, Alg, Opts) of
+is_auth_key(Key, User,Opts) ->
+ case lookup_user_key(Key, User, Opts) of
{ok, Key} ->
true;
_ ->
@@ -138,13 +139,13 @@ add_host_key(Host, Key, Opts) ->
Error
end.
-lookup_user_key(Key, User, Alg, Opts) ->
+lookup_user_key(Key, User, Opts) ->
SshDir = ssh_dir({remoteuser,User}, Opts),
- case lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys", Opts) of
+ case lookup_user_key_f(Key, User, SshDir, "authorized_keys", Opts) of
{ok, Key} ->
{ok, Key};
_ ->
- lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys2", Opts)
+ lookup_user_key_f(Key, User, SshDir, "authorized_keys2", Opts)
end.
@@ -213,9 +214,9 @@ do_lookup_host_key(Host, Alg, Opts) ->
Error -> Error
end.
-identity_key_filename("ssh-dss") ->
+identity_key_filename('ssh-dss') ->
"id_dsa";
-identity_key_filename("ssh-rsa") ->
+identity_key_filename('ssh-rsa') ->
"id_rsa".
identity_pass_phrase("ssh-dss") ->
@@ -261,9 +262,9 @@ host_name(Atom) when is_atom(Atom) ->
host_name(List) ->
List.
-key_match(#'RSAPublicKey'{}, "ssh-rsa") ->
+key_match(#'RSAPublicKey'{}, 'ssh-rsa') ->
true;
-key_match({_, #'Dss-Parms'{}}, "ssh-dss") ->
+key_match({_, #'Dss-Parms'{}}, 'ssh-dss') ->
true;
key_match(_, _) ->
false.
@@ -272,11 +273,11 @@ add_key_fd(Fd, Host,Key) ->
SshBin = public_key:ssh_encode([{Key, [{hostnames, [Host]}]}], known_hosts),
file:write(Fd, SshBin).
-lookup_user_key_f(_, _User, [], _Alg, _F, _Opts) ->
+lookup_user_key_f(_, _User, [], _F, _Opts) ->
{error, nouserdir};
-lookup_user_key_f(_, _User, nouserdir, _Alg, _F, _Opts) ->
+lookup_user_key_f(_, _User, nouserdir, _F, _Opts) ->
{error, nouserdir};
-lookup_user_key_f(Key, _User, Dir, _Alg, F, _Opts) ->
+lookup_user_key_f(Key, _User, Dir, F, _Opts) ->
FileName = filename:join(Dir, F),
case file:open(FileName, [read, binary]) of
{ok, Fd} ->
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 1dbd097423..01fc713569 100644
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2012. 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
@@ -23,37 +23,52 @@
-module(ssh_io).
--export([yes_no/1, read_password/1, read_line/1, format/2]).
+-export([yes_no/2, read_password/2, read_line/2, format/2]).
-import(lists, [reverse/1]).
+-include("ssh.hrl").
+read_line(Prompt, Ssh) ->
+ format("~s", [listify(Prompt)]),
+ proplists:get_value(user_pid, Ssh) ! {self(), question},
+ receive
+ Answer ->
+ Answer
+ end.
-read_line(Prompt) when is_list(Prompt) ->
- io:get_line(list_to_atom(Prompt));
-read_line(Prompt) when is_atom(Prompt) ->
- io:get_line(Prompt).
-
-read_ln(Prompt) ->
- trim(read_line(Prompt)).
-
-yes_no(Prompt) ->
+yes_no(Prompt, Ssh) ->
io:format("~s [y/n]?", [Prompt]),
- case read_ln('') of
- "y" -> yes;
- "n" -> no;
- "Y" -> yes;
- "N" -> no;
- _ ->
- io:format("please answer y or n\n"),
- yes_no(Prompt)
+ proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question},
+ receive
+ Answer ->
+ case trim(Answer) of
+ "y" -> yes;
+ "n" -> no;
+ "Y" -> yes;
+ "N" -> no;
+ y -> yes;
+ n -> no;
+ _ ->
+ io:format("please answer y or n\n"),
+ yes_no(Prompt, Ssh)
+ end
end.
-read_password(Prompt) ->
+read_password(Prompt, Ssh) ->
format("~s", [listify(Prompt)]),
- case io:get_password() of
- "" ->
- read_password(Prompt);
- Pass -> Pass
+ case is_list(Ssh) of
+ false ->
+ proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password};
+ _ ->
+ proplists:get_value(user_pid, Ssh) ! {self(), user_password}
+ end,
+ receive
+ Answer ->
+ case Answer of
+ "" ->
+ read_password(Prompt, Ssh);
+ Pass -> Pass
+ end
end.
listify(A) when is_atom(A) ->
diff --git a/lib/ssh/src/ssh_key_api.erl b/lib/ssh/src/ssh_key_api.erl
deleted file mode 100644
index 8085c12e21..0000000000
--- a/lib/ssh/src/ssh_key_api.erl
+++ /dev/null
@@ -1,45 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2011-2012. 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_key_api).
-
--include_lib("public_key/include/public_key.hrl").
--include("ssh.hrl").
-
--type ssh_algorithm() :: string().
--type file_error() :: file:posix() | badarg | system_limit | terminated.
-
--callback host_key(Algorithm :: ssh_algorithm(), Options :: list()) ->
- {ok, [{public_key(), Attributes::list()}]} | public_key()
- | {error, string()}.
-
--callback user_key(Algorithm :: ssh_algorithm(), Options :: list()) ->
- {ok, [{public_key(), Attributes::list()}]} | public_key()
- | {error, string()}.
-
--callback is_host_key(Key :: public_key(), PeerName :: string(),
- Algorithm :: ssh_algorithm(), Options :: list()) ->
- boolean().
-
--callback add_host_key(Host :: string(), Key :: public_key(), Options :: list()) ->
- ok | {error, file_error()}.
-
--callback is_auth_key(Key :: public_key(), User :: string(),
- Algorithm :: ssh_algorithm(), Options :: list()) ->
- boolean().
diff --git a/lib/ssh/src/ssh_server_key.erl b/lib/ssh/src/ssh_server_key.erl
new file mode 100644
index 0000000000..8140114990
--- /dev/null
+++ b/lib/ssh/src/ssh_server_key.erl
@@ -0,0 +1,33 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2012. 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_server_key).
+
+-include_lib("public_key/include/public_key.hrl").
+-include("ssh.hrl").
+
+-type ssh_algorithm() :: string().
+
+-callback host_key(Algorithm :: ssh_algorithm(), Options :: list()) ->
+ {ok, [{public_key(), Attributes::list()}]} | public_key()
+ | {error, string()}.
+
+-callback is_auth_key(Key :: public_key(), User :: string(),
+ Algorithm :: ssh_algorithm(), Options :: list()) ->
+ boolean().
diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl
new file mode 100644
index 0000000000..4fd660ecb5
--- /dev/null
+++ b/lib/ssh/src/ssh_server_key_api.erl
@@ -0,0 +1,30 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2011-2012. 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_server_key_api).
+
+-include_lib("public_key/include/public_key.hrl").
+-include("ssh.hrl").
+
+-callback host_key(Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), DaemonOptions :: proplists:proplist()) ->
+ {ok, PrivateKey :: #'RSAPrivateKey'{}| #'DSAPrivateKey'{} | term()} | {error, string()}.
+
+-callback is_auth_key(PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term(),
+ User :: string(), DaemonOptions :: proplists:proplist()) ->
+ boolean().
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index ec7b76b0b3..c7e8373840 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -24,7 +24,7 @@
-module(ssh_sftpd).
%%-behaviour(gen_server).
--behaviour(ssh_channel).
+-behaviour(ssh_subsystem).
-include_lib("kernel/include/file.hrl").
@@ -36,7 +36,7 @@
-export([subsystem_spec/1,
listen/1, listen/2, listen/3, stop/1]).
--export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2, code_change/3]).
+-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]).
-record(state, {
xf, % [{channel,ssh_xfer states}...]
@@ -119,23 +119,13 @@ init(Options) ->
{Root0, State0}
end,
MaxLength = proplists:get_value(max_files, Options, 0),
-
- Vsn = proplists:get_value(vsn, Options, 5),
-
+ Vsn = proplists:get_value(sftpd_vsn, Options, 5),
{ok, State#state{cwd = CWD, root = Root, max_files = MaxLength,
handles = [], pending = <<>>,
xf = #ssh_xfer{vsn = Vsn, ext = []}}}.
%%--------------------------------------------------------------------
-%% Function: code_change(OldVsn, State, Extra) -> {ok, NewState}
-%% Description:
-%%--------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-
-%%--------------------------------------------------------------------
%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
%%
%% Description: Handles channel messages
@@ -369,17 +359,21 @@ handle_op(?SSH_FXP_FSETSTAT, ReqId, <<?UINT32(HLen), BinHandle:HLen/binary,
State0
end;
handle_op(?SSH_FXP_REMOVE, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>,
- State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ State0 = #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}}) ->
Path = relate_file_name(BPath, State0),
- %% case FileMod:is_dir(Path) of %% This version 6 we still have ver 5
- %% true ->
- %% ssh_xfer:xf_send_status(State#state.xf, ReqId,
- %% ?SSH_FX_FILE_IS_A_DIRECTORY);
- %% false ->
- {Status, FS1} = FileMod:delete(Path, FS0),
- State1 = State0#state{file_state = FS1},
- send_status(Status, ReqId, State1);
- %%end;
+ {IsDir, _FS1} = FileMod:is_dir(Path, FS0),
+ case IsDir of %% This version 6 we still have ver 5
+ true when Vsn > 5 ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory");
+ true ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_FAILURE, "File is a directory");
+ false ->
+ {Status, FS1} = FileMod:delete(Path, FS0),
+ State1 = State0#state{file_state = FS1},
+ send_status(Status, ReqId, State1)
+ end;
handle_op(?SSH_FXP_RMDIR, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>,
State0 = #state{file_handler = FileMod, file_state = FS0}) ->
Path = relate_file_name(BPath, State0),
@@ -637,31 +631,34 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
do_open(ReqId, State, Path, Flags).
do_open(ReqId, State0, Path, Flags) ->
- #state{file_handler = FileMod, file_state = FS0, root = Root} = State0,
+ #state{file_handler = FileMod, file_state = FS0, root = Root, xf = #ssh_xfer{vsn = Vsn}} = State0,
XF = State0#state.xf,
F = [binary | Flags],
- %% case FileMod:is_dir(Path) of %% This is version 6 we still have 5
- %% true ->
- %% ssh_xfer:xf_send_status(State#state.xf, ReqId,
- %% ?SSH_FX_FILE_IS_A_DIRECTORY);
- %% false ->
-
- AbsPath = case Root of
- "" ->
- Path;
- _ ->
- relate_file_name(Path, State0)
- end,
-
- {Res, FS1} = FileMod:open(AbsPath, F, FS0),
- State1 = State0#state{file_state = FS1},
- case Res of
- {ok, IoDevice} ->
- add_handle(State1, XF, ReqId, file, {Path,IoDevice});
- {error, Error} ->
- ssh_xfer:xf_send_status(State1#state.xf, ReqId,
- ssh_xfer:encode_erlang_status(Error)),
- State1
+ {IsDir, _FS1} = FileMod:is_dir(Path, FS0),
+ case IsDir of
+ true when Vsn > 5 ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory");
+ true ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_FAILURE, "File is a directory");
+ false ->
+ AbsPath = case Root of
+ "" ->
+ Path;
+ _ ->
+ relate_file_name(Path, State0)
+ end,
+ {Res, FS1} = FileMod:open(AbsPath, F, FS0),
+ State1 = State0#state{file_state = FS1},
+ case Res of
+ {ok, IoDevice} ->
+ add_handle(State1, XF, ReqId, file, {Path,IoDevice});
+ {error, Error} ->
+ ssh_xfer:xf_send_status(State1#state.xf, ReqId,
+ ssh_xfer:encode_erlang_status(Error)),
+ State1
+ end
end.
%% resolve all symlinks in a path
diff --git a/lib/ssh/src/ssh_subsystem.erl b/lib/ssh/src/ssh_subsystem.erl
new file mode 100644
index 0000000000..5a9fa32668
--- /dev/null
+++ b/lib/ssh/src/ssh_subsystem.erl
@@ -0,0 +1,47 @@
+-module(ssh_subsystem).
+
+%% API to special server side channel that can be pluged into the erlang ssh daemeon
+-callback init(Args :: term()) ->
+ {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} |
+ {stop, Reason :: term()} | ignore.
+
+-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} |
+ term()),
+ State :: term()) ->
+ term().
+
+-callback handle_msg(Msg ::term(), State :: term()) ->
+ {noreply, NewState :: term()} |
+ {noreply, NewState :: term(), timeout() | hibernate} |
+ {stop, Reason :: term(), NewState :: term()}.
+
+-callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()},
+ State::term()) -> {ok, State::term()} |
+ {stop, ChannelId::integer(),
+ State::term()}.
+
+%%% API
+-export([start/4, start/5, start_link/4, start_link/5, enter_loop/1]).
+
+%% gen_server callbacks
+-export([init/1, terminate/2]).
+
+start(ConnectionManager, ChannelId, CallBack, CbInitArgs) ->
+ ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined).
+
+start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) ->
+ ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec).
+
+start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) ->
+ ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined).
+
+start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) ->
+ ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec).
+
+enter_loop(State) ->
+ ssh_channel:enter_loop(State).
+
+init(Args) ->
+ ssh_channel:init(Args).
+terminate(Reason, State) ->
+ ssh_channel:terminate(Reason, State).
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 1f912c9bdf..1abb69921d 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -133,7 +133,7 @@ kex_dh_gex_messages() ->
].
yes_no(Ssh, Prompt) ->
- (Ssh#ssh.io_cb):yes_no(Prompt).
+ (Ssh#ssh.io_cb):yes_no(Prompt, Ssh).
connect(ConnectionSup, Address, Port, SocketOpts, Opts) ->
Timeout = proplists:get_value(connect_timeout, Opts, infinity),
@@ -449,7 +449,7 @@ verify_host_key_rsa(SSH, K_S, H, H_SIG) ->
false ->
{error, bad_signature};
true ->
- known_host_key(SSH, Public, "ssh-rsa")
+ known_host_key(SSH, Public, 'ssh-rsa')
end;
_ ->
{error, bad_format}
@@ -464,7 +464,7 @@ verify_host_key_dss(SSH, K_S, H, H_SIG) ->
false ->
{error, bad_signature};
true ->
- known_host_key(SSH, Public, "ssh-dss")
+ known_host_key(SSH, Public, 'ssh-dss')
end;
_ ->
{error, bad_host_key_format}
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
index d5b6dd03d1..4dfd9ed8b0 100644
--- a/lib/ssh/src/ssh_xfer.erl
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -383,6 +383,8 @@ decode_status(Status) ->
?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle;
?SSH_FX_LOCK_CONFlICT -> lock_conflict;
?SSH_FX_NOT_A_DIRECTORY -> not_a_directory;
+ ?SSH_FX_FILE_IS_A_DIRECTORY -> file_is_a_directory;
+ ?SSH_FX_CANNOT_DELETE -> cannot_delete;
_ -> {error,Status}
end.
@@ -392,6 +394,9 @@ encode_erlang_status(Status) ->
eof -> ?SSH_FX_EOF;
enoent -> ?SSH_FX_NO_SUCH_FILE;
eacces -> ?SSH_FX_PERMISSION_DENIED;
+ eisdir -> ?SSH_FX_FILE_IS_A_DIRECTORY;
+ eperm -> ?SSH_FX_CANNOT_DELETE;
+ eexist -> ?SSH_FX_FILE_ALREADY_EXISTS;
_ -> ?SSH_FX_FAILURE
end.
diff --git a/lib/ssh/src/ssh_xfer.hrl b/lib/ssh/src/ssh_xfer.hrl
index c13950eb6e..0d85cf2094 100644
--- a/lib/ssh/src/ssh_xfer.hrl
+++ b/lib/ssh/src/ssh_xfer.hrl
@@ -58,7 +58,6 @@
%%% # SSH_FX_xxx
%%% Description: Response packet types for file transfer protocol.
%%%----------------------------------------------------------------------
-
-define(SSH_FX_OK, 0).
-define(SSH_FX_EOF, 1).
-define(SSH_FX_NO_SUCH_FILE, 2).
@@ -79,7 +78,18 @@
-define(SSH_FX_LOCK_CONFlICT, 17).
-define(SSH_FX_DIR_NOT_EMPTY, 18).
-define(SSH_FX_NOT_A_DIRECTORY, 19).
+-define(SSH_FX_INVALID_FILENAME, 20).
+-define(SSH_FX_LINK_LOOP, 21).
+-define(SSH_FX_CANNOT_DELETE, 22).
+-define(SSH_FX_INVALID_PARAMETER, 23).
-define(SSH_FX_FILE_IS_A_DIRECTORY, 24).
+-define(SSH_FX_BYTE_RANGE_LOCK_CONFLICT,25).
+-define(SSH_FX_BYTE_RANGE_LOCK_REFUSED, 26).
+-define(SSH_FX_DELETE_PENDING, 27).
+-define(SSH_FX_FILE_CORRUPT, 28).
+-define(SSH_FX_OWNER_INVALID, 29).
+-define(SSH_FX_GROUP_INVALID, 30).
+-define(SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK,31).
%%%----------------------------------------------------------------------
%%% # SSH_FILEXFER_xxx
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 25072688ad..f5db31baee 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -36,7 +36,9 @@ MODULES= \
ssh_to_openssh_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
- ssh_sftpd_erlclient_SUITE
+ ssh_sftpd_erlclient_SUITE \
+ ssh_connection_SUITE \
+ ssh_echo_server
HRL_FILES_NEEDED_IN_TEST= \
$(ERL_TOP)/lib/ssh/src/ssh.hrl \
diff --git a/lib/ssh/test/ssh.spec.vxworks b/lib/ssh/test/ssh.spec.vxworks
deleted file mode 100644
index 81f665283c..0000000000
--- a/lib/ssh/test/ssh.spec.vxworks
+++ /dev/null
@@ -1,3 +0,0 @@
-{topcase, {dir, "../ssh_test"}}.
-{require_nodenames, 1}.
-%{skip, {M, F, "Not yet implemented"}}.
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 2ceaa9daa5..efcb11f88f 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -22,7 +22,6 @@
-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).
@@ -30,78 +29,12 @@
-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.
+%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-init_per_suite(Config) ->
- case catch crypto:start() of
- ok ->
- 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) ->
- ssh:stop(),
- crypto:stop(),
- ok.
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
-%%--------------------------------------------------------------------
-%% 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) when TestCase == server_password_option;
- TestCase == server_userpassword_option ->
- UserDir = filename:join(?config(priv_dir, Config), nopubkey),
- ssh_test_lib:del_dirs(UserDir),
- end_per_testcase(Config);
-end_per_testcase(_TestCase, Config) ->
- end_per_testcase(Config).
-end_per_testcase(_Config) ->
- ssh:stop(),
- ok.
-
-%%--------------------------------------------------------------------
-%% 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() ->
[app_test,
{group, dsa_key},
@@ -110,17 +43,29 @@ all() ->
{group, rsa_pass_key},
{group, internal_error},
daemon_already_started,
- server_password_option, server_userpassword_option,
+ server_password_option,
+ server_userpassword_option,
close].
groups() ->
- [{dsa_key, [], [exec, exec_compressed, shell, known_hosts]},
- {rsa_key, [], [exec, exec_compressed, shell, known_hosts]},
+ [{dsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time, rekey]},
+ {rsa_key, [], [send, exec, exec_compressed, shell, known_hosts, idle_time, rekey]},
{dsa_pass_key, [], [pass_phrase]},
{rsa_pass_key, [], [pass_phrase]},
{internal_error, [], [internal_error]}
].
-
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ Config;
+ _Else ->
+ {skip, "Crypto could not be started!"}
+ end.
+end_per_suite(_Config) ->
+ ssh:stop(),
+ crypto:stop().
+%%--------------------------------------------------------------------
init_per_group(dsa_key, Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
@@ -173,11 +118,25 @@ end_per_group(internal_error, Config) ->
end_per_group(_, Config) ->
Config.
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+end_per_testcase(TestCase, Config) when TestCase == server_password_option;
+ TestCase == server_userpassword_option ->
+ UserDir = filename:join(?config(priv_dir, Config), nopubkey),
+ ssh_test_lib:del_dirs(UserDir),
+ end_per_testcase(Config);
+end_per_testcase(_TestCase, Config) ->
+ end_per_testcase(Config).
+end_per_testcase(_Config) ->
+ ssh:stop(),
+ ok.
-%% Test cases starts here.
%%--------------------------------------------------------------------
-app_test(suite) ->
- [];
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
app_test(doc) ->
["Application consistency test."];
app_test(Config) when is_list(Config) ->
@@ -188,8 +147,6 @@ misc_ssh_options(doc) ->
["Test that we can set some misc options not tested elsewhere, "
"some options not yet present are not decided if we should support or "
"if they need thier own test case."];
-misc_ssh_options(suite) ->
- [];
misc_ssh_options(Config) when is_list(Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
UserDir = ?config(priv_dir, Config),
@@ -208,10 +165,6 @@ misc_ssh_options(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
exec(doc) ->
["Test api function ssh_connection:exec"];
-
-exec(suite) ->
- [];
-
exec(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -232,7 +185,7 @@ exec(Config) when is_list(Config) ->
expected ->
ok;
Other0 ->
- test_server:fail(Other0)
+ ct:fail(Other0)
end,
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0),
@@ -246,7 +199,7 @@ exec(Config) when is_list(Config) ->
expected ->
ok;
Other1 ->
- test_server:fail(Other1)
+ ct:fail(Other1)
end,
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId1),
ssh:stop_daemon(Pid).
@@ -254,10 +207,6 @@ exec(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
exec_compressed(doc) ->
["Test that compression option works"];
-
-exec_compressed(suite) ->
- [];
-
exec_compressed(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -279,19 +228,58 @@ exec_compressed(Config) when is_list(Config) ->
expected ->
ok;
Other ->
- test_server:fail(Other)
+ ct:fail(Other)
end,
ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+idle_time(doc) ->
+ ["Idle timeout test"];
+idle_time(Config) ->
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false},
+ {idle_time, 2000}]),
+ {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
+ ssh_connection:close(ConnectionRef, Id),
+ receive
+ after 10000 ->
+ {error,channel_closed} = ssh_connection:session_channel(ConnectionRef, 1000)
+ end,
+ ssh:stop_daemon(Pid).
+%%--------------------------------------------------------------------
+rekey(doc) ->
+ ["Idle timeout test"];
+rekey(Config) ->
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {rekey_limit, 0}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false},
+ {rekey_limit, 0}]),
+ receive
+ after 15000 ->
+ %%By this time rekeying would have been done
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid)
+ end.
+%%--------------------------------------------------------------------
shell(doc) ->
["Test that ssh:shell/2 works"];
-
-shell(suite) ->
- [];
-
shell(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -299,76 +287,22 @@ shell(Config) when is_list(Config) ->
{_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
- test_server:sleep(500),
+ ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
receive
{'EXIT', _, _} ->
- test_server:fail(no_ssh_connection);
+ ct:fail(no_ssh_connection);
ErlShellStart ->
- test_server:format("Erlang shell start: ~p~n", [ErlShellStart]),
+ ct:pal("Erlang shell start: ~p~n", [ErlShellStart]),
do_shell(IO, Shell)
end.
-do_shell(IO, Shell) ->
- 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),
UserDir = ?config(priv_dir, Config),
@@ -385,8 +319,6 @@ daemon_already_started(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
server_password_option(doc) ->
["validate to server that uses the 'password' option"];
-server_password_option(suite) ->
- [];
server_password_option(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -412,7 +344,7 @@ server_password_option(Config) when is_list(Config) ->
{user_interaction, false},
{user_dir, UserDir}]),
- test_server:format("Test of wrong password: Error msg: ~p ~n", [Reason]),
+ ct:pal("Test of wrong password: Error msg: ~p ~n", [Reason]),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
@@ -421,8 +353,6 @@ server_password_option(Config) when is_list(Config) ->
server_userpassword_option(doc) ->
["validate to server that uses the 'password' option"];
-server_userpassword_option(suite) ->
- [];
server_userpassword_option(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -459,8 +389,6 @@ server_userpassword_option(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
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),
PrivDir = ?config(priv_dir, Config),
@@ -488,10 +416,6 @@ known_hosts(Config) when is_list(Config) ->
pass_phrase(doc) ->
["Test that we can use keyes protected by pass phrases"];
-
-pass_phrase(suite) ->
- [];
-
pass_phrase(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -513,10 +437,6 @@ pass_phrase(Config) when is_list(Config) ->
internal_error(doc) ->
["Test that client does not hang if disconnects due to internal error"];
-
-internal_error(suite) ->
- [];
-
internal_error(Config) when is_list(Config) ->
process_flag(trap_exit, true),
SystemDir = filename:join(?config(priv_dir, Config), system),
@@ -532,12 +452,29 @@ internal_error(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
-close(doc) ->
- ["Simulate that we try to close an already closed connection"];
+send(doc) ->
+ ["Test ssh_connection:send/3"];
+send(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:send(ConnectionRef, ChannelId, <<"Data">>),
+ ok = ssh_connection:send(ConnectionRef, ChannelId, << >>),
+ ssh:stop_daemon(Pid).
-close(suite) ->
- [];
+%%--------------------------------------------------------------------
+close(doc) ->
+ ["Simulate that we try to close an already closed connection"];
close(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
@@ -557,10 +494,8 @@ close(Config) when is_list(Config) ->
exit(CM, {shutdown, normal}),
ok = ssh:close(CM).
-
-
%%--------------------------------------------------------------------
-%% Internal functions
+%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
basic_test(Config) ->
@@ -571,3 +506,53 @@ basic_test(Config) ->
{ok, CM} = ssh:connect(Host, Port, ClientOpts),
ok = ssh:close(CM),
ssh:stop_daemon(Pid).
+
+do_shell(IO, Shell) ->
+ receive
+ ErlPrompt0 ->
+ ct:pal("Erlang prompt: ~p~n", [ErlPrompt0])
+ end,
+ IO ! {input, self(), "1+1.\r\n"},
+ receive
+ Echo0 ->
+ ct:pal("Echo: ~p ~n", [Echo0])
+ end,
+ receive
+ ?NEWLINE ->
+ ok
+ end,
+ receive
+ Result0 = <<"2">> ->
+ ct:pal("Result: ~p~n", [Result0])
+ end,
+ receive
+ ?NEWLINE ->
+ ok
+ end,
+ receive
+ ErlPrompt1 ->
+ ct:pal("Erlang prompt: ~p~n", [ErlPrompt1])
+ end,
+ exit(Shell, kill).
+ %%Does not seem to work in the testserver!
+ %% IO ! {input, self(), "q().\r\n"},
+ %% receive
+ %% ?NEWLINE ->
+ %% ok
+ %% end,
+ %% receive
+ %% Echo1 ->
+ %% ct:pal("Echo: ~p ~n", [Echo1])
+ %% end,
+ %% receive
+ %% ?NEWLINE ->
+ %% ok
+ %% end,
+ %% receive
+ %% Result1 ->
+ %% ct:pal("Result: ~p~n", [Result1])
+ %% end,
+ %% receive
+ %% {'EXIT', Shell, killed} ->
+ %% ok
+ %% end.
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
new file mode 100644
index 0000000000..acaf3d6eeb
--- /dev/null
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -0,0 +1,313 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2012. 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_connection_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-compile(export_all).
+
+-define(SSH_DEFAULT_PORT, 22).
+-define(EXEC_TIMEOUT, 10000).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [
+ {group, openssh_payload},
+ interrupted_send
+ ].
+groups() ->
+ [{openssh_payload, [], [simple_exec,
+ small_cat,
+ big_cat,
+ send_after_exit
+ ]}].
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ Config;
+ _Else ->
+ {skip, "Crypto could not be started!"}
+ end.
+
+end_per_suite(_Config) ->
+ crypto:stop().
+
+%%--------------------------------------------------------------------
+init_per_group(openssh_payload, _Config) ->
+ case gen_tcp:connect("localhost", 22, []) of
+ {error,econnrefused} ->
+ {skip,"No openssh deamon"};
+ {ok, Socket} ->
+ gen_tcp:close(Socket)
+ end;
+init_per_group(_, Config) ->
+ Config.
+
+end_per_group(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+end_per_testcase(_Config) ->
+ ssh:stop().
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+%%--------------------------------------------------------------------
+simple_exec(doc) ->
+ ["Simple openssh connectivity test for ssh_connection:exec"];
+
+simple_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),
+
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+small_cat(doc) ->
+ ["Use 'cat' to echo small data block back to us."];
+
+small_cat(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,
+ "cat", infinity),
+
+ Data = <<"I like spaghetti squash">>,
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Data}} ->
+ ok
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+big_cat(doc) ->
+ ["Use 'cat' to echo large data block back to us."];
+
+big_cat(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,
+ "cat", infinity),
+
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(Data)),
+
+ ct:pal("sending ~p byte binary~n",[size(Data)]),
+ ok = ssh_connection:send(ConnectionRef, ChannelId0, Data, 10000),
+ ok = ssh_connection:send_eof(ConnectionRef, ChannelId0),
+
+ %% collect echoed data until eof
+ case big_cat_rx(ConnectionRef, ChannelId0) of
+ {ok, Data} ->
+ ok;
+ {ok, Other} ->
+ case size(Data) =:= size(Other) of
+ true ->
+ ct:pal("received and sent data are same"
+ "size but do not match~n",[]);
+ false ->
+ ct:pal("sent ~p but only received ~p~n",
+ [size(Data), size(Other)])
+ end,
+ ct:fail(receive_data_mismatch);
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages (eof already consumed)
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+send_after_exit(doc) ->
+ ["Send channel data after the channel has been closed."];
+
+send_after_exit(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),
+
+ %% Shell command "false" will exit immediately
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,
+ "false", infinity),
+
+ timer:sleep(2000), %% Allow incoming eof/close/exit_status ssh messages to be processed
+
+ Data = <<"I like spaghetti squash">>,
+ case ssh_connection:send(ConnectionRef, ChannelId0, Data, 2000) of
+ {error, closed} -> ok;
+ ok ->
+ ct:fail({expected,{error,closed}});
+ {error, timeout} ->
+ ct:fail({expected,{error,closed}});
+ Else ->
+ ct:fail(Else)
+ end,
+
+ %% receive close messages
+ receive
+ {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef, {exit_status, ChannelId0, _}} ->
+ ok
+ end,
+ receive
+ {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
+ ok
+ end.
+%%--------------------------------------------------------------------
+interrupted_send(doc) ->
+ ["Use a subsystem that echos n char and then sends eof to cause a channel exit partway through a large send."];
+
+interrupted_send(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {subsystems, [{"echo_n", {ssh_echo_server, [4000000]}}]}]),
+
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ success = ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity),
+
+ %% build 10MB binary
+ Data = << <<X:32>> || X <- lists:seq(1,2500000)>>,
+
+ %% expect remote end to send us 4MB back
+ <<ExpectedData:4000000/binary, _/binary>> = Data,
+
+ %% pre-adjust receive window so the other end doesn't block
+ ssh_connection:adjust_window(ConnectionRef, ChannelId, size(ExpectedData) + 1),
+
+ case ssh_connection:send(ConnectionRef, ChannelId, Data, 10000) of
+ {error, closed} ->
+ ok;
+ Msg ->
+ ct:fail({expected,{error,closed}, got, Msg})
+ end,
+ receive_data(ExpectedData, ConnectionRef, ChannelId),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+big_cat_rx(ConnectionRef, ChannelId) ->
+ big_cat_rx(ConnectionRef, ChannelId, []).
+
+big_cat_rx(ConnectionRef, ChannelId, Acc) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
+ %% window was pre-adjusted, don't adjust again here
+ big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ {ok, iolist_to_binary(lists:reverse(Acc))}
+ after ?EXEC_TIMEOUT ->
+ timeout
+ end.
+
+receive_data(ExpectedData, ConnectionRef, ChannelId) ->
+ ExpectedData = collect_data(ConnectionRef, ChannelId).
+
+collect_data(ConnectionRef, ChannelId) ->
+ collect_data(ConnectionRef, ChannelId, []).
+
+collect_data(ConnectionRef, ChannelId, Acc) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
+ collect_data(ConnectionRef, ChannelId, [Data | Acc]);
+ {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
+ iolist_to_binary(lists:reverse(Acc))
+ after 5000 ->
+ timeout
+ end.
diff --git a/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..6ae7ee023d
--- /dev/null
+++ b/lib/ssh/test/ssh_connection_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_echo_server.erl b/lib/ssh/test/ssh_echo_server.erl
new file mode 100644
index 0000000000..007b00c373
--- /dev/null
+++ b/lib/ssh/test/ssh_echo_server.erl
@@ -0,0 +1,71 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2012. 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: Example ssh server
+-module(ssh_echo_server).
+-behaviour(ssh_subsytem).
+-record(state, {
+ n,
+ id,
+ cm
+ }).
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([N]) ->
+ {ok, #state{n = N}}.
+
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
+ {ok, State#state{id = ChannelId,
+ cm = ConnectionManager}}.
+
+handle_ssh_msg({ssh_cm, CM, {data, ChannelId, 0, Data}}, #state{n = N} = State) ->
+ M = N - size(Data),
+ case M > 0 of
+ true ->
+ ssh_connection:send(CM, ChannelId, Data),
+ {ok, State#state{n = M}};
+ false ->
+ <<SendData:N/binary, _/binary>> = Data,
+ ssh_connection:send(CM, ChannelId, SendData),
+ ssh_connection:send_eof(CM, ChannelId),
+ {stop, ChannelId, State}
+ end;
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 1, Data}}, State) ->
+ error_logger:format(standard_error, " ~p~n", [binary_to_list(Data)]),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, _Error, _}},
+ State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, _Status}}, State) ->
+ {stop, ChannelId, State}.
+
+terminate(_Reason, _State) ->
+ ok.
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index d40b1d544d..232161d029 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -24,7 +24,6 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
-
-include_lib("kernel/include/file.hrl").
% Default timetrap timeout
@@ -33,16 +32,18 @@
-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.
+%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group, erlang_server},
+ {group, openssh_server}].
+
+
init_per_suite(Config) ->
case (catch crypto:start()) of
ok ->
@@ -52,35 +53,58 @@ init_per_suite(Config) ->
{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) ->
ssh:stop(),
crypto:stop(),
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
+groups() ->
+ [{erlang_server, [], [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]},
+ {openssh_server, [], [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]}].
+
+init_per_group(erlang_server, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ SysDir = ?config(data_dir, Config),
+ Sftpd =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {user_passwords,
+ [{?USER, ?PASSWD}]},
+ {failfun,
+ fun ssh_test_lib:failfun/2}]),
+ [{group, erlang_server}, {sftpd, Sftpd} | Config];
+
+init_per_group(openssh_server, Config) ->
+ Host = ssh_test_lib:hostname(),
+ case (catch ssh_sftp:start_channel(Host,
+ [{user_interaction, false},
+ {silently_accept_hosts, true}])) of
+ {ok, _ChannelPid, Connection} ->
+ ssh:close(Connection),
+ [{group, openssh_server} | Config];
+ _ ->
+ {skip, "No openssh server"}
+ end.
+
+end_per_group(erlang_server, Config) ->
+ Config;
+end_per_group(_, Config) ->
+ Config.
+
%%--------------------------------------------------------------------
+
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),
+ Dog = ct:timetrap(?default_timeout),
case ?config(group, Config) of
erlang_server ->
@@ -105,14 +129,6 @@ init_per_testcase(Case, Config) ->
[{sftp, Sftp}, {watchdog, Dog} | TmpConfig]
end.
-%%--------------------------------------------------------------------
-%% 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(rename_file, Config) ->
PrivDir = ?config(priv_dir, Config),
NewFileName = filename:join(PrivDir, "test.txt"),
@@ -124,69 +140,13 @@ end_per_testcase(_, Config) ->
end_per_testcase(Config) ->
{Sftp, Connection} = ?config(sftp, Config),
ssh_sftp:stop_channel(Sftp),
- ssh:close(Connection),
- Dog = ?config(watchdog, Config),
- test_server:timetrap_cancel(Dog),
- ok.
+ ssh:close(Connection).
%%--------------------------------------------------------------------
-%% 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() ->
- [{group, erlang_server},
- {group, openssh_server}].
-
-groups() ->
- [{erlang_server, [], [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]},
- {openssh_server, [], [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]}].
-
-init_per_group(erlang_server, Config) ->
- PrivDir = ?config(priv_dir, Config),
- SysDir = ?config(data_dir, Config),
- Sftpd =
- ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, PrivDir},
- {user_passwords,
- [{?USER, ?PASSWD}]},
- {failfun,
- fun ssh_test_lib:failfun/2}]),
- [{group, erlang_server}, {sftpd, Sftpd} | Config];
-
-init_per_group(openssh_server, Config) ->
- Host = ssh_test_lib:hostname(),
- case (catch ssh_sftp:start_channel(Host,
- [{user_interaction, false},
- {silently_accept_hosts, true}])) of
- {ok, _ChannelPid, Connection} ->
- ssh:close(Connection),
- [{group, openssh_server} | Config];
- _ ->
- {skip, "No openssh server"}
- end.
-
-end_per_group(erlang_server, Config) ->
- Config;
-end_per_group(_, Config) ->
- Config.
-
-
-%% Test cases starts here.
+%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
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"),
@@ -198,21 +158,15 @@ open_close_file(Config) when is_list(Config) ->
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.
+ ok = open_close_file(Sftp, FileName, [read, binary]).
open_close_file(Server, File, Mode) ->
{ok, Handle} = ssh_sftp:open(Server, File, Mode),
- ok = ssh_sftp:close(Server, Handle),
- ok.
-
+ ok = ssh_sftp:close(Server, Handle).
%%--------------------------------------------------------------------
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),
@@ -220,138 +174,92 @@ open_close_dir(Config) when is_list(Config) ->
{ok, Handle} = ssh_sftp:opendir(Sftp, PrivDir),
ok = ssh_sftp:close(Sftp, Handle),
- {error, _} = ssh_sftp:opendir(Sftp, FileName),
+ {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, 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.
+ ct:pal("sftp list dir: ~p~n", [Files]).
%%--------------------------------------------------------------------
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.
+ {ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
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.
-
+ {error, _} = ssh_sftp:delete(Sftp, FileName).
%%--------------------------------------------------------------------
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]),
-
+ ct:pal("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]),
+ ct:pal("FileName: ~p, Files: ~p~n", [FileName, NewFiles]),
false = lists:member(filename:basename(FileName), NewFiles),
- true = lists:member(filename:basename(NewFileName), NewFiles),
-
- ok.
+ true = lists:member(filename:basename(NewFileName), NewFiles).
%%--------------------------------------------------------------------
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.
+ {error, _} = ssh_sftp:del_dir(Sftp, PrivDir).
%%--------------------------------------------------------------------
links(doc) ->
["Tests API function make_symlink/3"];
-links(suite) ->
- [];
links(Config) when is_list(Config) ->
- case test_server:os_type() of
+ case os:type() of
{win32, _} ->
{skip, "Links are not fully supported by windows"};
_ ->
@@ -361,74 +269,60 @@ links(Config) when is_list(Config) ->
LinkFileName = filename:join(PrivDir, "link_test.txt"),
ok = ssh_sftp:make_symlink(Sftp, LinkFileName, FileName),
- {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName),
- ok
+ {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName)
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),
+ {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.
+ ct:pal("SFTP: ~p FILE: ~p~n", [FileInfo, NewFileInfo]).
%%--------------------------------------------------------------------
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),
+ {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.
+ ok = file:write_file(FileName, "hello again").
%%--------------------------------------------------------------------
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]),
+ ct:pal("Data: ~p~n", [Data]),
ok;
Msg ->
- test_server:fail(Msg)
- end,
- ok.
+ ct:fail(Msg)
+ end.
%%--------------------------------------------------------------------
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),
@@ -441,16 +335,13 @@ async_write(Config) when is_list(Config) ->
{async_reply, Ref, ok} ->
{ok, Data} = file:read_file(FileName);
Msg ->
- test_server:fail(Msg)
- end,
- ok.
+ ct:fail(Msg)
+ end.
%%--------------------------------------------------------------------
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"),
@@ -458,7 +349,6 @@ position(Config) when is_list(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}),
@@ -477,15 +367,11 @@ position(Config) when is_list(Config) ->
{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.
+ {ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).
%%--------------------------------------------------------------------
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"),
@@ -494,7 +380,6 @@ pos_read(Config) when is_list(Config) ->
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!",
@@ -503,21 +388,17 @@ pos_read(Config) when is_list(Config) ->
{async_reply, Ref, {ok, NewData}} ->
ok;
Msg ->
- test_server:fail(Msg)
+ ct:fail(Msg)
end,
NewData1 = "hopp",
- {ok, NewData1} = ssh_sftp:pread(Sftp, Handle, {bof, 4}, 4),
+ {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),
@@ -533,17 +414,16 @@ pos_write(Config) when is_list(Config) ->
{async_reply, Ref, ok} ->
ok;
Msg ->
- test_server:fail(Msg)
+ ct: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, NewData1} = ssh_sftp:read_file(Sftp, FileName).
- ok.
-
-%% Internal functions
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
prep(Config) ->
PrivDir = ?config(priv_dir, Config),
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 695a7caa7d..5aa46872ee 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -24,12 +24,10 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
--include("test_server_line.hrl").
+-include_lib("kernel/include/file.hrl").
-include("ssh_xfer.hrl").
-include("ssh.hrl").
--include_lib("kernel/include/file.hrl").
-
-define(USER, "Alladin").
-define(PASSWD, "Sesame").
-define(XFER_PACKET_SIZE, 32768).
@@ -41,16 +39,33 @@
-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.
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+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,
+ relpath,
+ sshd_read_file,
+ ver6_basic].
+
+groups() ->
+ [].
+
%%--------------------------------------------------------------------
+
init_per_suite(Config) ->
case (catch crypto:start()) of
ok ->
@@ -66,34 +81,24 @@ init_per_suite(Config) ->
{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) ->
SysDir = ?config(priv_dir, Config),
ssh_test_lib:clean_dsa(SysDir),
UserDir = filename:join(?config(priv_dir, Config), nopubkey),
file:del_dir(UserDir),
ssh:stop(),
- crypto:stop(),
- ok.
+ crypto:stop().
%%--------------------------------------------------------------------
-%% 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_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
%%--------------------------------------------------------------------
+
init_per_testcase(TestCase, Config) ->
ssh:start(),
prep(Config),
@@ -102,12 +107,18 @@ init_per_testcase(TestCase, Config) ->
SystemDir = filename:join(?config(priv_dir, Config), system),
Port = ssh_test_lib:inet_port(node()),
-
- {ok, Sftpd} =
- ssh_sftpd:listen(Port, [{system_dir, SystemDir},
- {user_dir, PrivDir},
- {user_passwords,[{?USER, ?PASSWD}]},
- {pwdfun, fun(_,_) -> true end}]),
+ Options = [{system_dir, SystemDir},
+ {user_dir, PrivDir},
+ {user_passwords,[{?USER, ?PASSWD}]},
+ {pwdfun, fun(_,_) -> true end}],
+ {ok, Sftpd} = case TestCase of
+ ver6_basic ->
+ SubSystems = [ssh_sftpd:subsystem_spec([{sftpd_vsn, 6}])],
+ ssh:daemon(Port, [{subsystems, SubSystems}|Options]);
+ _ ->
+ SubSystems = [ssh_sftpd:subsystem_spec([])],
+ ssh:daemon(Port, [{subsystems, SubSystems}|Options])
+ end,
Cm = ssh_test_lib:connect(Port,
[{user_dir, ClientUserDir},
@@ -138,56 +149,22 @@ init_per_testcase(TestCase, Config) ->
{ok, <<?SSH_FXP_VERSION, ?UINT32(Version), _Ext/binary>>, _}
= reply(Cm, Channel),
- test_server:format("Client: ~p Server ~p~n", [ProtocolVer, Version]),
+ ct:pal("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.
+ ssh:stop().
%%--------------------------------------------------------------------
-%% 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.
+%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
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"),
@@ -214,15 +191,11 @@ open_close_file(Config) when is_list(Config) ->
?UINT32(?SSH_FX_FAILURE), _/binary>>, _} =
open_file(PrivDir, Cm, Channel, NewReqId1,
?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
- ?SSH_FXF_OPEN_EXISTING),
-
- ok.
+ ?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
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),
@@ -250,8 +223,6 @@ open_close_dir(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
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"),
@@ -270,28 +241,22 @@ read_file(Config) when is_list(Config) ->
Data/binary>>, _} =
read_file(Handle, 100, 0, Cm, Channel, NewReqId),
- {ok, Data} = file:read_file(FileName),
+ {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.
+ ok = read_dir(Handle, Cm, Channel, ReqId).
%%--------------------------------------------------------------------
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"),
@@ -311,15 +276,11 @@ write_file(Config) when is_list(Config) ->
_/binary>>, _}
= write_file(Handle, Data, 0, Cm, Channel, NewReqId),
- {ok, Data} = file:read_file(FileName),
-
- ok.
+ {ok, Data} = file:read_file(FileName).
%%--------------------------------------------------------------------
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"),
@@ -336,15 +297,11 @@ remove_file(Config) when is_list(Config) ->
{ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
?UINT32(?SSH_FX_FAILURE), _/binary>>, _} =
- remove(PrivDir, Cm, Channel, NewReqId),
-
- ok.
+ remove(PrivDir, Cm, Channel, NewReqId).
%%--------------------------------------------------------------------
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"),
@@ -377,15 +334,11 @@ rename_file(Config) when is_list(Config) ->
{ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2),
?UINT32(?SSH_FX_OP_UNSUPPORTED), _/binary>>, _} =
rename(FileName, NewFileName, Cm, Channel, NewReqId2, 6,
- ?SSH_FXP_RENAME_ATOMIC),
-
- ok.
+ ?SSH_FXP_RENAME_ATOMIC).
%%--------------------------------------------------------------------
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),
@@ -395,7 +348,7 @@ mk_rm_dir(Config) when is_list(Config) ->
_/binary>>, _} = mkdir(DirName, Cm, Channel, ReqId),
NewReqId = 1,
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), ?UINT32(?SSH_FX_FAILURE),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId), ?UINT32(?SSH_FX_FILE_ALREADY_EXISTS),
_/binary>>, _} = mkdir(DirName, Cm, Channel, NewReqId),
NewReqId1 = 2,
@@ -404,16 +357,13 @@ mk_rm_dir(Config) when is_list(Config) ->
NewReqId2 = 3,
{ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2), ?UINT32(?SSH_FX_NO_SUCH_FILE),
- _/binary>>, _} = rmdir(DirName, Cm, Channel, NewReqId2),
+ _/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
+ case os:type() of
{win32, _} ->
{skip, "Not a relevant test on windows"};
_ ->
@@ -432,20 +382,16 @@ real_path(Config) when is_list(Config) ->
RealPath = filename:absname(binary_to_list(Path)),
AbsPrivDir = filename:absname(PrivDir),
- test_server:format("Path: ~p PrivDir: ~p~n", [RealPath, AbsPrivDir]),
-
- true = RealPath == AbsPrivDir,
+ ct:pal("Path: ~p PrivDir: ~p~n", [RealPath, AbsPrivDir]),
- ok
+ true = RealPath == AbsPrivDir
end.
%%--------------------------------------------------------------------
links(doc) ->
[];
-links(suite) ->
- [];
links(Config) when is_list(Config) ->
- case test_server:os_type() of
+ case os:type() of
{win32, _} ->
{skip, "Links are not fully supported by windows"};
_ ->
@@ -467,15 +413,12 @@ links(Config) when is_list(Config) ->
true = binary_to_list(Path) == FileName,
- test_server:format("Path: ~p~n", [binary_to_list(Path)]),
- ok
+ ct:pal("Path: ~p~n", [binary_to_list(Path)])
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"),
@@ -536,16 +479,13 @@ retrieve_attributes(Config) when is_list(Config) ->
Owner = list_to_integer(binary_to_list(BinOwner)),
Group = list_to_integer(binary_to_list(BinGroup))
- end, AttrValues),
+ 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) ->
- case test_server:os_type() of
+ case os:type() of
{win32, _} ->
{skip, "Known error bug in erts file:read_file_info"};
_ ->
@@ -574,10 +514,10 @@ set_attributes(Config) when is_list(Config) ->
%% 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]),
+ ct:pal("Org: ~p New: ~p~n", [OrigPermissions, NewPermissions]),
true = OrigPermissions =/= NewPermissions,
- test_server:format("Try to open the file"),
+ ct:pal("Try to open the file"),
NewReqId = 2,
{ok, <<?SSH_FXP_HANDLE, ?UINT32(NewReqId), Handle/binary>>, _} =
open_file(FileName, Cm, Channel, NewReqId,
@@ -589,25 +529,20 @@ set_attributes(Config) when is_list(Config) ->
NewReqId1 = 3,
- test_server:format("Set original permissions on the now open file"),
+ ct:pal("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
+ OrigPermissions = NewFileInfo1#file_info.mode
end.
%%--------------------------------------------------------------------
-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) ->
+ver3_rename(doc) ->
+ ["Test that ver3 rename message is handled OTP 6352"];
+ver3_rename(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
NewFileName = filename:join(PrivDir, "test1.txt"),
@@ -616,22 +551,16 @@ ver3_rename_OTP_6352(Config) when is_list(Config) ->
{ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
?UINT32(?SSH_FX_OK), _/binary>>, _} =
- rename(FileName, NewFileName, Cm, Channel, ReqId, 3, 0),
-
- ok.
+ rename(FileName, NewFileName, Cm, Channel, ReqId, 3, 0).
%%--------------------------------------------------------------------
-seq10670(doc) ->
- ["Check that realpath works ok"];
-
-seq10670(suite) ->
- [];
-
-seq10670(Config) when is_list(Config) ->
+relpath(doc) ->
+ ["Check that realpath works ok seq10670"];
+relpath(Config) when is_list(Config) ->
ReqId = 0,
{Cm, Channel} = ?config(sftp, Config),
- case test_server:os_type() of
+ case os:type() of
{win32, _} ->
{skip, "Not a relevant test on windows"};
_ ->
@@ -644,11 +573,45 @@ seq10670(Config) when is_list(Config) ->
{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
+%%--------------------------------------------------------------------
+sshd_read_file(doc) ->
+ ["Test SSH_FXP_READ command, using sshd-server"];
+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).
+ver6_basic(doc) ->
+ ["Test SFTP Version 6"];
+ver6_basic(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_STATUS, ?UINT32(ReqId), % Ver 6 we have 5
+ ?UINT32(?SSH_FX_FILE_IS_A_DIRECTORY), _/binary>>, _} =
+ open_file(PrivDir, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING).
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
prep(Config) ->
PrivDir = ?config(priv_dir, Config),
@@ -684,7 +647,7 @@ reply(Cm, Channel, RBuf) ->
{ssh_cm, Cm, {closed, Channel}} ->
closed;
{ssh_cm, Cm, Msg} ->
- test_server:fail(Msg)
+ ct:fail(Msg)
end.
@@ -778,7 +741,7 @@ read_dir(Handle, Cm, Channel, ReqId) ->
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",
+ ct:pal("Count: ~p Listing: ~p~n",
[Count, binary_to_list(Listing)]),
read_dir(Handle, Cm, Channel, ReqId);
{ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
@@ -921,32 +884,5 @@ encode_file_type(Type) ->
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.
-
not_default_permissions() ->
8#600. %% User read-write-only
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
index 4c469ed5f7..8f722941d4 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -24,24 +24,32 @@
-compile(export_all).
-include_lib("common_test/include/ct.hrl").
--include("test_server_line.hrl").
-
-include_lib("kernel/include/file.hrl").
-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.
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [close_file,
+ quit,
+ file_cb,
+ root_dir,
+ list_dir_limited,
+ ver6_basic].
+
+groups() ->
+ [].
+
%%--------------------------------------------------------------------
+
init_per_suite(Config) ->
catch ssh:stop(),
case catch crypto:start() of
@@ -60,12 +68,6 @@ init_per_suite(Config) ->
{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) ->
UserDir = filename:join(?config(priv_dir, Config), nopubkey),
file:del_dir(UserDir),
@@ -75,18 +77,14 @@ end_per_suite(Config) ->
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_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
%%--------------------------------------------------------------------
+
init_per_testcase(TestCase, Config) ->
ssh:start(),
PrivDir = ?config(priv_dir, Config),
@@ -115,7 +113,12 @@ init_per_testcase(TestCase, Config) ->
[{system_dir, SystemDir},
{user_dir, PrivDir},
{subsystems, [Spec]}];
-
+ "ver6_basic" ->
+ Spec =
+ ssh_sftpd:subsystem_spec([{sftpd_vsn, 6}]),
+ [{system_dir, SystemDir},
+ {user_dir, PrivDir},
+ {subsystems, [Spec]}];
_ ->
[{user_dir, PrivDir},
{system_dir, SystemDir}]
@@ -132,53 +135,21 @@ init_per_testcase(TestCase, Config) ->
NewConfig = lists:keydelete(sftpd, 1, TmpConfig),
[{port, Port}, {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.
+ ssh:stop().
%%--------------------------------------------------------------------
-%% 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
+%% Test cases starts here. -------------------------------------------
%%--------------------------------------------------------------------
-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) ->
+close_file(doc) ->
["Test that sftpd closes its fildescriptors after compleating the "
- "transfer"];
-
-close_file_OTP_6350(suite) ->
- [];
+ "transfer OTP-6350"];
-close_file_OTP_6350(Config) when is_list(Config) ->
+close_file(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
FileName = filename:join(DataDir, "test.txt"),
@@ -186,28 +157,20 @@ close_file_OTP_6350(Config) when is_list(Config) ->
NumOfPorts = length(erlang:ports()),
- test_server:format("Number of open ports: ~p~n", [NumOfPorts]),
+ ct:pal("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.
+ NumOfPorts = length(erlang:ports()).
%%--------------------------------------------------------------------
-quit_OTP_6349(doc) ->
+quit(doc) ->
[" When the sftp client ends the session the "
"server will now behave correctly and not leave the "
- "client hanging."];
-
-quit_OTP_6349(suite) ->
- [];
+ "client hanging. OTP-6349"];
-quit_OTP_6349(Config) when is_list(Config) ->
+quit(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
FileName = filename:join(DataDir, "test.txt"),
UserDir = ?config(priv_dir, Config),
@@ -230,19 +193,15 @@ quit_OTP_6349(Config) when is_list(Config) ->
{ok, <<_/binary>>} = ssh_sftp:read_file(NewSftp, FileName),
- ok = ssh_sftp:stop_channel(NewSftp),
- ok.
+ ok = ssh_sftp:stop_channel(NewSftp).
%%--------------------------------------------------------------------
-file_cb_OTP_6356(doc) ->
+file_cb(doc) ->
["Test that it is possible to change the callback module for"
- " the sftpds filehandling."];
-
-file_cb_OTP_6356(suite) ->
- [];
+ " the sftpds filehandling. OTP-6356"];
-file_cb_OTP_6356(Config) when is_list(Config) ->
+file_cb(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
FileName = filename:join(DataDir, "test.txt"),
@@ -279,17 +238,15 @@ file_cb_OTP_6356(Config) when is_list(Config) ->
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.
+ alt_file_handler_check(alt_del_dir).
+%%--------------------------------------------------------------------
root_dir(doc) ->
[""];
-root_dir(suite) ->
- [];
root_dir(Config) when is_list(Config) ->
{Sftp, _} = ?config(sftp, Config),
FileName = "test.txt",
@@ -298,26 +255,36 @@ root_dir(Config) when is_list(Config) ->
{ok, Bin} = ssh_sftp:read_file(Sftp, FileName),
{ok, Listing} =
ssh_sftp:list_dir(Sftp, "."),
- test_server:format("Listing: ~p~n", [Listing]),
- ok.
+ ct:pal("Listing: ~p~n", [Listing]).
+%%--------------------------------------------------------------------
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.
+ ct:pal("Listing: ~p~n", [Listing]).
+ver6_basic(doc) ->
+ ["Test some version 6 features"];
+ver6_basic(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ NewDir = filename:join(PrivDir, "testdir2"),
+ {Sftp, _} = ?config(sftp, Config),
+ ok = ssh_sftp:make_dir(Sftp, NewDir),
+ %%Test file_is_a_directory
+ {error, file_is_a_directory} = ssh_sftp:delete(Sftp, NewDir).
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+
alt_file_handler_check(Msg) ->
receive
Msg ->
ok;
Other ->
- test_server:fail({Msg, Other})
+ ct:fail({Msg, Other})
after 10000 ->
- test_server:fail("Not alt file handler")
+ ct: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
index 9e119c4929..9f8a7c496c 100644
--- 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
@@ -48,7 +48,7 @@ get_cwd(State) ->
{file:get_cwd(), State}.
is_dir(AbsPath, State) ->
- sftpd_file_alt_tester ! alt_is_dir,
+ %sftpd_file_alt_tester ! alt_is_dir,
{filelib:is_dir(AbsPath), State}.
list_dir(AbsPath, State) ->
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 609663c87a..6ed3dfa68c 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -25,8 +25,7 @@
-compile(export_all).
-include_lib("public_key/include/public_key.hrl").
--include("test_server.hrl").
--include("test_server_line.hrl").
+-include_lib("common_test/include/ct.hrl").
-define(TIMEOUT, 50000).
@@ -129,16 +128,16 @@ reply(TestCase, Result) ->
TestCase ! Result.
receive_exec_result(Msg) ->
- test_server:format("Expect data! ~p", [Msg]),
+ ct:pal("Expect data! ~p", [Msg]),
receive
{ssh_cm,_,{data,_,1, Data}} ->
- test_server:format("StdErr: ~p~n", [Data]),
+ ct:pal("StdErr: ~p~n", [Data]),
receive_exec_result(Msg);
Msg ->
- test_server:format("1: Collected data ~p", [Msg]),
+ ct:pal("1: Collected data ~p", [Msg]),
expected;
Other ->
- test_server:format("Other ~p", [Other]),
+ ct:pal("Other ~p", [Other]),
{unexpected_msg, Other}
end.
@@ -150,19 +149,19 @@ receive_exec_end(ConnectionRef, 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]),
+ ct:pal("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])
+ ct:pal("3: Collected data ~p", [Closed])
end;
expected ->
- test_server:format("4: Collected data ~p", [ExitStatus]),
+ ct:pal("4: Collected data ~p", [ExitStatus]),
expected = receive_exec_result(Eof),
expected = receive_exec_result(Closed);
Other ->
- test_server:fail({unexpected_msg, Other})
+ ct:fail({unexpected_msg, Other})
end.
receive_exec_result(Data, ConnectionRef, ChannelId) ->
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index c337617ee4..99dc76e12d 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -21,7 +21,6 @@
-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).
@@ -29,76 +28,10 @@
-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 ->
- case gen_tcp:connect("localhost", 22, []) of
- {error,econnrefused} ->
- {skip,"No openssh deamon"};
- _ ->
- Config
- end;
- _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
+%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-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 ->
@@ -122,6 +55,23 @@ groups() ->
erlang_server_openssh_client_pulic_key_dsa]}
].
+init_per_suite(Config) ->
+ case catch crypto:start() of
+ ok ->
+ case gen_tcp:connect("localhost", 22, []) of
+ {error,econnrefused} ->
+ {skip,"No openssh deamon"};
+ _ ->
+ Config
+ end;
+ _Else ->
+ {skip,"Could not start crypto!"}
+ end.
+
+end_per_suite(_Config) ->
+ crypto:stop(),
+ ok.
+
init_per_group(erlang_server, Config) ->
DataDir = ?config(data_dir, Config),
UserDir = ?config(priv_dir, Config),
@@ -137,14 +87,21 @@ end_per_group(erlang_server, Config) ->
end_per_group(_, Config) ->
Config.
-%% TEST cases starts here.
+init_per_testcase(_TestCase, Config) ->
+ ssh:start(),
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ssh:stop(),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
+
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(),
@@ -159,22 +116,19 @@ erlang_shell_client_openssh_server(Config) when is_list(Config) ->
ok
end;
Other0 ->
- test_server:fail({unexpected_msg, Other0})
+ ct:fail({unexpected_msg, Other0})
end,
receive
{'EXIT', Shell, normal} ->
ok;
Other1 ->
- test_server:fail({unexpected_msg, Other1})
+ ct: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}]),
@@ -187,11 +141,11 @@ erlang_client_openssh_server_exec(Config) when is_list(Config) ->
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]),
+ ct:pal("0: Collected data ~p", [ExitStatus0]),
ssh_test_lib:receive_exec_result(Data0,
ConnectionRef, ChannelId0);
Other0 ->
- test_server:fail(Other0)
+ ct:fail(Other0)
end,
{ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
@@ -203,20 +157,17 @@ erlang_client_openssh_server_exec(Config) when is_list(Config) ->
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]),
+ ct:pal("0: Collected data ~p", [ExitStatus1]),
ssh_test_lib:receive_exec_result(Data1,
ConnectionRef, ChannelId1);
Other1 ->
- test_server:fail(Other1)
+ ct: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},
@@ -230,19 +181,16 @@ erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) ->
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]),
+ ct:pal("0: Collected data ~p", [ExitStatus]),
ssh_test_lib:receive_exec_result(Data, ConnectionRef, ChannelId);
Other ->
- test_server:fail(Other)
+ ct: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),
PrivDir = ?config(priv_dir, Config),
@@ -252,12 +200,12 @@ erlang_server_openssh_client_exec(Config) when is_list(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
- test_server:sleep(500),
+ ct:sleep(500),
Cmd = "ssh -p " ++ integer_to_list(Port) ++
" -o UserKnownHostsFile=" ++ KnownHosts ++ " " ++ Host ++ " 1+1.",
- test_server:format("Cmd: ~p~n", [Cmd]),
+ ct:pal("Cmd: ~p~n", [Cmd]),
SshPort = open_port({spawn, Cmd}, [binary]),
@@ -265,7 +213,7 @@ erlang_server_openssh_client_exec(Config) when is_list(Config) ->
{SshPort,{data, <<"2\n">>}} ->
ok
after ?TIMEOUT ->
- test_server:fail("Did not receive answer")
+ ct:fail("Did not receive answer")
end,
ssh:stop_daemon(Pid).
@@ -274,9 +222,6 @@ erlang_server_openssh_client_exec(Config) when is_list(Config) ->
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),
PrivDir = ?config(priv_dir, Config),
@@ -286,7 +231,7 @@ erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
{compression, zlib},
{failfun, fun ssh_test_lib:failfun/2}]),
- test_server:sleep(500),
+ ct:sleep(500),
Cmd = "ssh -p " ++ integer_to_list(Port) ++
" -o UserKnownHostsFile=" ++ KnownHosts ++ " -C "++ Host ++ " 1+1.",
@@ -296,7 +241,7 @@ erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
{SshPort,{data, <<"2\n">>}} ->
ok
after ?TIMEOUT ->
- test_server:fail("Did not receive answer")
+ ct:fail("Did not receive answer")
end,
ssh:stop_daemon(Pid).
@@ -305,9 +250,6 @@ erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
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},
@@ -332,15 +274,15 @@ erlang_client_openssh_server_setenv(Config) when is_list(Config) ->
{data,0,1, UnxpectedData}}} ->
%% Some os may return things as
%% ENV_TEST: Undefined variable.\n"
- test_server:format("UnxpectedData: ~p", [UnxpectedData]),
+ ct:pal("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]),
+ ct:pal("0: Collected data ~p", [ExitStatus]),
ssh_test_lib:receive_exec_result(Data,
ConnectionRef, ChannelId);
Other ->
- test_server:fail(Other)
+ ct:fail(Other)
end.
%%--------------------------------------------------------------------
@@ -350,8 +292,6 @@ erlang_client_openssh_server_setenv(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
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),
KeyFile = filename:join(Home, ".ssh/id_rsa"),
@@ -379,8 +319,6 @@ erlang_client_openssh_server_publickey_rsa(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
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),
KeyFile = filename:join(Home, ".ssh/id_dsa"),
@@ -406,10 +344,6 @@ erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
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),
PrivDir = ?config(priv_dir, Config),
@@ -419,7 +353,7 @@ erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
{public_key_alg, ssh_dsa},
{failfun, fun ssh_test_lib:failfun/2}]),
- test_server:sleep(500),
+ ct:sleep(500),
Cmd = "ssh -p " ++ integer_to_list(Port) ++
" -o UserKnownHostsFile=" ++ KnownHosts ++
@@ -430,17 +364,13 @@ erlang_server_openssh_client_pulic_key_dsa(Config) when is_list(Config) ->
{SshPort,{data, <<"2\n">>}} ->
ok
after ?TIMEOUT ->
- test_server:fail("Did not receive answer")
+ ct: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),
@@ -451,7 +381,7 @@ erlang_client_openssh_server_password(Config) when is_list(Config) ->
{user_interaction, false},
{user_dir, UserDir}]),
- test_server:format("Test of user foo that does not exist. "
+ ct:pal("Test of user foo that does not exist. "
"Error msg: ~p~n", [Reason0]),
User = string:strip(os:cmd("whoami"), right, $\n),
@@ -465,10 +395,10 @@ erlang_client_openssh_server_password(Config) when is_list(Config) ->
{password, "foo"},
{user_interaction, false},
{user_dir, UserDir}]),
- test_server:format("Test of wrong Pasword. "
+ ct:pal("Test of wrong Pasword. "
"Error msg: ~p~n", [Reason1]);
_ ->
- test_server:format("Whoami failed reason: ~n", [])
+ ct:pal("Whoami failed reason: ~n", [])
end.
%%--------------------------------------------------------------------
@@ -477,13 +407,13 @@ erlang_client_openssh_server_password(Config) when is_list(Config) ->
%%
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
-%%% Internal functions
+%%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
receive_hej() ->
receive
<<"Hej\n">> = Hej->
- test_server:format("Expected result: ~p~n", [Hej]);
+ ct:pal("Expected result: ~p~n", [Hej]);
Info ->
- test_server:format("Extra info: ~p~n", [Info]),
+ ct:pal("Extra info: ~p~n", [Info]),
receive_hej()
end.
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index defa47f824..921ec2206a 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,5 +1,5 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 2.1.1
+SSH_VSN = 2.1.2
APP_VSN = "ssh-$(SSH_VSN)"