aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/src/introduction.xml182
-rw-r--r--lib/ssh/doc/src/notes.xml27
-rw-r--r--lib/ssh/doc/src/ref_man.xml4
-rw-r--r--lib/ssh/doc/src/ssh.xml479
-rw-r--r--lib/ssh/doc/src/ssh_app.xml122
-rw-r--r--lib/ssh/doc/src/ssh_channel.xml296
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml96
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml496
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml77
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml712
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml56
-rw-r--r--lib/ssh/doc/src/usersguide.xml7
-rw-r--r--lib/ssh/doc/src/using_ssh.xml206
-rw-r--r--lib/ssh/src/Makefile6
-rw-r--r--lib/ssh/src/ssh.erl216
-rw-r--r--lib/ssh/src/ssh.hrl7
-rw-r--r--lib/ssh/src/ssh_acceptor.erl6
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl24
-rw-r--r--lib/ssh/src/ssh_auth.erl194
-rw-r--r--lib/ssh/src/ssh_auth.hrl2
-rw-r--r--lib/ssh/src/ssh_bits.erl8
-rw-r--r--lib/ssh/src/ssh_cli.erl51
-rw-r--r--lib/ssh/src/ssh_connection.erl26
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl468
-rw-r--r--lib/ssh/src/ssh_info.erl11
-rw-r--r--lib/ssh/src/ssh_message.erl34
-rw-r--r--lib/ssh/src/ssh_sftp.erl26
-rw-r--r--lib/ssh/src/ssh_system_sup.erl64
-rw-r--r--lib/ssh/src/ssh_transport.erl191
-rw-r--r--lib/ssh/src/sshd_sup.erl27
-rw-r--r--lib/ssh/test/Makefile5
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl685
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl23
-rw-r--r--lib/ssh/test/ssh_relay.erl407
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl192
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/id_dsa13
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_test_lib.erl15
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl23
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE.erl1
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE.erl206
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa13
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts1
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/vsn.mk2
56 files changed, 4220 insertions, 1610 deletions
diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml
index b42910cb34..1efbc16016 100644
--- a/lib/ssh/doc/src/introduction.xml
+++ b/lib/ssh/doc/src/introduction.xml
@@ -25,31 +25,181 @@
<title>Introduction</title>
<prepared>OTP team</prepared>
+ <responsible></responsible>
+ <docno></docno>
+ <approved></approved>
+ <checked></checked>
+ <date></date>
+ <rev></rev>
<file>introduction.xml</file>
</header>
-
+ <p>SSH is a protocol for secure remote logon and
+ other secure network services over an insecure network.</p>
<section>
- <title>Purpose</title>
+ <title>Scope and 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
+ <p>SSH provides a single, full-duplex, and 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>
+ server authentication, and man-in-the-middle protection.</p>
+
+ <p>The <c>ssh</c> application is an implementation of the SSH Transport, Connection and Authentication
+ Layer Protocols in Erlang. It provides the following:</p>
+ <list type="bulleted">
+ <item>API functions to write customized SSH clients and servers applications</item>
+ <item>The Erlang shell available over SSH</item>
+ <item>An SFTP client (<seealso marker="ssh_sftp">ssh_sftp</seealso>)
+ and server (<seealso marker="ssh_sftp">ssh_sftpd</seealso>)</item>
+ </list>
</section>
<section>
<title>Prerequisites</title>
- <p>It is assumed that the reader is familiar with the concepts of <seealso marker="doc/design_principles:des_princ">OTP</seealso>
- and has a basic understanding of <url href="http://en.wikipedia.org/wiki/Public-key_cryptography">public keys</url>.</p>
+ <p>It is assumed that the reader is familiar with the Erlang programming language,
+ concepts of <em>OTP</em>, and has a basic understanding of <em>public keys</em>.</p>
+ </section>
+
+<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. A minimum of
+ Message Authentication Code (MAC) and encryption
+ algorithms are supported. For details, see the
+ <seealso marker="ssh">ssh(3)</seealso> manual page in <c>ssh</c>.</p>
+ </section>
+
+ <section>
+ <title>Authentication Protocol</title>
+
+ <p>The SSH Authentication Protocol is a general-purpose user
+ authentication protocol run over the SSH Transport Layer
+ Protocol. The <c>ssh</c> application supports user authentication as follows:
+ </p>
+ <list type="bulleted">
+ <item>
+ Using public key technology. RSA and DSA, X509-certificates
+ are not supported.
+ </item>
+ <item>
+ Using keyboard-interactive authentication.
+ This is suitable for interactive authentication methods
+ that do not need any special software support on the client side.
+ Instead, all authentication data is entered from the keyboard.
+ </item>
+ <item>
+ Using a pure password-based authentication scheme.
+ Here, the plain text password is encrypted before sent
+ over the network.
+ </item>
+ </list>
+ <p>Several configuration options for
+ authentication handling are available in
+ <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>
+ and <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</p>
+ <p>
+ The public key handling can be customized by implementing
+ the following behaviours from <c>ssh</c>:</p>
+ <list type="bulleted">
+ <item>Module
+ <seealso marker="ssh_client_key_api">ssh_client_key_api</seealso>.
+ </item>
+ <item>Module
+ <seealso marker="ssh_server_key_api">ssh_server_key_api</seealso>.
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Connection Protocol</title>
+
+ <p>The SSH Connection Protocol provides application-support
+ services over the transport pipe, for example, channel multiplexing,
+ flow control, remote program execution, signal propagation, and
+ connection forwarding. Functions for handling the SSH
+ Connection Protocol can be found in the module <seealso
+ marker="ssh_connection">ssh_connection</seealso> in <c>ssh</c>.
+ </p>
+ </section>
+
+ <section>
+ <title>Channels</title>
+
+ <p>All terminal sessions, forwarded connections, and so on, are
+ channels. Multiple channels are multiplexed into a single
+ connection. All channels are flow-controlled. This means that no
+ data is sent to a channel peer until a message is received to
+ indicate that window space is available.
+ The <em>initial window size</em> specifies how many bytes of channel
+ data that can be sent to the channel peer without adjusting the
+ window. Typically, an SSH client opens a channel, sends data (commands),
+ receives data (control information), and then closes the channel.
+ The <seealso marker="ssh_channel">ssh_channel</seealso> behaviour
+ handles generic parts of SSH channel management. This makes it easy
+ to write your own SSH client/server processes that use flow-control
+ and thus opens for more focus on the application logic.
+ </p>
+
+ <p>Channels come in the following 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 into the
+ SSH daemon (server) by default, but it can be disabled. The Erlang <c>ssh</c>
+ daemon can be configured to run any Erlang-
+ implemented SSH subsystem.
+ </item>
+ <item><em>Shell</em> - Interactive shell. By default the
+ Erlang daemon runs the Erlang shell. The shell can be customized by
+ providing your own read-eval-print loop. You can also provide your
+ own Command-Line Interface (CLI) implementation,
+ but that is much more work.
+ </item>
+ <item><em>Exec</em> - One-time remote execution of commands. See function
+ <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>
+ for more information.</item>
+ </list>
+ </section>
+
+
+
</section>
+ <section>
+ <title>Where to Find More Information</title>
+ <p>
+ For detailed information about the SSH protocol, refer to the
+ following Request for Comments(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/notes.xml b/lib/ssh/doc/src/notes.xml
index 579a3ae4a8..c77ee1e77a 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -29,6 +29,33 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 3.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Gracefully terminate if sockets is unexpectedly closed.</p>
+ <p>
+ Own Id: OTP-12782</p>
+ </item>
+ <item>
+ <p>
+ Made Codenomicon Defensics test suite pass: <list>
+ <item>limit number of algorithms in kexinit
+ message</item> <item>check 'e' and 'f' parameters in
+ kexdh</item> <item>implement 'keyboard-interactive' user
+ authentication on server side</item> <item> return plain
+ text message to bad version exchange message</item>
+ </list></p>
+ <p>
+ Own Id: OTP-12784</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 3.2.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml
index 55339298e8..afe3f2ddf9 100644
--- a/lib/ssh/doc/src/ref_man.xml
+++ b/lib/ssh/doc/src/ref_man.xml
@@ -28,8 +28,8 @@
<file>ref_man.xml</file>
</header>
<description>
- <p>The SSH application is an erlang implementation of the
- secure shell protocol (SSH) as defined by RFC 4250 - 4254</p>
+ <p>The <c>ssh</c> application is an Erlang implementation of the
+ Secure Shell Protocol (SSH) as defined by RFC 4250 - 4254.</p>
</description>
<xi:include href="ssh_app.xml"/>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index 501668ca78..c1235715cc 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -22,54 +22,85 @@
</legalnotice>
<title>ssh</title>
+ <prepared></prepared>
+ <docno></docno>
<date>2007-10-06</date>
+ <rev></rev>
</header>
<module>ssh</module>
- <modulesummary>Main API of the SSH application</modulesummary>
+ <modulesummary>Main API of the ssh application</modulesummary>
<description>
- <p>Interface module for the SSH application. </p>
+ <p>Interface module for the <c>ssh</c> application.</p>
</description>
<section>
<title>SSH</title>
-
+ <marker id="supported"/>
<list type="bulleted">
- <item>SSH requires the crypto and public_key applications.</item>
- <item>Supported SSH version is 2.0 </item>
- <item>Supported MAC algorithms: hmac-sha2-256 and hmac-sha1</item>
- <item>Supported encryption algorithms: aes128-ctr, aes128-cb and 3des-cbc</item>
- <item>Supports unicode filenames if the emulator and the underlaying OS supports it. See the DESCRIPTION section in <seealso marker="kernel:file">file</seealso> for information about this subject</item>
- <item>Supports unicode in shell and cli</item>
+ <item>For application dependencies see <seealso marker="SSH_app"> ssh(6)</seealso> </item>
+ <item>Supported SSH version is 2.0.</item>
+ <item>Supported public key algorithms: ssh-rsa and ssh-dss.</item>
+ <item>Supported MAC algorithms: hmac-sha2-256 and hmac-sha1.</item>
+ <item>Supported encryption algorithms: aes128-ctr, aes128-cb and 3des-cbc.</item>
+ <item>Supported key exchange algorithms: diffie-hellman-group1-sha1.</item>
+ <item>Supported compression algorithms: none, zlib, [email protected],</item>
+ <item>Supports unicode filenames if the emulator and the underlaying OS support it.
+ See section DESCRIPTION in the
+ <seealso marker="kernel:file">file</seealso> manual page in <c>kernel</c>
+ for information about this subject.</item>
+ <item>Supports unicode in shell and CLI.</item>
</list>
</section>
<section>
- <title>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>
- <p><c>boolean() = true | false </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() - inet::ip_address()</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>
+ this module, or abstractions to indicate the intended use of the data
+ type, or both:</p>
+ <taglist>
+ <tag><c>boolean() =</c></tag>
+ <item><p><c>true | false</c></p></item>
+ <tag><c>string() =</c></tag>
+ <item><p><c>[byte()]</c></p></item>
+ <tag><c>ssh_daemon_ref() =</c></tag>
+ <item><p>opaque() -
+ as returned by <c>ssh:daemon/[1,2,3]</c></p></item>
+ <tag><c>ssh_connection_ref() =</c></tag>
+ <item><p>opaque() - as returned by <c>ssh:connect/3</c></p></item>
+ <tag><c>ip_address() =</c></tag>
+ <item><p><c>inet::ip_address</c></p></item>
+ <tag><c>subsystem_spec() =</c></tag>
+ <item><p><c>{subsystem_name(),
+ {channel_callback(), channel_init_args()}}</c></p></item>
+ <tag><c>subsystem_name() =</c></tag>
+ <item><p><c>string()</c></p></item>
+ <tag><c>channel_callback() =</c></tag>
+ <item><p><c>atom()</c> - Name of the Erlang module
+ implementing the subsystem using the <c>ssh_channel</c> behavior, see
+ <seealso marker="ssh_channel">ssh_channel(3)</seealso></p></item>
+ <tag><c>channel_init_args() =</c></tag>
+ <item><p><c>list()</c></p></item>
+
+ <tag><c>algs_list() =</c></tag>
+ <item><p><c>list( alg_entry() )</c></p></item>
+
+ <tag><c>alg_entry() =</c></tag>
+ <item><p><c>{kex, simple_algs()} | {public_key, simple_algs()} | {cipher, double_algs()} | {mac, double_algs()} | {compression, double_algs()}</c></p></item>
+
+ <tag><c>simple_algs() =</c></tag>
+ <item><p><c>list( atom() )</c></p></item>
+
+ <tag><c>double_algs() =</c></tag>
+ <item><p><c>[{client2serverlist,simple_algs()},{server2client,simple_algs()}] | simple_algs()</c></p></item>
+ </taglist>
+</section>
<funcs>
<func>
<name>close(ConnectionRef) -> ok </name>
- <fsummary>Closes an SSH connection</fsummary>
+ <fsummary>Closes an SSH connection.</fsummary>
<type>
<v>ConnectionRef = ssh_connection_ref()</v>
</type>
@@ -81,104 +112,163 @@
<name>connect(Host, Port, Options) -> </name>
<name>connect(Host, Port, Options, Timeout) -> {ok,
ssh_connection_ref()} | {error, Reason}</name>
- <fsummary>Connect to an ssh server.</fsummary>
+ <fsummary>Connects to an SSH server.</fsummary>
<type>
<v>Host = string()</v>
<v>Port = integer()</v>
- <d>The default is <c><![CDATA[22]]></c>, the assigned well known port
+ <d><c><![CDATA[22]]></c> is default, the assigned well-known port
number for SSH.</d>
<v>Options = [{Option, Value}]</v>
- <v>Timeout = infinity | integer(milliseconds)</v>
- <d>Negotiation timeout, for connection timeout use the option <c>{connect_timeout, timeout()}</c>.</d>
+ <v>Timeout = infinity | integer()</v>
+ <d>Negotiation time-out in milli-seconds. The default value is <c>infinity</c>.
+ For connection time-out, use option <c>{connect_timeout, timeout()}</c>.</d>
</type>
<desc>
<p>Connects to an SSH server. No channel is started. This is done
by calling
- <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/[2, 4]</seealso>.</p>
- <p>Options are:</p>
+ <seealso marker="ssh_connection#session_channel/2">
+ ssh_connection:session_channel/[2, 4]</seealso>.</p>
+ <p>Options:</p>
<taglist>
<tag><c><![CDATA[{inet, inet | inet6}]]></c></tag>
- <item> IP version to use.</item>
+ <item>
+ <p>IP version to use.</p>
+ </item>
<tag><c><![CDATA[{user_dir, string()}]]></c></tag>
<item>
- <p>Sets the user directory i.e. the directory containing
- ssh configuration files for the user such as
+ <p>Sets the user directory, that is, the directory containing
+ <c>ssh</c> configuration files for the user, such as
<c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa,
- id_dsa]]></c> and
+ id_dsa]]></c>, and
<c><![CDATA[authorized_key]]></c>. Defaults to the
directory normally referred to as
- <c><![CDATA[~/.ssh]]></c> </p>
+ <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 passphrase 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 passphrase it can be
+ <p>If the user RSA key is protected by a passphrase, it can be
supplied with this option.
</p>
</item>
<tag><c><![CDATA[{silently_accept_hosts, boolean()}]]></c></tag>
<item>
- <p>When true hosts are added to the
+ <p>When <c>true</c>, hosts are added to the
file <c><![CDATA[known_hosts]]></c> without asking the user.
- Defaults to false.
+ Defaults to <c>false</c>.
</p>
</item>
<tag><c><![CDATA[{user_interaction, boolean()}]]></c></tag>
<item>
- <p>If false disables the client to connect to the server
- if any user interaction is needed such as accepting that
- the server will be added to the <c>known_hosts</c> file or
- supplying a password. Defaults to true.
+ <p>If <c>false</c>, disables the client to connect to the server
+ if any user interaction is needed, such as accepting
+ the server to be added to the <c>known_hosts</c> file, or
+ supplying a password. Defaults to <c>true</c>.
Even if user interaction is allowed it can be
- suppressed by other options such as silently_accept_hosts and
- password. Do note that it may not always be desirable to use
- those options from a security point of view.</p>
+ suppressed by other options, such as <c>silently_accept_hosts</c>
+ and <c>password</c>. However, those optins are not always desirable
+ to use from a security point of view.</p>
</item>
+
+ <tag><c><![CDATA[{disconnectfun, fun(Reason:term()) -> _}]]></c></tag>
+ <item>
+ <p>Provides a fun to implement your own logging when a server disconnects the client.</p>
+ </item>
+
+ <tag><c><![CDATA[{unexpectedfun, fun(Message:term(), Peer) -> report | skip }]]></c></tag>
+ <item>
+ <p>Provides a fun to implement your own logging or other action when an unexpected message arrives.
+ If the fun returns <c>report</c> the usual info report is issued but if <c>skip</c> is returned no
+ report is generated.</p>
+ <p><c>Peer</c> is in the format of <c>{Host,Port}</c>.</p>
+ </item>
+
<tag><c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></tag>
<item>
+ <note>
+ <p>This option is kept for compatibility. It is ignored if the <c>preferred_algorithms</c>
+ option is used. The equivalence of <c>{public_key_alg,'ssh-dss'}</c> is
+ <c>{preferred_algorithms, [{public_key,['ssh-dss','ssh-rsa']}]}</c>.</p>
+ </note>
<p>Sets the preferred public key algorithm to use for user
- authentication. If the the preferred algorithm fails for
- some reason, the other algorithm is tried. The default is
+ authentication. If the preferred algorithm fails,
+ 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>
+ <note>
+ <p>This option is kept for compatibility. It is ignored if the <c>preferred_algorithms</c>
+ option is used. The equivalence of <c>{pref_public_key_algs,['ssh-dss']}</c> is
+ <c>{preferred_algorithms, [{public_key,['ssh-dss']}]}</c>.</p>
+ </note>
+ <p>List of public key algorithms to try to use.
+ <c>'ssh-rsa'</c> and <c>'ssh-dss'</c> are available.
+ Overrides <c><![CDATA[{public_key_alg, 'ssh-rsa' | 'ssh-dss'}]]></c></p>
</item>
+
+ <tag><c><![CDATA[{preferred_algorithms, algs_list()}]]></c></tag>
+ <item>
+ <p>List of algorithms to use in the algorithm negotiation. The default <c>algs_list()</c> can
+ be obtained from <seealso marker="#default_algorithms/0">default_algorithms/0</seealso>.
+ </p>
+ <p>Here is an example of this option:</p>
+ <code>
+{preferred_algorithms,
+ [{public_key,['ssh-rsa','ssh-dss']},
+ {cipher,[{client2server,['aes128-ctr']},
+ {server2client,['aes128-cbc','3des-cbc']}]},
+ {mac,['hmac-sha2-256','hmac-sha1']},
+ {compression,[none,zlib]}
+}
+</code>
+ <p>The example specifies different algorithms in the two directions (client2server and server2client), for cipher but specifies the same
+algorithms for mac and compression in both directions. The kex (key exchange) and public key algorithms are set to their default values,
+kex is implicit but public_key is set explicitly.</p>
+
+ <warning>
+ <p>Changing the values can make a connection less secure. Do not change unless you
+ know exactly what you are doing. If you do not understand the values then you
+ are not supposed to change them.</p>
+ </warning>
+ </item>
+
<tag><c><![CDATA[{connect_timeout, timeout()}]]></c></tag>
<item>
- <p>Sets a timeout on the transport layer
- connection. Defaults to <c>infinity</c>.</p>
+ <p>Sets a time-out on the transport layer
+ connection. For <c>gen_tcp</c> the time is in milli-seconds and the default value is
+ <c>infinity</c>.</p>
</item>
<tag><c><![CDATA[{user, string()}]]></c></tag>
<item>
- <p>Provides a user name. If this option is not given, ssh
+ <p>Provides a username. If this option is not given, <c>ssh</c>
reads from the environment (<c><![CDATA[LOGNAME]]></c> or
- <c><![CDATA[USER]]></c> on unix,
+ <c><![CDATA[USER]]></c> on UNIX,
<c><![CDATA[USERNAME]]></c> on Windows).</p>
</item>
<tag><c><![CDATA[{password, string()}]]></c></tag>
<item>
- <p>Provide a password for password authentication. If
- this option is not given, the user will be asked for a
- password if the password authentication method is
+ <p>Provides a password for password authentication.
+ If this option is not given, the user is asked for a
+ password, if the password authentication method is
attempted.</p>
</item>
<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>.
+ <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[{quiet_mode, atom() = boolean()}]]></c></tag>
<item>
- <p>If true, the client will not print out anything on authorization.</p>
+ <p>If <c>true</c>, the client does not print anything on authorization.</p>
</item>
<tag><c><![CDATA[{id_string, random | string()}]]></c></tag>
@@ -191,17 +281,17 @@
<tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag>
<item>
- <p>Allow an existing file descriptor to be used
- (simply passed on to the transport protocol).</p></item>
+ <p>Allows an existing file descriptor to be used
+ (by passing it on to the transport protocol).</p></item>
<tag><c><![CDATA[{rekey_limit, integer()}]]></c></tag>
<item>
- <p>Provide, in bytes, when rekeying should be initiated,
- defaults to one time each GB and one time per hour.</p>
+ <p>Provides, in bytes, when rekeying is to be initiated.
+ Defaults to once per each GB and once per hour.</p>
</item>
<tag><c><![CDATA[{idle_time, integer()}]]></c></tag>
<item>
- <p>Sets a timeout on connection when no channels are active, default is infinity</p></item>
-
+ <p>Sets a time-out on a connection when no channels are active.
+ Defaults to <c>infinity</c>.</p></item>
<tag><c><![CDATA[{ssh_msg_debug_fun, fun(ConnectionRef::ssh_connection_ref(), AlwaysDisplay::boolean(), Msg::binary(), LanguageTag::binary()) -> _}]]></c></tag>
<item>
<p>Provide a fun to implement your own logging of the SSH message SSH_MSG_DEBUG. The last three parameters are from the message, see RFC4253, section 11.3. The <c>ConnectionRef</c> is the reference to the connection on which the message arrived. The return value from the fun is not checked.</p>
@@ -215,18 +305,17 @@
<func>
<name>connection_info(ConnectionRef, [Option]) ->[{Option,
- Value}] </name>
- <fsummary> Retrieves information about a connection. </fsummary>
+ Value}]</name>
+ <fsummary>Retrieves information about a connection.</fsummary>
<type>
<v>Option = client_version | server_version | user | peer | sockname </v>
<v>Value = [option_value()] </v>
- <v>option_value() = {{Major::integer(), Minor::integer()}, VersionString::string()} | User::string() |
- Peer::{inet:hostname(), {inet::ip_adress(), inet::port_number()}} |
- Sockname::{inet::ip_adress(), inet::port_number()} () </v>
+ <v>option_value() = {{Major::integer(), Minor::integer()}, VersionString::string()} |
+ User::string() | Peer::{inet:hostname(), {inet::ip_adress(), inet::port_number()}} |
+ Sockname::{inet::ip_adress(), inet::port_number()}</v>
</type>
<desc>
- <p> Retrieves information about a connection.
- </p>
+ <p>Retrieves information about a connection.</p>
</desc>
</func>
@@ -247,111 +336,168 @@
<desc>
<p>Starts a server listening for SSH connections on the given
port.</p>
- <p>Options are:</p>
+ <p>Options:</p>
<taglist>
<tag><c><![CDATA[{inet, inet | inet6}]]></c></tag>
- <item> IP version to use when the host address is specified as <c>any</c>. </item>
+ <item><p>IP version to use when the host address is specified as <c>any</c>.</p></item>
<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 is
- 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.
+ <p>Provides specifications for handling of subsystems. The
+ "sftp" subsystem specification is retrieved by calling
+ <c>ssh_sftpd:subsystem_spec/1</c>. If the subsystems option is
+ not present, the value of
+ <c>[ssh_sftpd:subsystem_spec([])]</c> is used.
+ The option can be set to the empty list if
+ you do not want the daemon to run any subsystems.</p>
</item>
<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. Default is to use the erlang shell:
- <c><![CDATA[{shell, start, []}]]></c>
+ <p>Defines the read-eval-print loop used when a shell is
+ requested by the client. The default is to use the Erlang shell:
+ <c><![CDATA[{shell, start, []}]]></c></p>
</item>
<tag><c><![CDATA[{ssh_cli, {channel_callback(),
channel_init_args()} | no_cli}]]></c></tag>
<item>
- 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
- your own CLI channel. If set to <c>no_cli</c> you will disable
- CLI channels and only subsystem channels will be allowed.
+ <p>Provides your own CLI implementation, that is, a channel callback
+ module that implements a shell and command execution. The shell
+ read-eval-print loop can be customized, using the
+ option <c>shell</c>. This means less work than implementing
+ an own CLI channel. If set to <c>no_cli</c>, the CLI channels
+ are disabled and only subsystem channels are allowed.</p>
</item>
- <tag><c><![CDATA[{user_dir, String}]]></c></tag>
+ <tag><c><![CDATA[{user_dir, string()}]]></c></tag>
<item>
- <p>Sets the user directory i.e. the directory containing
- ssh configuration files for the user such as
+ <p>Sets the user directory. That is, the directory containing
+ <c>ssh</c> configuration files for the user, such as
<c><![CDATA[known_hosts]]></c>, <c><![CDATA[id_rsa,
- id_dsa]]></c> and
+ id_dsa]]></c>, and
<c><![CDATA[authorized_key]]></c>. Defaults to the
directory normally referred to as
- <c><![CDATA[~/.ssh]]></c> </p>
+ <c><![CDATA[~/.ssh]]></c>.</p>
</item>
<tag><c><![CDATA[{system_dir, string()}]]></c></tag>
<item>
<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>
+ that identify the host keys for <c>ssh</c>. Defaults to
+ <c><![CDATA[/etc/ssh]]></c>. For security reasons,
+ this directory is normally accessible only to 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
+ <p>Comma-separated string that determines which
+ authentication methods that the server is to support and
+ in what order they are tried. Defaults to
<c><![CDATA["publickey,keyboard-interactive,password"]]></c></p>
</item>
+
+ <tag><c><![CDATA[{auth_method_kb_interactive_data, PromptTexts}]]>
+ <br/>where:
+ <br/>PromptTexts = kb_int_tuple() | fun(Peer::{IP::tuple(),Port::integer()}, User::string(), Service::string()) -> kb_int_tuple()
+ <br/>kb_int_tuple() = {Name::string(), Instruction::string(), Prompt::string(), Echo::boolean()}</c>
+ </tag>
+ <item>
+ <p>Sets the text strings that the daemon sends to the client for presentation to the user when using <c>keyboar-interactive</c> authentication. If the fun/3 is used, it is called when the actual authentication occurs and may therefore return dynamic data like time, remote ip etc.</p>
+ <p>The parameter <c>Echo</c> guides the client about need to hide the password.</p>
+ <p>The default value is:
+ <c>{auth_method_kb_interactive_data, {"SSH server", "Enter password for \""++User++"\"", "password: ", false}></c></p>
+ </item>
+
<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
- public key user authentication fails. The option provides
- a list of valid user names and the corresponding password.
+ <p>Provides passwords for password authentication. The passwords
+ are used when someone tries to connect to the server and
+ public key user-authentication fails. The option provides
+ a list of valid usernames and the corresponding passwords.
</p>
</item>
<tag><c><![CDATA[{password, string()}]]></c></tag>
<item>
- <p>Provide a global password that will authenticate any
+ <p>Provides a global password that authenticates any
user. From a security perspective this option makes
the server very vulnerable.</p>
</item>
+
+ <tag><c><![CDATA[{preferred_algorithms, algs_list()}]]></c></tag>
+ <item>
+ <p>List of algorithms to use in the algorithm negotiation. The default <c>algs_list()</c> can
+ be obtained from <seealso marker="#default_algorithms/0">default_algorithms/0</seealso>.
+ </p>
+ <p>Here is an example of this option:</p>
+ <code>
+{preferred_algorithms,
+ [{public_key,['ssh-rsa','ssh-dss']},
+ {cipher,[{client2server,['aes128-ctr']},
+ {server2client,['aes128-cbc','3des-cbc']}]},
+ {mac,['hmac-sha2-256','hmac-sha1']},
+ {compression,[none,zlib]}
+}
+</code>
+ <p>The example specifies different algorithms in the two directions (client2server and server2client), for cipher but specifies the same
+algorithms for mac and compression in both directions. The kex (key exchange) and public key algorithms are set to their default values,
+kex is implicit but public_key is set explicitly.</p>
+
+ <warning>
+ <p>Changing the values can make a connection less secure. Do not change unless you
+ know exactly what you are doing. If you do not understand the values then you
+ are not supposed to change them.</p>
+ </warning>
+ </item>
+
<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
+ <p>Provides a function for password validation. This function is called
+ with user and password as strings, and returns
<c><![CDATA[true]]></c> if the password is valid and
<c><![CDATA[false]]></c> otherwise.</p>
</item>
<tag><c><![CDATA[{negotiation_timeout, integer()}]]></c></tag>
<item>
- <p>Max time in milliseconds for the authentication negotiation. The default value is 2 minutes. If the client fails to login within this time, the connection is closed.
+ <p>Maximum time in milliseconds for the authentication negotiation.
+ Defaults to 120000 (2 minutes). If the client fails to log in within this time,
+ the connection is closed.
</p>
</item>
<tag><c><![CDATA[{max_sessions, pos_integer()}]]></c></tag>
<item>
- <p>The maximum number of simultaneous sessions that are accepted at any time for this daemon. This includes sessions that are being authorized. So if set to <c>N</c>, and <c>N</c> clients have connected but not started the login process, the <c>N+1</c> connection attempt will be aborted. If <c>N</c> connections are authenticated and still logged in, no more loggins will be accepted until one of the existing ones log out.
+ <p>The maximum number of simultaneous sessions that are accepted at any time
+ for this daemon. This includes sessions that are being authorized.
+ Thus, if set to <c>N</c>, and <c>N</c> clients have connected but not started
+ the login process, connection attempt <c>N+1</c> is aborted.
+ If <c>N</c> connections are authenticated and still logged in, no more logins
+ are accepted until one of the existing ones log out.
</p>
- <p>The counter is per listening port, so if two daemons are started, one with <c>{max_sessions,N}</c> and the other with <c>{max_sessions,M}</c> there will be in total <c>N+M</c> connections accepted for the whole ssh application.
+ <p>The counter is per listening port. Thus, if two daemons are started, one with
+ <c>{max_sessions,N}</c> and the other with <c>{max_sessions,M}</c>, in total
+ <c>N+M</c> connections are accepted for the whole <c>ssh</c> application.
</p>
- <p>Note that if <c>parallel_login</c> is <c>false</c>, only one client at a time may be in the authentication phase.
+ <p>Notice that if <c>parallel_login</c> is <c>false</c>, only one client
+ at a time can be in the authentication phase.
</p>
- <p>As default, the option is not set. This means that the number is not limited.
+ <p>By default, this option is not set. This means that the number is not limited.
</p>
</item>
<tag><c><![CDATA[{parallel_login, boolean()}]]></c></tag>
<item>
- <p>If set to false (the default value), only one login is handled a time. If set to true, an unlimited number of login attempts will be allowed simultanously.
+ <p>If set to false (the default value), only one login is handled at a time.
+ If set to true, an unlimited number of login attempts are allowed simultaneously.
</p>
- <p>If the <c>max_sessions</c> option is set to <c>N</c> and <c>parallel_login</c> is set to <c>true</c>, the max number of simultaneous login attempts at any time is limited to <c>N-K</c> where <c>K</c> is the number of authenticated connections present at this daemon.
+ <p>If the <c>max_sessions</c> option is set to <c>N</c> and <c>parallel_login</c>
+ is set to <c>true</c>, the maximum number of simultaneous login attempts at any time is
+ limited to <c>N-K</c>, where <c>K</c> is the number of authenticated connections present
+ at this daemon.
</p>
<warning>
- <p>Do not enable <c>parallel_logins</c> without protecting the server by other means, for example the <c>max_sessions</c> option or a firewall configuration. If set to <c>true</c>, there is no protection against DOS attacks.</p>
+ <p>Do not enable <c>parallel_logins</c> without protecting the server by other means,
+ for example, by the <c>max_sessions</c> option or a firewall configuration. If set to
+ <c>true</c>, there is no protection against DOS attacks.</p>
</warning>
</item>
@@ -371,25 +517,49 @@
<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>.
+ <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>{profile, atom()}</c></tag>
+ <item>
+ <p>Used together with <c>ip-address</c> and <c>port</c> to
+ uniquely identify a ssh daemon. This can be useful in a
+ virtualized environment, where there can be more that one
+ server that has the same <c>ip-address</c> and
+ <c>port</c>. If this property is not explicitly set, it is
+ assumed that the the <c>ip-address</c> and <c>port</c>
+ uniquely identifies the SSH daemon.
+ </p>
+ </item>
+
<tag><c><![CDATA[{fd, file_descriptor()}]]></c></tag>
<item>
- <p>Allow an existing file-descriptor to be used
- (simply passed on to the transport protocol).</p></item>
- <tag><c><![CDATA[{failfun, fun(User::string(), PeerAddress::ip_address(), Reason::term()) -> _}]]></c></tag>
+ <p>Allows an existing file-descriptor to be used
+ (passed on to the transport protocol).</p></item>
+ <tag><c><![CDATA[{failfun, fun(User::string(),
+ PeerAddress::ip_address(), Reason::term()) -> _}]]></c></tag>
<item>
- <p>Provide a fun to implement your own logging when a user fails to authenticate.</p>
+ <p>Provides a fun to implement your own logging when a user fails to authenticate.</p>
</item>
- <tag><c><![CDATA[{connectfun, fun(User::string(), PeerAddress::ip_address(), Method::string()) ->_}]]></c></tag>
+ <tag><c><![CDATA[{connectfun, fun(User::string(), PeerAddress::ip_address(),
+ Method::string()) ->_}]]></c></tag>
<item>
- <p>Provide a fun to implement your own logging when a user authenticates to the server.</p>
+ <p>Provides a fun to implement your own logging when a user authenticates to the server.</p>
</item>
<tag><c><![CDATA[{disconnectfun, fun(Reason:term()) -> _}]]></c></tag>
<item>
- <p>Provide a fun to implement your own logging when a user disconnects from the server.</p>
+ <p>Provides a fun to implement your own logging when a user disconnects from the server.</p>
+ </item>
+
+ <tag><c><![CDATA[{unexpectedfun, fun(Message:term(), Peer) -> report | skip }]]></c></tag>
+ <item>
+ <p>Provides a fun to implement your own logging or other action when an unexpected message arrives.
+ If the fun returns <c>report</c> the usual info report is issued but if <c>skip</c> is returned no
+ report is generated.</p>
+ <p><c>Peer</c> is in the format of <c>{Host,Port}</c>.</p>
</item>
<tag><c><![CDATA[{ssh_msg_debug_fun, fun(ConnectionRef::ssh_connection_ref(), AlwaysDisplay::boolean(), Msg::binary(), LanguageTag::binary()) -> _}]]></c></tag>
@@ -403,21 +573,41 @@
</desc>
</func>
+ <func>
+ <name>default_algorithms() -> algs_list()</name>
+ <fsummary>Get a list declaring the supported algorithms</fsummary>
+ <desc>
+ <p>Returns a key-value list, where the keys are the different types of algorithms and the values are the
+ algorithms themselves. An example:</p>
+ <code>
+20> ssh:default_algorithms().
+[{kex,['diffie-hellman-group1-sha1']},
+ {public_key,['ssh-rsa','ssh-dss']},
+ {cipher,[{client2server,['aes128-ctr','aes128-cbc','3des-cbc']},
+ {server2client,['aes128-ctr','aes128-cbc','3des-cbc']}]},
+ {mac,[{client2server,['hmac-sha2-256','hmac-sha1']},
+ {server2client,['hmac-sha2-256','hmac-sha1']}]},
+ {compression,[{client2server,[none,zlib]},
+ {server2client,[none,zlib]}]}]
+21>
+</code>
+ </desc>
+ </func>
<func>
<name>shell(Host) -> </name>
<name>shell(Host, Option) -> </name>
<name>shell(Host, Port, Option) -> _</name>
- <fsummary> </fsummary>
+ <fsummary>Starts an interactive shell over an SSH server.</fsummary>
<type>
- <v> Host = string()</v>
- <v> Port = integer()</v>
- <v> Options - see ssh:connect/3</v>
+ <v>Host = string()</v>
+ <v>Port = integer()</v>
+ <v>Options - see ssh:connect/3</v>
</type>
<desc>
- <p>Starts an interactive shell via an SSH server on the
+ <p>Starts an interactive shell over 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 (i.e.
+ and does not return until the remote shell is ended (that is,
exit from the shell).
</p>
</desc>
@@ -426,28 +616,29 @@
<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>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>
+ <p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>,
+ and <c>ssh</c>. Default type is <c>temporary</c>.
+ For more information, see the <seealso marker="kernel:application">application(3)</seealso>
+ manual page in <c>kernel</c>.</p>
</desc>
</func>
<func>
<name>stop() -> ok | {error, Reason}</name>
- <fsummary>Stops the SSH application.</fsummary>
+ <fsummary>Stops the <c>ssh</c> application.</fsummary>
<type>
<v>Reason = term()</v>
</type>
<desc>
- <p>Stops the SSH application. See also
- <seealso marker="kernel:application">application(3)</seealso></p>
+ <p>Stops the <c>ssh</c> application.
+ For more information, see the <seealso marker="kernel:application">application(3)</seealso>
+ manual page in <c>kernel</c>.</p>
</desc>
</func>
@@ -471,7 +662,7 @@
<name>stop_listener(DaemonRef) -> </name>
<name>stop_listener(Address, Port) -> ok </name>
<fsummary>Stops the listener, but leaves existing connections started
- by the listener up and running.</fsummary>
+ by the listener operational.</fsummary>
<type>
<v>DaemonRef = ssh_daemon_ref()</v>
<v>Address = ip_address()</v>
@@ -479,7 +670,7 @@
</type>
<desc>
<p>Stops the listener, but leaves existing connections started
- by the listener up and running.</p>
+ by the listener operational.</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml
index a1d2402790..1dfe68b17d 100644
--- a/lib/ssh/doc/src/ssh_app.xml
+++ b/lib/ssh/doc/src/ssh_app.xml
@@ -18,83 +18,103 @@
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>
+ <prepared></prepared>
+ <docno></docno>
+ <checked></checked>
+ <date></date>
+ <rev></rev>
<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>
+ <appsummary>The ssh application implements the Secure Shell (SSH) protocol and
+ provides an SSH File Transfer Protocol (SFTP) client and server.</appsummary>
+ <description>
+ <p>The <c>ssh</c> application is an implementation of the SSH protocol in Erlang.
+ <c>ssh</c> offers API functions to write customized SSH clients and servers as well as
+ making the Erlang shell available over SSH. An SFTP client, <c>ssh_sftp</c>, and server,
+ <c>ssh_sftpd</c>, are also included.</p>
+ </description>
- <section>
+ <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>The <c>ssh</c> application uses the applications <c>public_key</c> and
+ <c>crypto</c> to handle public keys and encryption. Hence, these
+ applications must be loaded for the <c>ssh</c> application to work. In
+ an embedded environment this means that they must be started with
+ <c>application:start/[1,2]</c> before the <c>ssh</c> 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>
+ <p>The <c>ssh</c> application does not have an application-
+ specific configuration file, as described in <seealso marker="kernel:application">application(3)</seealso>.
+ However, by default it use the following configuration files
+ from OpenSSH:</p>
+ <list type="bulleted">
+ <item><c>known_hosts</c></item>
+ <item><c>authorized_keys</c></item>
+ <item><c>authorized_keys2</c></item>
+ <item><c>id_dsa</c></item>
+ <item><c>id_rsa</c></item>
+ <item><c>ssh_host_dsa_key</c></item>
+ <item><c>ssh_host_rsa_key</c></item>
+ </list>
+ <p>By default, <c>ssh</c> looks for <c>id_dsa</c>, <c>id_rsa</c>,
+ <c>known_hosts</c>, and <c>authorized_keys</c> in ~/.ssh,
+ and for the host key files in <c>/etc/ssh</c>. These locations can be changed
+ by the options <c>user_dir</c> and <c>system_dir</c>.
+ </p>
+ <p>Public key handling can also be customized through a callback module that
+ implements 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
+ </section>
+ <section>
+ <title>Public Keys</title>
+ <p><c>id_dsa</c> and <c>id_rsa</c> are the users private key files.
+ Notice that the public key is part of the private key so the <c>ssh</c>
+ application does not use the <c>id_&lt;*>.pub</c> files. These are
+ for the user's convenience when it is needed to convey the user's
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
+ </section>
+ <section>
+ <title>Known Hosts</title>
+ <p>The <c>known_hosts</c> 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
+ </section>
+ <section>
+ <title>Authorized Keys</title>
+ <p>The <c>authorized_key</c> 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.
+ log in without entering their password, which is supported by the
+ Erlang <c>ssh</c> 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.
+ </section>
+ <section>
+ <title>Host Keys</title>
+ <p>RSA and DSA host keys are supported and are
+ expected to be found in files named <c>ssh_host_rsa_key</c> and
+ <c>ssh_host_dsa_key</c>.
</p>
- </section>
+ </section>
+ <section>
+ <title>ERROR LOGGER AND EVENT HANDLERS</title>
+ <p>The <c>ssh</c> application uses the default <seealso marker="kernel:error_logger">OTP error logger</seealso> to log unexpected errors or print information about special events.</p>
</section>
<section>
<title>SEE ALSO</title>
- <p>application(3)</p>
+ <p><seealso marker="kernel:application">application(3)</seealso></p>
</section>
</appref>
diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml
index a52a6a115e..2fdecf9072 100644
--- a/lib/ssh/doc/src/ssh_channel.xml
+++ b/lib/ssh/doc/src/ssh_channel.xml
@@ -23,69 +23,84 @@
The Initial Developer of the Original Code is Ericsson AB.
</legalnotice>
<title>ssh_channel</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
</header>
<module>ssh_channel</module>
<modulesummary>-behaviour(ssh_channel).
</modulesummary>
<description>
<p>SSH services (clients and servers) are implemented as channels
- that are multiplexed over an SSH connection and communicates via
+ that are multiplexed over an SSH connection and communicates over
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
+ that takes care of generic channel aspects, such as flow control
+ and close messages. It 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.
+ the <c>ssh</c> applications supervisor tree.
</p>
- <note> <p>When implementing a SSH subsystem use the
- <c>-behaviour(ssh_daemon_channel).</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_daemon_channel
- behaviour is limited version of the ssh_channel behaviour.
- </p> </note>
+ <note><p>When implementing an <c>ssh</c> subsystem, use
+ <c>-behaviour(ssh_daemon_channel)</c> instead of <c>-behaviour(ssh_channel)</c>.
+ The reason is that the only relevant callback functions for subsystems are
+ <c>init/1</c>, <c>handle_ssh_msg/2</c>, <c>handle_msg/2</c>, and <c>terminate/2</c>.
+ So, the <c>ssh_daemon_channel</c> behaviour is a limited version of the
+ <c>ssh_channel</c> behaviour.
+ </p></note>
</description>
<section>
- <title>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>
+ <p>Type definitions that are used more than once in this module,
+ or abstractions to indicate the intended use of the data
+ type, or both:</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 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 <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254 </url> section 5.2.</c></p>
+ <taglist>
+ <tag><c>boolean() =</c></tag>
+ <item><p><c>true | false</c></p></item>
+ <tag><c>string() =</c></tag>
+ <item><p>list of ASCII characters</p></item>
+ <tag><c>timeout() =</c></tag>
+ <item><p><c>infinity | integer()</c> in milliseconds</p></item>
+ <tag><c>ssh_connection_ref() =</c></tag>
+ <item><p>opaque() -as returned by
+ <c>ssh:connect/3</c> or sent to an SSH channel process</p></item>
+ <tag><c>ssh_channel_id() =</c></tag>
+ <item><p><c>integer()</c></p></item>
+ <tag><c>ssh_data_type_code() =</c></tag>
+ <item><p><c>1</c> ("stderr") | <c>0</c> ("normal") are
+ the valid values,
+ see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>
+ Section 5.2</p></item>
+ </taglist>
</section>
<funcs>
<func>
<name>call(ChannelRef, Msg) -></name>
<name>call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name>
- <fsummary> Makes a synchronous call to a channel.</fsummary>
+ <fsummary>Makes a synchronous call to a channel.</fsummary>
<type>
<v>ChannelRef = pid() </v>
- <d>As returned by start_link/4 </d>
- <v>Msg = term() </v>
- <v>Timeout = timeout() </v>
- <v>Reply = term() </v>
- <v>Reason = closed | timeout </v>
+ <d>As returned by <seealso marker = "#start_link-4">ssh_channel:start_link/4</seealso></d>
+ <v>Msg = term()</v>
+ <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 <seealso marker =
+ a message and waiting until a reply arrives, or a time-out
+ occurs. The channel calls <seealso marker =
"#Module:handle_call-3">Module:handle_call/3</seealso>
- to handle the message. If the channel process does not exist
+ to handle the message. If the channel process does not exist,
<c>{error, closed}</c> is returned.
</p>
</desc>
@@ -96,14 +111,14 @@
<fsummary>Sends an asynchronous message to the channel
ChannelRef and returns ok.</fsummary>
<type>
- <v>ChannelRef = pid() </v>
- <d>As returned by start_link/4 </d>
- <v>Msg = term() </v>
+ <v>ChannelRef = pid()</v>
+ <d>As returned by <seealso marker = "#start_link-4">ssh_channel:start_link/4</seealso></d>
+ <v>Msg = term()</v>
</type>
<desc>
<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
+ channel process does not exist. The channel calls
<seealso marker = "#Module:handle_cast-2">Module:handle_cast/2</seealso>
to handle the message.
</p>
@@ -112,31 +127,32 @@
<func>
<name>enter_loop(State) -> _ </name>
- <fsummary> Makes an existing process an ssh_channel process. </fsummary>
+ <fsummary>Makes an existing process an ssh_channel process.</fsummary>
<type>
- <v> State = term() - as returned by <seealso marker = "#init-1">ssh_channel:init/1</seealso></v>
+ <v>State = term()</v>
+ <d>as returned by <seealso marker = "#init-1">ssh_channel:init/1</seealso></d>
</type>
<desc>
- <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 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 <seealso marker = "#init-1">ssh_channel:init/1</seealso>
+ <p>Makes an existing process an <c>ssh_channel</c>
+ process. Does not return, instead the calling process
+ enters 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 <c>proc_lib</c>, see the <seealso
+ marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in <c>stdlib</c>.
+ The user is responsible for any initialization of the process
+ and must call <seealso marker = "#init-1">ssh_channel:init/1</seealso>.
</p>
</desc>
</func>
<func>
<name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name>
- <fsummary> Initiates a ssh_channel process.</fsummary>
+ <fsummary>Initiates an <c>ssh_channel</c> process.</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
<v>State = term()</v>
- <v>Timeout = timeout() </v>
- <v>Reason = term() </v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
</type>
<desc>
<p>
@@ -144,48 +160,47 @@
</p>
<taglist>
<tag><c><![CDATA[{channel_cb, atom()}]]></c></tag>
- <item>The module that implements the channel behaviour.</item>
+ <item><p>The module that implements the channel behaviour.</p></item>
<tag><c><![CDATA[{init_args(), list()}]]></c></tag>
- <item> The list of arguments to the callback module's
- init function.</item>
+ <item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item>
<tag><c><![CDATA[{cm, connection_ref()}]]></c></tag>
- <item> Reference to the ssh connection as returned by <seealso
- marker="ssh#connect-3">ssh:connect/3</seealso></item>
+ <item><p>Reference to the <c>ssh</c> connection as returned by <seealso
+ marker="ssh#connect-3">ssh:connect/3</seealso></p></item>
<tag><c><![CDATA[{channel_id, channel_id()}]]></c></tag>
- <item> Id of the SSH channel.</item>
+ <item><p>Id of the <c>ssh</c> channel.</p></item>
</taglist>
<note><p>This function is normally not called by the
- user. The user only needs to call if for some reason the
+ user. The user only needs to call if 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>
+ <c>ssh_channel:start_link/4</c>.</p>
</note>
</desc>
</func>
<func>
<name>reply(Client, Reply) -> _</name>
- <fsummary>Send a reply to a client.</fsummary>
+ <fsummary>Sends a reply to a client.</fsummary>
<type>
- <v>Client - opaque to the user, see explanation below</v>
+ <v>Client = opaque()</v>
<v>Reply = term()</v>
</type>
<desc>
- <p>This function can be used by a channel to explicitly send a
+ <p>This function can be used by a channel to send a
reply to a client that called <c>call/[2,3]</c> when the reply
cannot be defined in the return value of
<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
- <seealso marker="#call-2">ssh_channel:call/[2,3].</seealso>></p>
+ which is given back to the client as the return value of
+ <seealso marker="#call-2">ssh_channel:call/[2,3].</seealso></p>
</desc>
</func>
@@ -193,24 +208,25 @@
<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 process that handles an SSH channel.</fsummary>
<type>
<v>SshConnection = ssh_connection_ref()</v>
- <v>ChannelId = ssh_channel_id() </v>
- <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>ChannelId = ssh_channel_id()</v>
+ <d>As returned by
+ <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
+ <d>Name of the module implementing the service-specific parts
of the channel.</d>
<v>CbInitArgs = [term()]</v>
- <d>Argument list for the init function in the callback module. </d>
+ <d>Argument list for the <c>init</c> function in the callback module.</d>
<v>ChannelRef = pid()</v>
</type>
<desc>
- <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>Starts a process that handles an SSH channel. It is
+ called internally, by the <c>ssh</c> daemon, or explicitly by the <c>ssh</c>
+ client implementations. The behavior sets the
+ <c>trap_exit</c> flag to <c>true</c>.
</p>
</desc>
</func>
@@ -219,19 +235,19 @@
<section>
<marker id="cb_timeouts"></marker>
- <title> CALLBACK TIMEOUTS</title>
+ <title>CALLBACK TIME-OUTS</title>
- <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="#Module:handle_msg-2">handle_msg/2</seealso>
- will be called as <c>handle_msg(timeout, State). </c></p>
+ <p>The time-out values that can be returned by the callback functions
+ have the same semantics as in a <seealso marker="stdlib:gen_server">gen_server</seealso>.
+ If the time-out occurs, <seealso marker="#Module:handle_msg-2">handle_msg/2</seealso>
+ is called as <c>handle_msg(timeout, State)</c>.</p>
</section>
<funcs>
<func>
<name>Module:code_change(OldVsn, State, Extra) -> {ok,
NewState}</name>
- <fsummary> Converts process state when code is changed.</fsummary>
+ <fsummary>Converts process state when code is changed.</fsummary>
<type>
<v>OldVsn = term()</v>
<d>In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and
@@ -241,31 +257,31 @@
<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>
+ <d>Internal state of the channel.</d>
<v>Extra = term()</v>
- <d>Passed as-is from the <c>{advanced,Extra}</c>
+ <d>Passed "as-is" from the <c>{advanced,Extra}</c>
part of the update instruction.</d>
</type>
<desc>
- <p> Converts process state when code is changed.</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>This function is called by a client-side channel when it
+ is to update its internal state during a release
+ upgrade or downgrade, that is, when the instruction
+ <c>{update,Module,Change,...}</c>, where
+ <c>Change={advanced,Extra}</c>, is given in the <c>appup</c>
+ file. For more information, refer to Section 9.11.6
+ Release Handling Instructions in the
+ <seealso marker="doc/design_principles:release_handling#instr">System Documentation</seealso>.
</p>
<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>
+ channel processes are spawned by the <c>ssh</c> application and
+ hence added to its supervisor tree. The subsystem channels can
+ be upgraded when upgrading the user application, if the callback
+ functions can handle two versions of the state, but this function
+ cannot be used in the normal way.</p>
</note>
</desc>
@@ -274,36 +290,38 @@
<func>
<name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} |
{stop, Reason}</name>
- <fsummary> Makes necessary initializations and returns the
+ <fsummary>Makes necessary initializations and returns the
initial channel state if the initializations succeed.</fsummary>
<type>
- <v> Args = term() </v>
- <d> Last argument to ssh_channel:start_link/4.</d>
- <v> State = term() </v>
- <v> Reason = term() </v>
+ <v>Args = term()</v>
+ <d>Last argument to <c>ssh_channel:start_link/4</c>.</d>
+ <v>State = term()</v>
+ <v>Reason = term()</v>
</type>
<desc>
- <p> Makes necessary initializations and returns the initial channel
+ <p>Makes necessary initializations and returns the initial channel
state if the initializations succeed.
</p>
- <p>For more detailed information on timeouts see the section
- <seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p>
+ <p>For more detailed information on time-outs, see Section
+ <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>. </p>
</desc>
</func>
<func>
<name>Module:handle_call(Msg, From, State) -> Result</name>
- <fsummary> Handles messages sent by calling
- <c>ssh_channel:call/[2,3]</c></fsummary>
+ <fsummary>Handles messages sent by calling
+ <c>ssh_channel:call/[2,3]</c>.</fsummary>
<type>
<v>Msg = term()</v>
- <v>From = opaque to the user should be used as argument to
- ssh_channel:reply/2</v>
+ <v>From = opaque()</v>
+ <d>Is to be used as argument to
+ <seealso marker="#reply-2">ssh_channel:reply/2</seealso></d>
<v>State = term()</v>
<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>Reply = term()</v>
+ <d>Will be the return value of <seealso marker="#call-2">ssh_channel:call/[2,3]</seealso></d>
<v>NewState = term()</v>
<v>Reason = term()</v>
</type>
@@ -311,15 +329,15 @@
<p>Handles messages sent by calling
<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>
+ <p>For more detailed information on time-outs,, see Section
+ <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>.</p>
</desc>
</func>
<func>
<name>Module:handle_cast(Msg, State) -> Result</name>
- <fsummary> Handles messages sent by calling
- <c>ssh_channel:cact/2</c></fsummary>
+ <fsummary>Handles messages sent by calling
+ <c>ssh_channel:cact/2</c>.</fsummary>
<type>
<v>Msg = term()</v>
<v>State = term()</v>
@@ -329,11 +347,11 @@
<v>Reason = term()</v>
</type>
<desc>
- <p> Handles messages sent by calling
- <c>ssh_channel:cast/2</c>
+ <p>Handles messages sent by calling
+ <c>ssh_channel:cast/2</c>.
</p>
- <p>For more detailed information on timeouts see the section
- <seealso marker="#cb_timeouts">CALLBACK TIMEOUTS</seealso>. </p>
+ <p>For more detailed information on time-outs, see Section
+ <seealso marker="#cb_timeouts">CALLBACK TIME-OUTS</seealso>.</p>
</desc>
</func>
@@ -341,33 +359,33 @@
<name>Module:handle_msg(Msg, State) -> {ok, State} |
{stop, ChannelId, State}</name>
- <fsummary> Handle other messages than SSH connection protocol,
- call or cast messages sent to the channel.</fsummary>
+ <fsummary>Handles other messages than SSH connection protocol,
+ call, or cast messages sent to the channel.</fsummary>
<type>
<v>Msg = timeout | term()</v>
<v>ChannelId = ssh_channel_id()</v>
<v>State = term() </v>
</type>
<desc>
- <p>Handle other messages than ssh connection protocol, call or
+ <p>Handles other messages than SSH Connection Protocol, call, or
cast messages sent to the channel.
</p>
- <p> Possible erlang 'EXIT'-messages should be handled by this
- function and all channels should handle the following message.</p>
+ <p>Possible Erlang 'EXIT' messages is to be handled by this
+ function and all channels are to handle the following message.</p>
<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 <seealso
+ <item><p>This is the first message that the channel receives.
+ It is sent just before the <seealso
marker="#init-1">ssh_channel:init/1</seealso> function
- returns successfully. This is especially useful if the
+ 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>
+ useful for your particular scenario, ignore it by
+ immediately returning <c>{ok, State}</c>.
+ </p></item>
</taglist>
</desc>
</func>
@@ -375,42 +393,44 @@
<func>
<name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop,
ChannelId, State}</name>
- <fsummary> Handles ssh connection protocol messages. </fsummary>
+ <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary>
<type>
- <v>Msg = <seealso marker="ssh_connection"> ssh_connection:event() </seealso> </v>
+ <v>Msg = ssh_connection:event()</v>
<v>ChannelId = ssh_channel_id()</v>
<v>State = term()</v>
</type>
<desc>
- <p> Handles SSH connection protocol messages that may need
- service specific attention.
+ <p>Handles SSH Connection Protocol messages that may need
+ service-specific attention. For details,
+ see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>.
</p>
- <p> The following message is completely taken care of by the
- SSH channel behavior</p>
+ <p>The following message is taken care of by the
+ <c>ssh_channel</c> behavior.</p>
<taglist>
<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>
+ <item><p>The channel behavior sends a close message to the
+ other side, if such a message has not already been sent.
+ Then it terminates the channel with reason <c>normal</c>.</p></item>
</taglist>
</desc>
</func>
<func>
<name>Module:terminate(Reason, State) -> _</name>
- <fsummary> </fsummary>
+ <fsummary>Does cleaning up before channel process termination.
+ </fsummary>
<type>
<v>Reason = term()</v>
<v>State = term()</v>
</type>
<desc>
<p>This function is called by a channel process when it is
- about to terminate. Before this function is called <seealso
+ 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
+ </seealso> is called, if it has not been called earlier.
+ This function does any necessary cleaning
up. When it returns, the channel process terminates with
reason <c>Reason</c>. The return value is ignored.
</p>
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
index f3d05a8980..9a892d71fd 100644
--- a/lib/ssh/doc/src/ssh_client_key_api.xml
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -23,102 +23,112 @@
The Initial Developer of the Original Code is Ericsson AB.
</legalnotice>
<title>ssh_client_key_api</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
</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>
+ <p>Behavior describing the API for public key handling of an SSH client. By implementing
+ the callbacks defined in this behavior, the public key handling of an SSH client can
+ be customized. By default the <c>ssh</c> application implements this behavior
+ with help of the standard OpenSSH files,
+ see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p>
</description>
<section>
- <title>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. For more details on public key data types
- see the <seealso marker="public_key:public_key_records"> public_key user's guide.</seealso>
+ <p>Type definitions that are used more than once in this module,
+ or abstractions to indicate the intended use of the data
+ type, or both. For more details on public key data types,
+ refer to Section 2 Public Key Records in the
+ <seealso marker="public_key:public_key_records"> public_key user's guide:</seealso>
</p>
-
- <p> boolean() = true | false</p>
- <p> string() = [byte()] </p>
- <p> public_key() = #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</p>
- <p> private_key() = #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</p>
- <p> public_key_algorithm() = 'ssh-rsa'| 'ssh-dss' | atom()</p>
-
+ <taglist>
+ <tag><c>boolean() =</c></tag>
+ <item><p><c>true | false</c></p></item>
+ <tag><c>string() =</c></tag>
+ <item><p><c>[byte()]</c></p></item>
+ <tag><c>public_key() =</c></tag>
+ <item><p><c>#'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</c></p></item>
+ <tag><c>private_key() =</c></tag>
+ <item><p><c>#'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</c></p></item>
+ <tag><c>public_key_algorithm() =</c></tag>
+ <item><p><c>'ssh-rsa'| 'ssh-dss' | atom()</c></p></item>
+ </taglist>
</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>
+ <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>
+ <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>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#connect-3">ssh:connect/[3,4]</seealso></d>
- <v>Reason = term() </v>
+ <v>ConnectOptions = proplists:proplist()</v>
+ <d>Options provided to <seealso marker="ssh#connect-3">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>
+ <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>
+ <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>
+ <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>
+ <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
+ <d>Host key algorithm. Is to support <c>'ssh-rsa'| 'ssh-dss'</c>, but more algorithms
can be handled.</d>
- <v> ConnectOptions = proplists:proplist() </v>
- <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d>
+ <v>ConnectOptions = proplists:proplist() </v>
+ <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso>.</d>
- <v> Result = boolean()</v>
+ <v>Result = boolean()</v>
</type>
<desc>
- <p>Checks if a host key is trusted</p>
+ <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>
+ <fsummary>Fetches the users <em>public key</em> 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
+ <d>Host key algorithm. Is to support <c>'ssh-rsa'| 'ssh-dss'</c> but more algorithms
can be handled.</d>
- <v> ConnectOptions = proplists:proplist() </v>
- <d>Options provided to <seealso marker="ssh#connect-3">ssh:connect/[3,4]</seealso></d>
+ <v>ConnectOptions = proplists:proplist()</v>
+ <d>Options provided to <seealso marker="ssh#connect-3">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>PrivateKey = private_key()</v>
+ <d>Private key of the user matching the <c>Algorithm</c>.</d>
- <v>Reason = term() </v>
+ <v>Reason = term()</v>
</type>
<desc>
- <p>Fetches the users "public key" matching the <c>Algorithm</c>.
- <note><p>The private key contains the public key</p></note>
- </p>
+ <p>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</p>
+ <note><p>The private key contains the public key.</p></note>
+
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index 5e2926dfa6..e2b4e2ceb7 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -24,156 +24,174 @@
</legalnotice>
<title>ssh_connection</title>
+ <prepared></prepared>
+ <docno></docno>
<date></date>
+ <rev></rev>
</header>
<module>ssh_connection</module>
- <modulesummary>This module provides API functions to send <url href="http://www.ietf.org/rfc/rfc4254.txt"> SSH Connection Protocol </url>
+ <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>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#Module:handle_ssh_msg-2">handle_ssh_msg/2 </seealso>.</p>
+ <p>The SSH Connection Protocol is used by clients and servers,
+ that is, SSH channels, to communicate over the SSH connection. The
+ API functions in this module send SSH Connection Protocol events,
+ which are received as messages by the remote channel.
+ If the receiving channel is an Erlang process, the
+ messages have the 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 messages are handled by
+ <seealso marker="ssh_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</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><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 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</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>
- <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>
- <p><c>reason() = timeout | closed </c></p>
+ <title>DATA TYPES</title>
+
+ <p>Type definitions that are used more than once in this module,
+ or abstractions to indicate the intended use of the data
+ type, or both:</p>
+
+ <taglist>
+ <tag><c>boolean() =</c></tag>
+ <item><p><c>true | false </c></p></item>
+ <tag><c>string() =</c></tag>
+ <item><p>list of ASCII characters</p></item>
+ <tag><c>timeout() =</c></tag>
+ <item><p><c>infinity | integer()</c> in milliseconds</p></item>
+ <tag><c>ssh_connection_ref() =</c></tag>
+ <item><p>opaque() -as returned by
+ <c>ssh:connect/3</c> or sent to an SSH channel processes</p></item>
+ <tag><c>ssh_channel_id() =</c></tag>
+ <item><p><c>integer()</c></p></item>
+ <tag><c>ssh_data_type_code() =</c></tag>
+ <item><p><c>1</c> ("stderr") | <c>0</c> ("normal") are
+ valid values, see
+ <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 5.2.</p></item>
+ <tag><c>ssh_request_status() =</c></tag>
+ <item><p> <c>success | failure</c></p></item>
+ <tag><c>event() =</c></tag>
+ <item><p><c>{ssh_cm, ssh_connection_ref(), ssh_event_msg()}</c></p></item>
+ <tag><c>ssh_event_msg() =</c></tag>
+ <item><p><c>data_events() | status_events() | terminal_events()</c></p></item>
+ <tag><c>reason() =</c></tag>
+ <item><p><c>timeout | closed</c></p></item>
+ </taglist>
<taglist>
- <tag><b>data_events()</b></tag>
+ <tag><em>data_events()</em></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="ssh_connection#send-3"> ssh_connection:send/[3,4,5] </seealso></item>
+ <tag><c><![CDATA[{data, ssh_channel_id(), ssh_data_type_code(), Data :: binary()}]]></c></tag>
+ <item><p>Data has arrived on the channel. This event is sent as a
+ result of calling <seealso marker="ssh_connection#send-3">
+ ssh_connection:send/[3,4,5]</seealso>.</p></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="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>
- </item>
+ <item><p>Indicates that the other side sends no more data.
+ This event is sent as a result of calling <seealso
+ marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>.
+ </p></item>
</taglist>
</item>
- <tag><b>status_events()</b></tag>
+ <tag><em>status_events()</em></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
+ <item><p>A signal can be delivered to the remote process/service
+ using the following message. Some systems do not support
+ signals, in which case they are to ignore this message. There is
+ currently no function to generate this event as the signals
+ referred to are on OS-level and not something generated by an
+ Erlang program.</p></item>
+
+ <tag><c><![CDATA[{exit_signal, ssh_channel_id(), ExitSignal :: string(), ErrorMsg ::string(),
+ LanguageString :: string()}]]></c></tag>
+
+ <item><p>A remote execution can terminate violently because of a signal.
+ Then this message can be received. For details on valid string
+ values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>
+ Section 6.10, which shows a special case of these signals.</p></item>
+
+ <tag><c><![CDATA[{exit_status, ssh_channel_id(), ExitStatus :: integer()}]]></c></tag>
+ <item><p>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
+ command. A zero <c>exit_status</c> usually means that the command
+ terminated successfully. This event is sent as a result of calling
<seealso marker="ssh_connection#exit_status-3">
- ssh_connection:exit_status/3</seealso></item>
+ ssh_connection:exit_status/3</seealso>.</p></item>
<tag><c><![CDATA[{closed, ssh_channel_id()}]]></c></tag>
- <item> This event is sent as result of calling
- <seealso marker="ssh_connection#close-2">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>
+ <item><p>This event is sent as a result of calling
+ <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>.
+ Both the handling of this event and sending it are taken care of by the
+ <seealso marker="ssh_channel">ssh_channel</seealso> behavior.</p></item>
</taglist>
</item>
- <tag><b>terminal_events()</b></tag>
+ <tag><em>terminal_events()</em></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>Channels implementing a shell and command execution on the
+ server side are to handle the following messages that can be sent by client-
+ channel processes.</p>
- <note> <p>Events that includes a <c> WantReply</c> expects the event handling
- process to call <seealso marker="ssh_connection#reply_request-4">ssh_connection:reply_request/4</seealso>
- with the boolean value of <c> WantReply</c> as the second
- argument. </p></note>
+ <p>Events that include a <c>WantReply</c> expect the event handling
+ process to call <seealso marker="ssh_connection#reply_request-4">
+ ssh_connection:reply_request/4</seealso>
+ with the boolean value of <c>WantReply</c> as the second argument.</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="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>
- </item>
+ <tag><c><![CDATA[{env, ssh_channel_id(), WantReply :: boolean(),
+ Var ::string(), Value :: string()}]]></c></tag>
+ <item><p>Environment variables can be passed to the shell/command
+ to be started later. This event is sent as a result of calling <seealso
+ marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>.
+ </p></item>
<tag><c><![CDATA[{pty, ssh_channel_id(),
- boolean() = WantReply, {string() = Terminal, integer() = CharWidth,
- integer() = RowHeight, integer() = PixelWidth, integer() = PixelHeight,
- [{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
+ WantReply :: boolean(), {Terminal :: string(), CharWidth :: integer(),
+ RowHeight :: integer(), PixelWidth :: integer(), PixelHeight :: integer(),
+ TerminalModes :: [{Opcode :: atom() | integer(),
+ Value :: integer()}]}}]]></c></tag>
+ <item><p>A pseudo-terminal has been requested for the
+ session. <c>Terminal</c> is the value of the TERM environment
+ variable value, that is, <c>vt100</c>. Zero dimension parameters must
+ be ignored. The character/row dimensions override the pixel
+ dimensions (when non-zero). Pixel dimensions refer to the
+ drawable area of the window. <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.
- It may also be an opcode if the mnemonic name is not listed in the
- RFC. Example <c>OP code: 53, mnemonic name ECHO erlang atom:
- echo</c>.This event is sent as result of calling <seealso
- marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso></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="ssh_connection#shell-2"> 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>
+ as a lowercase Erlang atom, defined in
+ <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>, Section 8.
+ It can also be an <c>Opcode</c> if the mnemonic name is not listed in the
+ RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom:
+ echo</c>. This event is sent as a result of calling <seealso
+ marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item>
+
+ <tag><c><![CDATA[{shell, WantReply :: boolean()}]]></c></tag>
+ <item><p>This message requests that the user default shell
+ is started at the other end. This event is sent as a result of calling
+ <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>.
+ </p></item>
+
+ <tag><c><![CDATA[{window_change, ssh_channel_id(), CharWidth() :: integer(),
+ RowHeight :: integer(), PixWidth :: integer(), PixHeight :: integer()}]]></c></tag>
+ <item><p>When the window (terminal) size changes on the client
+ side, it <em>can</em> send a message to the server side to inform it of
+ the new dimensions. No API function generates this event.</p></item>
<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="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>
- </item>
+ WantReply :: boolean(), Cmd :: string()}]]></c></tag>
+ <item><p>This message requests that the server starts
+ execution of the given command. This event is sent as a result of calling <seealso
+ marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>.
+ </p></item>
</taglist>
</item>
</taglist>
@@ -183,80 +201,83 @@
<func>
<name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name>
- <fsummary>Adjusts the SSH flowcontrol window. </fsummary>
+ <fsummary>Adjusts the SSH flow control window.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id() </v>
- <v> NumOfBytes = integer()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>NumOfBytes = integer()</v>
</type>
<desc>
- <p>Adjusts the SSH flowcontrol window. This shall be done by both client and server side channel processes.</p>
+ <p>Adjusts the SSH flow control window. This is to be done by both the
+ client- and server-side channel processes.</p>
- <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
+ <note><p>Channels implemented with the <seealso marker="ssh_channel"> ssh_channel</seealso>
+ behavior do not normally need to call this function as flow control
+ is handled by the behavior. The behavior adjusts the window every time
the callback <seealso marker="ssh_channel#Module:handle_ssh_msg-2">
- handle_ssh_msg/2 </seealso> has returned after processing channel data</p> </note>
+ handle_ssh_msg/2</seealso> returns after processing channel data.</p></note>
</desc>
</func>
<func>
<name>close(ConnectionRef, ChannelId) -> ok</name>
- <fsummary>Sends a close message on the channel <c>ChannelId</c>. </fsummary>
+ <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
</type>
<desc>
- <p>A server or client channel process can choose to close their session by sending a close event.
+ <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
- behavior when the channel is terminated see <seealso
- marker="ssh_channel"> ssh_channel(3) </seealso> so channels implemented with the
- behavior should not call this function explicitly.</p></note>
+ <note><p>This function is called by the <c>ssh_channel</c>
+ behavior when the channel is terminated, see <seealso
+ marker="ssh_channel"> ssh_channel(3)</seealso>. Thus, channels implemented
+ with the behavior are not to call this function explicitly.</p></note>
</desc>
</func>
<func>
- <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | {error, reason()} </name>
- <fsummary>Request that the server start the execution of the given command. </fsummary>
+ <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() |
+ {error, reason()}</name>
+ <fsummary>Requests that the server starts the execution of the given command.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Command = string()</v>
- <v>Timeout = timeout() </v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Command = string()</v>
+ <v>Timeout = timeout()</v>
</type>
<desc>
- <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>
+ <p>Is to be called by a client-channel process to request that the server starts
+ executing the given command. The result is several messages according to the
+ following pattern. The last message is a channel close message, as the <c>exec</c>
+ 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>
- <item>The result of executing the command may be only one line
- or thousands of lines depending on the command.</item>
+ <tag><c>N x {ssh_cm, ssh_connection_ref(),
+ {data, ssh_channel_id(), ssh_data_type_code(), Data :: binary()}}</c></tag>
+ <item><p>The result of executing the command can be only one line
+ or thousands of lines depending on the command.</p></item>
<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>
+ <item><p>Indicates that no more data is to be sent.</p></item>
<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>
+ ssh_channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag>
+ <item><p>Not all systems send signals. For details on valid string
+ values, see RFC 4254, Section 6.10</p></item>
<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>
+ ssh_channel_id(), ExitStatus :: integer()}}</c></tag>
+ <item><p>It is recommended by the SSH Connection Protocol to send this
+ message, but that is not always the case.</p></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>
+ <item><p>Indicates that the <c>ssh_channel</c> started for the
+ execution of the command has now been shut down.</p></item>
</taglist>
</desc>
</func>
@@ -265,78 +286,72 @@
<name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name>
<fsummary>Sends the exit status of a command to the client.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Status = integer()</v>
+ <v>ConnectionRef = ssh_connection_ref() </v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Status = integer()</v>
</type>
<desc>
- <p>Should be called by a server channel process to sends the exit status of a command to the client.</p>
+ <p>Is to be called by a server-channel process to send the exit status of a command
+ to the client.</p>
</desc>
</func>
<func>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options) -> </name>
- <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | {error, reason()} </name>
- <fsummary>Send status replies to requests that want such replies. </fsummary>
+ <name>ptty_alloc(ConnectionRef, ChannelId, Options) -></name>
+ <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() |
+ {error, reason()}</name>
+ <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>,
+ to allocate a pseudo-terminal.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Options = proplists:proplist()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Options = proplists:proplist()</v>
</type>
<desc>
- <p> Sends a SSH Connection Protocol pty_req, to allocate a pseudo tty.
- Should be called by a SSH client process.
- Options are:
- </p>
+ <p>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal.
+ Is to be called by an SSH client process.</p>
+ <p>Options:</p>
<taglist>
<tag>{term, string()}</tag>
- <item>
- Defaults to os:getenv("TERM") or "vt100" if it is undefined.
- </item>
+ <item><p>Defaults to <em>os:getenv("TERM")</em> or <em>vt100</em>
+ if it is undefined.</p></item>
+
<tag>{width, integer()}</tag>
- <item>
- Defaults to 80 if pixel_width is not defined.
- </item>
+ <item><p>Defaults to 80 if <c>pixel_width</c> is not defined.</p></item>
+
<tag>{height, integer()}</tag>
- <item>
- Defaults to 24 if pixel_height is not defined.
- </item>
+ <item><p>Defaults to 24 if <c>pixel_height</c> is not defined.</p></item>
+
<tag>{pixel_width, integer()}</tag>
- <item>
- Is disregarded if width is defined.
- </item>
+ <item><p>Is disregarded if <c>width</c> is defined.</p></item>
+
<tag>{pixel_height, integer()}</tag>
- <item>
- Is disregarded if height is defined.
- </item>
+ <item><p>Is disregarded if <c>height</c> is defined.</p></item>
+
<tag>{pty_opts, [{posix_atom(), integer()}]}</tag>
- <item>
- Option may be an empty list, otherwise
- see possible POSIX names in section 8 in <url href="http://www.ietf.org/rfc/rfc4254.txt"> RFC 4254</url>.
+ <item><p>Option can be an empty list. Otherwise, see possible <em>POSIX</em> names
+ in Section 8 in <url href="http://www.ietf.org/rfc/rfc4254.txt"> RFC 4254</url>.</p>
</item>
</taglist>
-
</desc>
</func>
- <func>
+ <func>
<name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name>
- <fsummary>Send status replies to requests that want such replies. </fsummary>
+ <fsummary>Sends status replies to requests that want such replies.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> WantReply = boolean()</v>
- <v> Status = ssh_request_status() </v>
- <v> ChannelId = ssh_channel_id()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>WantReply = boolean()</v>
+ <v>Status = ssh_request_status()</v>
+ <v>ChannelId = ssh_channel_id()</v>
</type>
<desc>
<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 while handling an ssh connection
- protocol message containing a <c>WantReply</c> boolean
- value.
- </p>
+ stated that it wants a status report, that is, <c>WantReply = true</c>.
+ If <c>WantReply</c> is <c>false</c>, calling this function becomes a
+ "noop". Is to be called while handling an SSH Connection
+ Protocol message containing a <c>WantReply</c> boolean value.</p>
</desc>
</func>
@@ -346,98 +361,97 @@
<name>send(ConnectionRef, ChannelId, Type, Data) -></name>
<name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) ->
ok | {error, timeout} | {error, closed}</name>
- <fsummary>Sends channel data </fsummary>
+ <fsummary>Sends channel data.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Data = binary()</v>
- <v> Type = ssh_data_type_code()</v>
- <v> Timeout = timeout()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Data = binary()</v>
+ <v>Type = ssh_data_type_code()</v>
+ <v>Timeout = timeout()</v>
</type>
<desc>
- <p>Should be called by client- and server channel processes to send data to each other.
+ <p>Is to be called by client- and server-channel processes to send data to each other.
</p>
</desc>
</func>
<func>
<name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name>
- <fsummary>Sends eof on the channel <c>ChannelId</c>. </fsummary>
+ <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
</type>
<desc>
- <p>Sends eof on the channel <c>ChannelId</c>.
- </p>
+ <p>Sends EOF on channel <c>ChannelId</c>.</p>
</desc>
</func>
<func>
- <name>session_channel(ConnectionRef, Timeout) -> </name>
+ <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. </fsummary>
+ <fsummary>Opens a channel for an SSH session.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref()</v>
- <v> InitialWindowSize = integer() </v>
- <v> MaxPacketSize = integer() </v>
- <v> Timeout = timeout()</v>
- <v> Reason = term() </v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>InitialWindowSize = integer()</v>
+ <v>MaxPacketSize = integer()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
</type>
<desc>
<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>
+ is the id used as input to the other functions in this module.</p>
</desc>
</func>
<func>
- <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | {error, reason()} </name>
- <fsummary> Environment variables may be passed to the
+ <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() |
+ {error, reason()}</name>
+ <fsummary>Environment variables can be passed to the
shell/command to be started later.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Var = string()</v>
- <v> Value = string()</v>
- <v> Timeout = timeout()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Var = string()</v>
+ <v>Value = string()</v>
+ <v>Timeout = timeout()</v>
</type>
<desc>
- <p> Environment variables may be passed before starting the
- shell/command. Should be called by a client channel processes.
- </p>
+ <p>Environment variables can be passed before starting the
+ shell/command. Is to be called by a client channel processes.</p>
</desc>
</func>
<func>
<name>shell(ConnectionRef, ChannelId) -> ssh_request_status() | {error, closed}
</name>
- <fsummary> Requests that the user's default shell (typically
- defined in /etc/passwd in UNIX systems) shall be executed at the server
- end. </fsummary>
+ <fsummary>Requests that the user default shell (typically defined in
+ /etc/passwd in Unix systems) is to be executed at the server end.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
</type>
<desc>
- <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>
+ <p>Is to be called by a client channel process to request that the user default
+ shell (typically defined in /etc/passwd in Unix systems) is executed
+ at the server end.</p>
</desc>
</func>
<func>
- <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | {error, reason()} </name>
- <fsummary> </fsummary>
+ <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() |
+ {error, reason()}</name>
+ <fsummary>Requests to execute a predefined subsystem on the server.</fsummary>
<type>
- <v> ConnectionRef = ssh_connection_ref() </v>
- <v> ChannelId = ssh_channel_id()</v>
- <v> Subsystem = string()</v>
- <v> Timeout = timeout()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>ChannelId = ssh_channel_id()</v>
+ <v>Subsystem = string()</v>
+ <v>Timeout = timeout()</v>
</type>
<desc>
- <p> Should be called by a client channel process for requesting to execute a predefined subsystem on the server.
+ <p>Is to 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_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
index f7133e4ba5..73dd90c962 100644
--- a/lib/ssh/doc/src/ssh_server_key_api.xml
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -23,68 +23,81 @@
The Initial Developer of the Original Code is Ericsson AB.
</legalnotice>
<title>ssh_server_key_api</title>
+ <prepared></prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
</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>
+ <p>Behaviour describing the API for public key handling of an SSH server. By implementing
+ the callbacks defined in this behavior, the public key handling of an SSH server can
+ be customized. By default the SSH application implements this behavior
+ with help of the standard OpenSSH files,
+ see the <seealso marker="SSH_app"> ssh(6)</seealso> application manual.</p>
</description>
<section>
- <title>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. For more details on public key data types
- see the <seealso marker="public_key:public_key_records"> public_key user's guide.</seealso>
+ <p>Type definitions that are used more than once in this module,
+ or abstractions to indicate the intended use of the data
+ type, or both. For more details on public key data types,
+ refer to Section 2 Public Key Records in the
+ <seealso marker="public_key:public_key_records"> public_key user's guide</seealso>.
</p>
- <p> boolean() = true | false</p>
- <p> string() = [byte()]</p>
- <p> public_key() = #'RSAPublicKey'{} | {integer(), #'Dss-Parms'{}} | term()</p>
- <p> private_key() = #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</p>
- <p> public_key_algorithm() = 'ssh-rsa' | 'ssh-dss' | atom()</p>
+ <taglist>
+ <tag><c>boolean() =</c></tag>
+ <item><p><c>true | false</c></p></item>
+ <tag><c>string() =</c></tag>
+ <item><p><c>[byte()]</c></p></item>
+ <tag><c>public_key() =</c></tag>
+ <item><p><c>#'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term()</c></p></item>
+ <tag><c>private_key() =</c></tag>
+ <item><p><c>#'RSAPrivateKey'{} | #'DSAPrivateKey'{} | term()</c></p></item>
+ <tag><c>public_key_algorithm() =</c></tag>
+ <item><p><c>'ssh-rsa'| 'ssh-dss' | atom()</c></p></item>
+ </taglist>
</section>
-
+
<funcs>
<func>
<name>Module:host_key(Algorithm, DaemonOptions) ->
{ok, Key} | {error, Reason}</name>
- <fsummary>Fetches the hosts private key </fsummary>
+ <fsummary>Fetches the host’s private key.</fsummary>
<type>
<v>Algorithm = public_key_algorithm()</v>
- <d> Host key algorithm. Should support 'ssh-rsa' | 'ssh-dss' but additional algorithms
+ <d>Host key algorithm. Is to support <c>'ssh-rsa' | 'ssh-dss'</c>, but more algorithms
can be handled.</d>
- <v> DaemonOptions = proplists:proplist() </v>
- <d>Options provided to <seealso marker="ssh#daemon-2">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>
+ <v>DaemonOptions = proplists:proplist()</v>
+ <d>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</d>
+ <v>Key = private_key()</v>
+ <d>Private key of the host matching the <c>Algorithm</c>.</d>
+ <v>Reason = term()</v>
</type>
<desc>
- <p>Fetches the hosts private key</p>
+ <p>Fetches the private key of the host.</p>
</desc>
</func>
<func>
<name>Module:is_auth_key(Key, User, DaemonOptions) -> Result</name>
- <fsummary> Checks if the user key is authorized</fsummary>
+ <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-2">ssh:daemon/[2,3]</seealso></d>
- <v> Result = boolean()</v>
+ <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>User owning the public key.</d>
+ <v>DaemonOptions = proplists:proplist()</v>
+ <d>Options provided to <seealso marker="ssh#daemon-2">ssh:daemon/[2,3]</seealso>.</d>
+ <v>Result = boolean()</v>
</type>
<desc>
- <p> Checks if the user key is authorized </p>
+ <p>Checks if the user key is authorized.</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index ab111562f9..fc418bc934 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -23,131 +23,173 @@
<title>ssh_sftp</title>
<prepared>OTP</prepared>
+ <docno></docno>
<date>2005-09-22</date>
+ <rev></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
+ <p>This module implements an SSH FTP (SFTP) client. SFTP is a
secure, encrypted file transfer service available for
SSH.</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:
+ <title>DATA TYPES</title>
+ <p>Type definitions that are used more than once in this module,
+ or abstractions to indicate the intended use of the data type, or both:
</p>
- <p><c>ssh_connection_ref() - opaque to the user
- returned by ssh:connect/3</c></p>
- <p><c>timeout() = infinity | integer() - in milliseconds.</c></p>
+
+ <taglist>
+ <tag><c>ssh_connection_ref() =</c></tag>
+ <item><p>opaque() - as returned by <c>ssh:connect/3</c></p></item>
+ <tag><c>timeout()</c></tag>
+ <item><p>= <c>infinity | integer() in milliseconds. Default infinity.</c></p></item>
+ </taglist>
</section>
<section>
- <title>TIMEOUTS </title>
- <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>
+ <title>Time-outs</title>
+ <p>If the request functions for the SFTP channel return <c>{error, timeout}</c>,
+ it does not guarantee that the request never reached the server and was
+ not performed. It only means that no answer was received from the
+ server within the expected time.</p>
</section>
<funcs>
+ <func>
+ <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, Error}</name>
+ <v>ChannelPid = pid()</v>
+ <v>Handle = term()</v>
+ <v>Position = integer()</v>
+ <v>Len = integer()</v>
+ <v>N = term()</v>
+ <v>Reason = term()</v>
+
+ <desc><p>The <c><![CDATA[apread]]></c> function reads from a specified position,
+ combining the <c><![CDATA[position]]></c> and <c><![CDATA[aread]]></c> functions.</p>
+ <p><seealso marker="#apread-4">ssh_sftp:apread/4</seealso></p> </desc>
+ </func>
+
+ <func>
+ <name>apwrite(ChannelPid, Handle, Position, Data) -> ok | {error, Reason}</name>
+ <fsummary>Writes asynchronously to an open file.</fsummary>
+ <type>
+ <v>ChannelPid = pid()</v>
+ <v>Handle = term()</v>
+ <v>Position = integer()</v>
+ <v>Len = integer()</v>
+ <v>Data = binary()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p><c><![CDATA[apwrite]]></c> writes on a specified position, combining
+ the <c><![CDATA[position]]></c> and <c><![CDATA[awrite]]></c> operations.</p>
+ <p><seealso marker="#awrite-3">ssh_sftp:awrite/3</seealso> </p></desc>
+ </func>
+
+ <func>
+ <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, Error}</name>
+ <fsummary>Reads asynchronously from an open file.</fsummary>
+ <type>
+ <v>ChannelPid = pid()</v>
+ <v>Handle = term()</v>
+ <v>Position = integer()</v>
+ <v>Len = integer()</v>
+ <v>N = term()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Reads from an open file, without waiting for the result. If the
+ handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c>
+ is a term guaranteed to be unique between calls of <c><![CDATA[aread]]></c>.
+ The actual data is sent as a message to the calling process. This
+ message has the form <c><![CDATA[{async_reply, N, Result}]]></c>, where
+ <c><![CDATA[Result]]></c> is the result from the read, either <c><![CDATA[{ok, Data}]]></c>,
+ <c><![CDATA[eof]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p>
+ </desc>
+ </func>
+
+
+
<func>
- <name>start_channel(ConnectionRef) -> </name>
- <name>start_channel(ConnectionRef, Options) -> </name>
- <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>
+ <name>awrite(ChannelPid, Handle, Data) -> ok | {error, Reason}</name>
+ <fsummary>Writes asynchronously to an open file.</fsummary>
<type>
- <v>Host = string()</v>
- <v>ConnectionRef = ssh_connection_ref()</v>
- <v>Port = integer()</v>
- <v>Options = [{Option, Value}]</v>
+ <v>ChannelPid = pid()</v>
+ <v>Handle = term()</v>
+ <v>Position = integer()</v>
+ <v>Len = integer()</v>
+ <v>Data = binary()</v>
+ <v>Timeout = timeout()</v>
<v>Reason = term()</v>
</type>
<desc>
- <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>
- <tag><c><![CDATA[{timeout, timeout()}]]></c></tag>
- <item>
- <p>The timeout is passed to the ssh_channel start function,
- and defaults to infinity.</p>
- </item>
- <tag>
- <p><c><![CDATA[{sftp_vsn, integer()}]]></c></p>
- </tag>
- <item>
- <p>
- Desired SFTP protocol version.
- The actual version will be the minimum of
- the desired version and the maximum supported
- versions by the SFTP server.
- </p>
- </item>
- </taglist>
- <p>All other options are directly passed to
- <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a
- connection is already provided. </p>
+ <p>Writes to an open file, without waiting for the result. If the
+ handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c>
+ is a term guaranteed to be unique between calls of
+ <c><![CDATA[awrite]]></c>. The result of the <c><![CDATA[write]]></c> operation is sent
+ as a message to the calling process. This message has the form
+ <c><![CDATA[{async_reply, N, Result}]]></c>, where <c><![CDATA[Result]]></c> is the result
+ from the write, either <c><![CDATA[ok]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p>
</desc>
</func>
<func>
- <name>stop_channel(ChannelPid) -> ok</name>
- <fsummary>Stops the SFTP client channel.</fsummary>
+ <name>close(ChannelPid, Handle) -></name>
+ <name>close(ChannelPid, Handle, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Closes an open handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
+ <v>Handle = term()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
</type>
<desc>
- <p>Stops an SFTP channel. Does not close the SSH connetion.
- Use <seealso marker="ssh">ssh:close/1</seealso> to close it.</p>
+ <p>Closes a handle to an open file or directory on the server.</p>
</desc>
</func>
-
+
<func>
- <name>read_file(ChannelPid, File) -> </name>
- <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, Reason}</name>
- <fsummary>Read a file</fsummary>
+ <name>delete(ChannelPid, Name) -></name>
+ <name>delete(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Deletes a file.</fsummary>
<type>
- <v>ChannelPid = pid()</v>
- <v>File = string()</v>
- <v>Data = binary()</v>
+ <v>ChannelPid = pid()</v>
+ <v>Name = string()</v>
<v>Timeout = timeout()</v>
- <v>Reason = term()</v>
+ <v>Reason = term()</v>
</type>
<desc>
- <p>Reads a file from the server, and returns the data in a binary,
- like <c><![CDATA[file:read_file/1]]></c>.</p>
+ <p>Deletes the file specified by <c><![CDATA[Name]]></c>, like
+ <seealso marker="kernel:file#delete-1">file:delete/1</seealso></p>
</desc>
</func>
+
<func>
- <name>write_file(ChannelPid, File, Iolist) -> </name>
- <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Write a file</fsummary>
+ <name>del_dir(ChannelPid, Name) -></name>
+ <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Deletes an empty directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
- <v>File = string()</v>
- <v>Iolist = iolist()</v>
+ <v>Name = string()</v>
<v>Timeout = timeout()</v>
<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 does not exist or is owerwritten if it does.</p>
+ <p>Deletes a directory specified by <c><![CDATA[Name]]></c>.
+ The directory must be empty before it can be successfully deleted.
+ </p>
</desc>
</func>
- <func>
- <name>list_dir(ChannelPid, Path) -> </name>
+
+ <func>
+ <name>list_dir(ChannelPid, Path) -></name>
<name>list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, Reason}</name>
- <fsummary>List directory</fsummary>
+ <fsummary>Lists the directory.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Path = string()</v>
@@ -161,10 +203,45 @@
filenames as a list of strings.</p>
</desc>
</func>
+
+ <func>
+ <name>make_dir(ChannelPid, Name) -></name>
+ <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Creates a directory.</fsummary>
+ <type>
+ <v>ChannelPid = pid()</v>
+ <v>Name = string()</v>
+ <v>Timeout = timeout()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c>
+ must be a full path to a new directory. The directory can only be
+ created in an existing directory.</p>
+ </desc>
+ </func>
+
<func>
- <name>open(ChannelPid, File, Mode) -> </name>
+ <name>make_symlink(ChannelPid, Name, Target) -></name>
+ <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Creates a symbolic link.</fsummary>
+ <type>
+ <v>ChannelPid = pid()</v>
+ <v>Name = string()</v>
+ <v>Target = string()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the
+ name <c><![CDATA[Name]]></c>, like
+ <seealso marker="kernel:file#make_symlink-2">file:make_symlink/2</seealso></p>
+ </desc>
+ </func>
+
+ <func>
+ <name>open(ChannelPid, File, Mode) -></name>
<name>open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, Reason}</name>
- <fsummary>Open a file and return a handle</fsummary>
+ <fsummary>Opens a file and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>File = string()</v>
@@ -175,14 +252,14 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Opens a file on the server, and returns a handle that
+ <p>Opens a file on the server and returns a handle, which
can be used for reading or writing.</p>
</desc>
</func>
<func>
- <name>opendir(ChannelPid, Path) -> </name>
+ <name>opendir(ChannelPid, Path) -></name>
<name>opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, Reason}</name>
- <fsummary>Open a directory and return a handle</fsummary>
+ <fsummary>Opens a directory and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Path = string()</v>
@@ -190,7 +267,7 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Opens a handle to a directory on the server, the handle
+ <p>Opens a handle to a directory on the server. The handle
can be used for reading directory contents.</p>
</desc>
</func>
@@ -198,14 +275,15 @@
<func>
<name>open_tar(ChannelPid, Path, Mode) -></name>
<name>open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, Reason}</name>
- <fsummary>Opens a tar file on the server to which <v>ChannelPid</v> is connected and returns a handle</fsummary>
+ <fsummary>Opens a tar file on the server to which <c>ChannelPid</c>
+ is connected and returns a handle.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Path = string()</v>
- <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt] </v>
+ <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt]</v>
<v>EncryptOpt = {crypto,{InitFun,EncryptFun,CloseFun}}</v>
<v>DecryptOpt = {crypto,{InitFun,DecryptFun}}</v>
- <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize}) </v>
+ <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize})</v>
<v>CryptoState = any()</v>
<v>ChunkSize = undefined | pos_integer()</v>
<v>EncryptFun = (fun(PlainBin,CryptoState) -> EncryptResult)</v>
@@ -219,113 +297,86 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Opens a handle to a tar file on the server associated with <c>ChannelPid</c>. The handle
- can be used for remote tar creation and extraction as defined by the
- <seealso marker="stdlib:erl_tar#init/3">erl_tar:init/3</seealso> function.
+ <p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>.
+ The handle can be used for remote tar creation and extraction, as defined by the
+ <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function.
</p>
- <p>An example of writing and then reading a tar file:</p>
- <code type="none">
- {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]),
- ok = erl_tar:add(HandleWrite, .... ),
- ok = erl_tar:add(HandleWrite, .... ),
- ...
- ok = erl_tar:add(HandleWrite, .... ),
- ok = erl_tar:close(HandleWrite),
-
- %% And for reading
- {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read]),
- {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
- ok = erl_tar:close(HandleRead),
- </code>
-
- <p>The <c>crypto</c> mode option is applied to the generated stream of bytes just prior to sending
- them to the sftp server. This is intended for encryption but could of course be used for other
+
+ <p> For code exampel see Section
+ <seealso marker="using_ssh">SFTP Client with TAR Compression and Encryption</seealso> in
+ the ssh Users Guide. </p>
+
+ <p>The <c>crypto</c> mode option is applied to the generated stream of bytes prior to sending
+ them to the SFTP server. This is intended for encryption but can be used for other
purposes.
</p>
<p>The <c>InitFun</c> is applied once
- prior to any other crypto operation. The returned <c>CryptoState</c> is then folded into
- repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned
- from those Funs are sent further to the remote sftp server. Finally - if doing encryption
- - the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is
+ prior to any other <c>crypto</c> operation. The returned <c>CryptoState</c> is then folded into
+ repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned
+ from those funs are sent further to the remote SFTP server. Finally, if doing encryption,
+ the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is
responsible for padding (if needed) and encryption of that last piece.
</p>
<p>The <c>ChunkSize</c> defines the size of the <c>PlainBin</c>s that <c>EncodeFun</c> is applied
- to. If the <c>ChunkSize</c> is <c>undefined</c> the size of the <c>PlainBin</c>s varies because
- this is inteded for stream crypto while a fixed <c>ChunkSize</c> is intended for block crypto. It
- is possible to change the <c>ChunkSize</c>s in the return from the <c>EncryptFun</c> or
- <c>DecryptFun</c>. It is in fact possible to change the value between <c>pos_integer()</c> and
- <c>undefined</c>.
+ to. If the <c>ChunkSize</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies,
+ because this is intended for stream crypto, whereas a fixed <c>ChunkSize</c> is intended for block crypto.
+ <c>ChunkSize</c>s can be changed in the return from the <c>EncryptFun</c> or
+ <c>DecryptFun</c>. The value can be changed between <c>pos_integer()</c> and <c>undefined</c>.
</p>
- <p>The write and read example above can be extended with encryption and decryption:</p>
- <code type="none">
- %% First three parameters depending on which crypto type we select:
- Key = &lt;&lt;"This is a 256 bit key. abcdefghi">>,
- Ivec0 = crypto:rand_bytes(16),
- DataSize = 1024, % DataSize rem 16 = 0 for aes_cbc
-
- %% Initialization of the CryptoState, in this case it is the Ivector.
- InitFun = fun() -> {ok, Ivec0, DataSize} end,
-
- %% How to encrypt:
- EncryptFun =
- fun(PlainBin,Ivec) ->
- EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin),
- {ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)}
- end,
-
- %% What to do with the very last block:
- CloseFun =
- fun(PlainBin, Ivec) ->
- EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec,
- pad(16,PlainBin) %% Last chunk
- ),
- {ok, EncryptedBin}
- end,
-
- Cw = {InitFun,EncryptFun,CloseFun},
- {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]),
- ok = erl_tar:add(HandleWrite, .... ),
- ok = erl_tar:add(HandleWrite, .... ),
- ...
- ok = erl_tar:add(HandleWrite, .... ),
- ok = erl_tar:close(HandleWrite),
-
- %% And for decryption (in this crypto example we could use the same InitFun
- %% as for encryption):
- DecryptFun =
- fun(EncryptedBin,Ivec) ->
- PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin),
- {ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)}
- end,
-
- Cr = {InitFun,DecryptFun},
- {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cw}]),
- {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
- ok = erl_tar:close(HandleRead),
- </code>
+
</desc>
</func>
<func>
- <name>close(ChannelPid, Handle) -> </name>
- <name>close(ChannelPid, Handle, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Close an open handle</fsummary>
+ <name>position(ChannelPid, Handle, Location) -></name>
+ <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, Error}</name>
+ <fsummary>Sets the file position of a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Handle = term()</v>
+ <v>Location = Offset
+ | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v>
+ <v>Offset = integer()</v>
<v>Timeout = timeout()</v>
+ <v>NewPosition = integer()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Closes a handle to an open file or directory on the server.</p>
+ <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
+ successful, otherwise <c><![CDATA[{error, Reason}]]></c>. <c><![CDATA[Location]]></c> is
+ one of the following:</p>
+ <taglist>
+ <tag><c><![CDATA[Offset]]></c></tag>
+ <item>
+ <p>The same as <c><![CDATA[{bof, Offset}]]></c>.</p>
+ </item>
+ <tag><c><![CDATA[{bof, Offset}]]></c></tag>
+ <item>
+ <p>Absolute offset.</p>
+ </item>
+ <tag><c><![CDATA[{cur, Offset}]]></c></tag>
+ <item>
+ <p>Offset from the current position.</p>
+ </item>
+ <tag><c><![CDATA[{eof, Offset}]]></c></tag>
+ <item>
+ <p>Offset from the end of file.</p>
+ </item>
+ <tag><c><![CDATA[bof | cur | eof]]></c></tag>
+ <item>
+ <p>The same as eariler with <c><![CDATA[Offset]]></c> 0,
+ that is, <c><![CDATA[{bof, 0} | {cur, 0} | {eof, 0}]]></c>.
+ </p>
+ </item>
+ </taglist>
</desc>
</func>
+
<func>
- <name>read(ChannelPid, Handle, Len) -> </name>
- <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name>
- <name>pread(ChannelPid, Handle, Position, Len) -> </name>
+ <name>pread(ChannelPid, Handle, Position, Len) -></name>
<name>pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name>
- <fsummary>Read from an open file</fsummary>
+ <fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Handle = term()</v>
@@ -336,47 +387,16 @@
<v>Reason = term()</v>
</type>
<desc>
- <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by
- <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
- will be read and returned. If no bytes are read, <c><![CDATA[eof]]></c>
- is returned.</p>
- <p>The <c><![CDATA[pread]]></c> function reads from a specified position,
- combining the <c><![CDATA[position]]></c> and <c><![CDATA[read]]></c> functions.</p>
- </desc>
- </func>
- <func>
- <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, Error}</name>
- <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, Error}</name>
- <fsummary>Read asynchronously from an open file</fsummary>
- <type>
- <v>ChannelPid = pid()</v>
- <v>Handle = term()</v>
- <v>Position = integer()</v>
- <v>Len = integer()</v>
- <v>N = term()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>Reads from an open file, without waiting for the result. If the
- handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where N
- is a term guaranteed to be unique between calls of <c><![CDATA[aread]]></c>.
- The actual data is sent as a message to the calling process. This
- message has the form <c><![CDATA[{async_reply, N, Result}]]></c>, where
- <c><![CDATA[Result]]></c> is the result from the read, either <c><![CDATA[{ok, Data}]]></c>,
- or <c><![CDATA[eof]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p>
- <p>The <c><![CDATA[apread]]></c> function reads from a specified position,
- combining the <c><![CDATA[position]]></c> and <c><![CDATA[aread]]></c> functions.</p>
+ <p>The <c><![CDATA[pread]]></c> function reads from a specified position,
+ combining the <c><![CDATA[position]]></c> and <c><![CDATA[read]]></c> functions.</p>
+ <p><seealso marker="#read-4">ssh_sftp:read/4</seealso></p>
</desc>
</func>
+
<func>
- <name>write(ChannelPid, Handle, Data) -></name>
- <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, Error}</name>
- <name>pwrite(ChannelPid, Handle, Position, Data) -> ok </name>
+ <name>pwrite(ChannelPid, Handle, Position, Data) -> ok</name>
<name>pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, Error}</name>
- <fsummary>Write to an open file</fsummary>
+ <fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Handle = term()</v>
@@ -386,94 +406,59 @@
<v>Reason = term()</v>
</type>
<desc>
- <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 or S<c><![CDATA[{error, Reason}]]></c>
- otherwise.</p>
- <p>Typical error reasons are:</p>
- <taglist>
- <tag><c><![CDATA[ebadf]]></c></tag>
- <item>
- <p>The file is not opened for writing.</p>
- </item>
- <tag><c><![CDATA[enospc]]></c></tag>
- <item>
- <p>There is a no space left on the device.</p>
- </item>
- </taglist>
+ <p>The <c><![CDATA[pread]]></c> function writes to a specified position,
+ combining the <c><![CDATA[position]]></c> and <c><![CDATA[write]]></c> functions.</p>
+ <p><seealso marker="#write-3">ssh_sftp:write/3</seealso></p>
</desc>
</func>
- <func>
- <name>awrite(ChannelPid, Handle, Data) -> ok | {error, Reason} </name>
- <name>apwrite(ChannelPid, Handle, Position, Data) -> ok | {error, Reason}</name>
- <fsummary>Write asynchronously to an open file</fsummary>
+
+
+ <func>
+ <name>read(ChannelPid, Handle, Len) -></name>
+ <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, Error}</name>
+ <fsummary>Reads from an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Handle = term()</v>
<v>Position = integer()</v>
<v>Len = integer()</v>
- <v>Data = binary()</v>
<v>Timeout = timeout()</v>
+ <v>Data = string() | binary()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Writes to an open file, without waiting for the result. If the
- handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where N
- is a term guaranteed to be unique between calls of
- <c><![CDATA[awrite]]></c>. The result of the <c><![CDATA[write]]></c> operation is sent
- as a message to the calling process. This message has the form
- <c><![CDATA[{async_reply, N, Result}]]></c>, where <c><![CDATA[Result]]></c> is the result
- from the write, either <c><![CDATA[ok]]></c>, or <c><![CDATA[{error, Error}]]></c>.</p>
- <p>The <c><![CDATA[apwrite]]></c> writes on a specified position, combining
- the <c><![CDATA[position]]></c> and <c><![CDATA[awrite]]></c> operations.</p>
+ <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by
+ <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 <c>eof</c>, only the remaining bytes
+ are read and returned. If no bytes are read, <c><![CDATA[eof]]></c>
+ is returned.</p>
</desc>
</func>
- <func>
- <name>position(ChannelPid, Handle, Location) -> </name>
- <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, Error}</name>
- <fsummary>Seek position in open file</fsummary>
+
+ <func>
+ <name>read_file(ChannelPid, File) -></name>
+ <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, Reason}</name>
+ <fsummary>Reads a file.</fsummary>
<type>
- <v>ChannelPid = pid()</v>
- <v>Handle = term()</v>
- <v>Location = Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v>
- <v>Offset = integer()</v>
+ <v>ChannelPid = pid()</v>
+ <v>File = string()</v>
+ <v>Data = binary()</v>
<v>Timeout = timeout()</v>
- <v>NewPosition = integer()</v>
- <v>Reason = term()</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
- successful, otherwise <c><![CDATA[{error, Reason}]]></c>. <c><![CDATA[Location]]></c> is
- one of the following:</p>
- <taglist>
- <tag><c><![CDATA[Offset]]></c></tag>
- <item>
- <p>The same as <c><![CDATA[{bof, Offset}]]></c>.</p>
- </item>
- <tag><c><![CDATA[{bof, Offset}]]></c></tag>
- <item>
- <p>Absolute offset.</p>
- </item>
- <tag><c><![CDATA[{cur, Offset}]]></c></tag>
- <item>
- <p>Offset from the current position.</p>
- </item>
- <tag><c><![CDATA[{eof, Offset}]]></c></tag>
- <item>
- <p>Offset from the end of file.</p>
- </item>
- <tag><c><![CDATA[bof | cur | eof]]></c></tag>
- <item>
- <p>The same as above with <c><![CDATA[Offset]]></c> 0.</p>
- </item>
- </taglist>
+ <p>Reads a file from the server, and returns the data in a binary,
+ like
+ <seealso marker="kernel:file#read_file-1">file:read_file/1</seealso></p>
</desc>
</func>
- <func>
- <name>read_file_info(ChannelPid, Name) -> </name>
+
+ <func>
+ <name>read_file_info(ChannelPid, Name) -></name>
<name>read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name>
- <fsummary>Get information about a file</fsummary>
+ <fsummary>Gets information about a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Name = string()</v>
@@ -484,138 +469,191 @@
</type>
<desc>
<p>Returns a <c><![CDATA[file_info]]></c> record from the file specified by
- <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like <c><![CDATA[file:read_file_info/2]]></c>.</p>
+ <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>,
+ like <seealso marker="kernel:file#read_file_info-2">file:read_file_info/2</seealso></p>
</desc>
</func>
- <func>
- <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, Reason}</name>
- <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name>
- <fsummary>Get information about a symbolic link</fsummary>
+
+ <func>
+ <name>read_link(ChannelPid, Name) -></name>
+ <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, Reason}</name>
+ <fsummary>Reads symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Name = string()</v>
- <v>Handle = term()</v>
- <v>Timeout = timeout()</v>
- <v>FileInfo = record()</v>
+ <v>Target = string()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic
- link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like
- <c><![CDATA[file:read_link_info/2]]></c>.</p>
+ <p>Reads the link target from the symbolic link specified
+ by <c><![CDATA[name]]></c>, like
+ <seealso marker="kernel:file#read_link-1">file:read_link/1</seealso></p>
</desc>
</func>
- <func>
- <name>write_file_info(ChannelPid, Name, Info) -> </name>
- <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Write information for a file</fsummary>
+
+ <func>
+ <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, Reason}</name>
+ <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, Reason}</name>
+ <fsummary>Gets information about a symbolic link.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Name = string()</v>
- <v>Info = record()</v>
+ <v>Handle = term()</v>
<v>Timeout = timeout()</v>
+ <v>FileInfo = record()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the
- file specified by <c><![CDATA[Name]]></c>, like <c><![CDATA[file:write_file_info]]></c>.</p>
+ <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic
+ link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>, like
+ <seealso marker="kernel:file#read_link_info-2">file:read_link_info/2</seealso></p>
</desc>
</func>
+
<func>
- <name>read_link(ChannelPid, Name) -> </name>
- <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, Reason}</name>
- <fsummary>Read symbolic link</fsummary>
+ <name>rename(ChannelPid, OldName, NewName) -> </name>
+ <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Renames a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
- <v>Name = string()</v>
- <v>Target = string()</v>
+ <v>OldName = string()</v>
+ <v>NewName = string()</v>
+ <v>Timeout = timeout()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Reads the link target from the symbolic link specified
- by <c><![CDATA[name]]></c>, like <c><![CDATA[file:read_link/1]]></c>.</p>
+ <p>Renames a file named <c><![CDATA[OldName]]></c> and gives it the name
+ <c><![CDATA[NewName]]></c>, like
+ <seealso marker="kernel:file#rename-2">file:rename/2</seealso></p>
</desc>
</func>
+
<func>
- <name>make_symlink(ChannelPid, Name, Target) -> </name>
- <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Create symbolic link</fsummary>
+ <name>start_channel(ConnectionRef) -></name>
+ <name>start_channel(ConnectionRef, Options) -></name>
+ <name>start_channel(Host, Options) -></name>
+ <name>start_channel(Host, Port, Options) -> {ok, Pid} | {ok, Pid, ConnectionRef} |
+ {error, Reason}</name>
+ <fsummary>Starts an SFTP client.</fsummary>
<type>
- <v>ChannelPid = pid()</v>
- <v>Name = string()</v>
- <v>Target = string()</v>
+ <v>Host = string()</v>
+ <v>ConnectionRef = ssh_connection_ref()</v>
+ <v>Port = integer()</v>
+ <v>Options = [{Option, Value}]</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the
- name <c><![CDATA[Name]]></c>, like <c><![CDATA[file:make_symlink/2]]></c>.</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 <c>pid</c> for this process is to be used as input to
+ all other API functions in this module.</p>
+
+ <p>Options:</p>
+ <taglist>
+ <tag><c><![CDATA[{timeout, timeout()}]]></c></tag>
+ <item>
+ <p>The time-out is passed to the <c>ssh_channel</c> start function,
+ and defaults to <c>infinity</c>.</p>
+ </item>
+ <tag>
+ <c><![CDATA[{sftp_vsn, integer()}]]></c>
+ </tag>
+ <item>
+ <p>
+ Desired SFTP protocol version.
+ The actual version is the minimum of
+ the desired version and the maximum supported
+ versions by the SFTP server.
+ </p>
+ </item>
+ </taglist>
+ <p>All other options are directly passed to
+ <seealso marker="ssh">ssh:connect/3</seealso> or ignored if a
+ connection is already provided.</p>
</desc>
</func>
- <func>
- <name>rename(ChannelPid, OldName, NewName) -> </name>
- <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Rename a file</fsummary>
+
+ <func>
+ <name>stop_channel(ChannelPid) -> ok</name>
+ <fsummary>Stops the SFTP client channel.</fsummary>
<type>
<v>ChannelPid = pid()</v>
- <v>OldName = string()</v>
- <v>NewName = string()</v>
- <v>Timeout = timeout()</v>
- <v>Reason = term()</v>
</type>
<desc>
- <p>Renames a file named <c><![CDATA[OldName]]></c>, and gives it the name
- <c><![CDATA[NewName]]></c>, like <c><![CDATA[file:rename/2]]></c></p>
+ <p>Stops an SFTP channel. Does not close the SSH connection.
+ Use <seealso marker="ssh#close-1">ssh:close/1</seealso> to close it.</p>
</desc>
</func>
+
<func>
- <name>delete(ChannelPid, Name) -> </name>
- <name>delete(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Delete a file</fsummary>
+ <name>write(ChannelPid, Handle, Data) -></name>
+ <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, Error}</name>
+ <fsummary>Writes to an open file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
- <v>Name = string()</v>
+ <v>Handle = term()</v>
+ <v>Position = integer()</v>
+ <v>Data = iolist()</v>
<v>Timeout = timeout()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Deletes the file specified by <c><![CDATA[Name]]></c>, like
- <c><![CDATA[file:delete/1]]></c></p>
+ <p>Writes <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>.
+ The file is to be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c>
+ flag. Returns <c><![CDATA[ok]]></c> if successful or <c><![CDATA[{error, Reason}]]></c>
+ otherwise.</p>
+ <p>Typical error reasons:</p>
+ <taglist>
+ <tag><c><![CDATA[ebadf]]></c></tag>
+ <item>
+ <p>File is not opened for writing.</p>
+ </item>
+ <tag><c><![CDATA[enospc]]></c></tag>
+ <item>
+ <p>No space is left on the device.</p>
+ </item>
+ </taglist>
</desc>
</func>
+
<func>
- <name>make_dir(ChannelPid, Name) -> </name>
- <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Create a directory</fsummary>
+ <name>write_file(ChannelPid, File, Iolist) -></name>
+ <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Writes a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
- <v>Name = string()</v>
+ <v>File = string()</v>
+ <v>Iolist = iolist()</v>
<v>Timeout = timeout()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> should
- be a full path to a new directory. The directory can only be
- created in an existing directory.</p>
+ <p>Writes a file to the server, like <seealso
+ marker="kernel:file#write_file-2">file:write_file/2</seealso> The
+ file is created if it does not exist. The file is overwritten
+ if it exists.</p>
</desc>
</func>
+
<func>
- <name>del_dir(ChannelPid, Name) -> </name>
- <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Delete an empty directory</fsummary>
+ <name>write_file_info(ChannelPid, Name, Info) -></name>
+ <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Writes information for a file.</fsummary>
<type>
<v>ChannelPid = pid()</v>
<v>Name = string()</v>
+ <v>Info = record()</v>
<v>Timeout = timeout()</v>
<v>Reason = term()</v>
</type>
<desc>
- <p>Deletes a directory specified by <c><![CDATA[Name]]></c>.
- Note that the directory must be empty before it can be successfully deleted
- </p>
+ <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the
+ file specified by <c><![CDATA[Name]]></c>, like
+ <seealso marker="kernel:file#write_file_info-2">file:write_file_info/[2,3]</seealso></p>
</desc>
</func>
-
</funcs>
-
+
</erlref>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index 81c2acc575..8b2497e6a3 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -22,67 +22,73 @@
</legalnotice>
<title>ssh_sftpd</title>
+ <prepared></prepared>
+ <docno></docno>
<date>2005-09-22</date>
+ <rev></rev>
<file>ssh_sftpd.sgml</file>
</header>
<module>ssh_sftpd</module>
- <modulesummary>Specifies the channel process to handle an 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>
+ <p>Specifies a channel process to handle an SFTP subsystem.</p>
</description>
<section>
- <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
- subsystem using the ssh_channel behavior see
- <seealso marker="ssh_channel">ssh_channel(3)</seealso></p>
- <p><c> channel_init_args() = list() - The one given as argument to function
- subsystem_spec/1.</c></p>
+ <title>DATA TYPES</title>
+ <taglist>
+ <tag><c>subsystem_spec() =</c></tag>
+ <item><p><c>{subsystem_name(), {channel_callback(), channel_init_args()}}</c></p></item>
+ <tag><c>subsystem_name() =</c></tag>
+ <item><p><c>"sftp"</c></p></item>
+ <tag><c>channel_callback() =</c></tag>
+ <item><p><c>atom()</c> - Name of the Erlang module implementing the subsystem using the
+ <c>ssh_channel</c> behavior, see the
+ <seealso marker="ssh_channel">ssh_channel(3)</seealso> manual page.</p></item>
+ <tag><c>channel_init_args() =</c></tag>
+ <item><p><c>list()</c> - The one given as argument to function <c>subsystem_spec/1</c>.</p></item>
+ </taglist>
</section>
<funcs>
<func>
<name>subsystem_spec(Options) -> subsystem_spec()</name>
- <fsummary>Returns the subsystem specification that allows an ssh daemon to handle the subsystem "sftp".</fsummary>
+ <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary>
<type>
<v>Options = [{Option, Value}]</v>
</type>
<desc>
- <p>Should be used together with ssh:daemon/[1,2,3]</p>
- <p>Options are:</p>
+ <p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p>
+ <p>Options:</p>
<taglist>
<tag><c><![CDATA[{cwd, String}]]></c></tag>
<item>
- <p>Sets the initial current working directory for the
- server.</p>
+ <p>Sets the initial current working directory for the server.</p>
</item>
<tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag>
<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="stdlib:filelib">filelib</seealso> API:s to access the standard OTP file
- server. This option may be used to plug in
+ the file server. The default value is <c>ssh_sftpd_file</c>, which uses the
+ <seealso marker="kernel:file">file</seealso> and <seealso marker="stdlib:filelib">filelib</seealso>
+ APIs to access the standard OTP file server. This option can 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>
+ 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>.
+ <p>Sets the SFTP root directory. Then the user cannot see any files
+ above this root. If, for example, the root directory is set to <c>/tmp</c>,
+ then the user sees this directory as <c>/</c>. If the user then writes
+ <c>cd /etc</c>, the user moves to <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
+ <p>Sets the SFTP version to use. Defaults to 5. Version 6 is under
development and limited.</p>
</item>
</taglist>
diff --git a/lib/ssh/doc/src/usersguide.xml b/lib/ssh/doc/src/usersguide.xml
index 8ab14c2945..a9ed5fe21e 100644
--- a/lib/ssh/doc/src/usersguide.xml
+++ b/lib/ssh/doc/src/usersguide.xml
@@ -23,15 +23,16 @@
<title>SSH User's Guide</title>
<prepared>OTP Team</prepared>
+ <docno></docno>
<date>2012-10-11</date>
+ <rev></rev>
<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>The Erlang Secure Shell (SSH) application, <c>ssh</c>, implements the SSH Transport Layer Protocol and
+ provides SSH File Transfer Protocol (SFTP) clients and servers.
</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
index 46178d4018..cd7b64ac43 100644
--- a/lib/ssh/doc/src/using_ssh.xml
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -22,64 +22,70 @@
</legalnotice>
- <title>Getting started</title>
+ <title>Getting Started</title>
+ <prepared></prepared>
+ <docno></docno>
+ <approved></approved>
+ <date></date>
+ <rev></rev>
<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-0"> 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
- examples 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.
+ <title>General Information</title>
+ <p>The following examples use the utility function
+ <seealso marker="ssh#start-0"> ssh:start/0</seealso> to start
+ all needed applications (<c>crypto</c>, <c>public_key</c>, and <c>ssh</c>).
+ All examples are run in an Erlang shell, or in a bash shell, using <em>openssh</em>
+ to illustrate how the <c>ssh</c> application can be used. The
+ examples are run as the user <c>otptest</c> on a local network where the
+ user is authorized to log in over <c>ssh</c> to the host <em>tarlop</em>.
+ </p>
+ <p>If nothing else is stated, it is presumed that the <c>otptest</c> user
+ has an entry in the <em>authorized_keys</em> file of <em>tarlop</em>
+ (allowed to log in over <c>ssh</c> without entering a password).
+ Also, <em>tarlop</em> is a known host in the <c>known_hosts</c>
+ file of the user <c>otptest</c>. This means that host-verification
+ can be done without user-interaction.
</p>
</section>
<section>
- <title>Using the Erlang SSH Terminal Client</title>
+ <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>
+ <p>The user <c>otptest</c>, which has bash as default shell, uses the
+ <c>ssh:shell/1</c> client to connect to the <em>openssh</em> daemon running on a
+ host called <em>tarlop</em>:</p>
<code type="erl" >
1> ssh:start().
ok
2> {ok, S} = ssh:shell("tarlop").
- >pwd
+ otptest@tarlop:> pwd
/home/otptest
- >exit
+ otptest@tarlop:> exit
logout
3>
</code>
</section>
<section>
- <title>Running an Erlang SSH Daemon </title>
+ <marker id="Running an Erlang ssh Daemon"></marker>
+ <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
+ <p>The <c>system_dir</c> option must be a directory containing a host
+ key file and it defaults to <c>/etc/ssh</c>. 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>Normally, the <c>/etc/ssh</c> directory is only readable by root.</p>
</note>
- <p> The option user_dir defaults to the users ~/.ssh directory</p>
+ <p>The option <c>user_dir</c> defaults to directory <c>users ~/.ssh</c>.</p>
- <p>In the following example we generate new keys and host keys as
- to be able to run the example without having root privileges</p>
+ <p><em>Step 1.</em> To run the example without root privileges,
+ generate new keys and host keys:</p>
<code>
$bash> ssh-keygen -t rsa -f /tmp/ssh_daemon/ssh_host_rsa_key
@@ -88,19 +94,22 @@
[...]
</code>
- <p>Create the file /tmp/otptest_user/.ssh/authorized_keys and add the content
- of /tmp/otptest_user/.ssh/id_rsa.pub Now we can do</p>
+ <p><em>Step 2.</em> Create the file <c>/tmp/otptest_user/.ssh/authorized_keys</c>
+ and add the content of <c>/tmp/otptest_user/.ssh/id_rsa.pub</c>.</p>
+
+ <p><em>Step 3.</em> Start the Erlang <c>ssh</c> daemon:</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"}]).
+ 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>
+ <p><em>Step 4.</em> Use the <em>openssh</em> client from a shell to connect
+ to the Erlang <c>ssh</c> daemon:</p>
<code>
$bash> ssh tarlop -p 8989 -i /tmp/otptest_user/.ssh/id_rsa\
@@ -113,9 +122,12 @@
1>
</code>
- <p>There are two ways of shutting down an SSH daemon</p>
+ <p>There are two ways of shutting down an <c>ssh</c> daemon,
+ see <em>Step 5a</em> and <em>Step 5b</em>.</p>
- <p>1: Stops the listener, but leaves existing connections started by the listener up and running.</p>
+ <p><em>Step 5a.</em> Shut down the Erlang <c>ssh</c> daemon so that it
+ stops the listener but leaves existing connections, started by the listener,
+ operational:</p>
<code type="erl">
3> ssh:stop_listener(Sshd).
@@ -123,7 +135,8 @@
4>
</code>
- <p>2: Stops the listener and all connections started by the listener.</p>
+ <p><em>Step 5b.</em> Shut down the Erlang <c>ssh</c> daemon so that it
+ stops the listener and all connections started by the listener:</p>
<code type="erl">
3> ssh:stop_daemon(Sshd)
@@ -134,17 +147,18 @@
</section>
<section>
- <title>One Time Execution</title>
+ <title>One-Time Execution</title>
- <p>In the following example the Erlang shell is the client process
- that receives the channel replies. </p>
+ <p>In the following example, the Erlang shell is the client process
+ that receives the channel replies.</p>
- <note><p> 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>The number of received messages in this example depends on which OS
+ and which shell that is used on the machine running the <c>ssh</c> daemon.
+ See also <seealso marker="ssh_connection#exec-4">ssh_connection:exec/4</seealso>.
</p></note>
+ <p>Do a one-time execution of a remote command over <c>ssh</c>:</p>
+
<code type="erl" >
1> ssh:start().
ok
@@ -162,7 +176,8 @@
6>
</code>
- <p>Note only the channel is closed the connection is still up and can handle other channels</p>
+ <p>Notice that 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).
@@ -172,19 +187,22 @@
</section>
<section>
- <title>SFTP (SSH File Transport Protocol) server</title>
+ <title>SFTP Server</title>
+
+ <p>Start the Erlang <c>ssh</c> daemon with the SFTP subsystem:</p>
<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"}])]}]).
+ 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>
+ <p>Run the OpenSSH SFTP client:</p>
<code type="erl">
$bash> sftp -oPort=8989 -o IdentityFile=/tmp/otptest_user/.ssh/id_rsa\
@@ -197,7 +215,9 @@
</section>
<section>
- <title>SFTP (SSH File Transport Protocol) client</title>
+ <title>SFTP Client</title>
+
+ <p>Fetch a file with the Erlang SFTP client:</p>
<code type="erl" >
1> ssh:start().
@@ -210,10 +230,77 @@
</section>
<section>
- <title>Creating a subsystem</title>
+ <title>SFTP Client with TAR Compression and Encryption</title>
+
+ <p>Example of writing and then reading a tar file follows:</p>
+ <code type="erlang">
+ {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]),
+ ok = erl_tar:add(HandleWrite, .... ),
+ ok = erl_tar:add(HandleWrite, .... ),
+ ...
+ ok = erl_tar:add(HandleWrite, .... ),
+ ok = erl_tar:close(HandleWrite),
+
+ %% And for reading
+ {ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read]),
+ {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
+ ok = erl_tar:close(HandleRead),
+ </code>
+
+ <p>The previous write and read example can be extended with encryption and decryption as follows:</p>
+ <code type="erlang">
+%% First three parameters depending on which crypto type we select:
+Key = &lt;&lt;"This is a 256 bit key. abcdefghi">>,
+Ivec0 = crypto:rand_bytes(16),
+DataSize = 1024, % DataSize rem 16 = 0 for aes_cbc
+
+%% Initialization of the CryptoState, in this case it is the Ivector.
+InitFun = fun() -> {ok, Ivec0, DataSize} end,
+
+%% How to encrypt:
+EncryptFun =
+ fun(PlainBin,Ivec) ->
+ EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec, PlainBin),
+ {ok, EncryptedBin, crypto:next_iv(aes_cbc,EncryptedBin)}
+ end,
+
+%% What to do with the very last block:
+CloseFun =
+ fun(PlainBin, Ivec) ->
+ EncryptedBin = crypto:block_encrypt(aes_cbc256, Key, Ivec,
+ pad(16,PlainBin) %% Last chunk
+ ),
+ {ok, EncryptedBin}
+ end,
+
+Cw = {InitFun,EncryptFun,CloseFun},
+{ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write,{crypto,Cw}]),
+ok = erl_tar:add(HandleWrite, .... ),
+ok = erl_tar:add(HandleWrite, .... ),
+...
+ok = erl_tar:add(HandleWrite, .... ),
+ok = erl_tar:close(HandleWrite),
+
+%% And for decryption (in this crypto example we could use the same InitFun
+%% as for encryption):
+DecryptFun =
+ fun(EncryptedBin,Ivec) ->
+ PlainBin = crypto:block_decrypt(aes_cbc256, Key, Ivec, EncryptedBin),
+ {ok, PlainBin, crypto:next_iv(aes_cbc,EncryptedBin)}
+ end,
+
+Cr = {InitFun,DecryptFun},
+{ok,HandleRead} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [read,{crypto,Cw}]),
+{ok,NameValueList} = erl_tar:extract(HandleRead,[memory]),
+ok = erl_tar:close(HandleRead),
+ </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>
+ <p>A small <c>ssh</c> subsystem that echoes N bytes can be implemented as shown
+ in the following example:</p>
<code type="erl" >
-module(ssh_echo_server).
@@ -267,14 +354,16 @@ terminate(_Reason, _State) ->
ok.
</code>
- <p>And run like this on the host tarlop with the keys generated in section 3.3</p>
+ <p>The subsystem can be run on the host <em>tarlop</em> with the generated keys,
+ as described in Section <seealso marker="#Running an Erlang ssh Daemon">
+ Running an Erlang ssh Daemon</seealso>:</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]}}]}]).
+ 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>
@@ -293,6 +382,7 @@ terminate(_Reason, _State) ->
{ssh_msg, &lt;0.57.0>, {closed, 0}}
7> {error, closed} = ssh_connection:send(ConnectionRef, ChannelId, "10", infinity).
</code>
+<p>See also <seealso marker="ssh_channel"> ssh_channel(3)</seealso>.</p>
</section>
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 90d71107ad..a06d8acfd4 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -75,7 +75,7 @@ MODULES= \
ssh_transport \
ssh_xfer
-PUBLIC_HRL_FILES= ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
+HRL_FILES =
ERL_FILES= \
$(MODULES:%=%.erl) \
@@ -95,7 +95,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
-INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl
+INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
# ----------------------------------------------------
# FLAGS
@@ -140,7 +140,7 @@ release_spec: opt
$(INSTALL_DATA) $(BEHAVIOUR_TARGET_FILES) $(TARGET_FILES) $(APP_TARGET) \
$(APPUP_TARGET) "$(RELSYSDIR)/ebin"
$(INSTALL_DIR) "$(RELSYSDIR)/include"
- $(INSTALL_DATA) $(PUBLIC_HRL_FILES) "$(RELSYSDIR)/include"
+
release_docs_spec:
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 71e7d77475..86c042781c 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -24,11 +24,14 @@
-include("ssh.hrl").
-include("ssh_connect.hrl").
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/file.hrl").
-export([start/0, start/1, stop/0, connect/3, connect/4, close/1, connection_info/2,
channel_info/3,
daemon/1, daemon/2, daemon/3,
- stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2,
+ default_algorithms/0,
+ stop_listener/1, stop_listener/2, stop_listener/3,
+ stop_daemon/1, stop_daemon/2, stop_daemon/3,
shell/1, shell/2, shell/3]).
%%--------------------------------------------------------------------
@@ -157,7 +160,9 @@ daemon(HostAddr, Port, Options0) ->
stop_listener(SysSup) ->
ssh_system_sup:stop_listener(SysSup).
stop_listener(Address, Port) ->
- ssh_system_sup:stop_listener(Address, Port).
+ stop_listener(Address, Port, ?DEFAULT_PROFILE).
+stop_listener(Address, Port, Profile) ->
+ ssh_system_sup:stop_listener(Address, Port, Profile).
%%--------------------------------------------------------------------
-spec stop_daemon(pid()) -> ok.
@@ -169,8 +174,9 @@ stop_listener(Address, Port) ->
stop_daemon(SysSup) ->
ssh_system_sup:stop_system(SysSup).
stop_daemon(Address, Port) ->
- ssh_system_sup:stop_system(Address, Port).
-
+ ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE).
+stop_daemon(Address, Port, Profile) ->
+ ssh_system_sup:stop_system(Address, Port, Profile).
%%--------------------------------------------------------------------
-spec shell(string()) -> _.
-spec shell(string(), proplists:proplist()) -> _.
@@ -208,6 +214,11 @@ shell(Host, Port, Options) ->
end.
%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
+default_algorithms() ->
+ ssh_transport:default_algorithms().
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
fix_idle_time(SshOptions) ->
@@ -226,7 +237,8 @@ start_daemon(Host, Port, Options, Inet) ->
end.
do_start_daemon(Host, Port, Options, SocketOptions) ->
- case ssh_system_sup:system_supervisor(Host, Port) of
+ Profile = proplists:get_value(profile, Options, ?DEFAULT_PROFILE),
+ case ssh_system_sup:system_supervisor(Host, Port, Profile) of
undefined ->
%% It would proably make more sense to call the
%% address option host but that is a too big change at the
@@ -259,7 +271,7 @@ do_start_daemon(Host, Port, Options, SocketOptions) ->
end.
handle_options(Opts) ->
- try handle_option(proplists:unfold(Opts), [], []) of
+ try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of
{Inet, Ssh} ->
{handle_ip(Inet), Ssh}
catch
@@ -267,6 +279,35 @@ handle_options(Opts) ->
Error
end.
+
+algs_compatibility(Os) ->
+ %% Take care of old options 'public_key_alg' and 'pref_public_key_algs'
+ comp_pk(proplists:get_value(preferred_algorithms,Os),
+ proplists:get_value(pref_public_key_algs,Os),
+ proplists:get_value(public_key_alg, Os),
+ [{K,V} || {K,V} <- Os,
+ K =/= public_key_alg,
+ K =/= pref_public_key_algs]
+ ).
+
+comp_pk(undefined, undefined, undefined, Os) -> Os;
+comp_pk( PrefAlgs, _, _, Os) when PrefAlgs =/= undefined -> Os;
+
+comp_pk(undefined, undefined, ssh_dsa, Os) -> comp_pk(undefined, undefined, 'ssh-dss', Os);
+comp_pk(undefined, undefined, ssh_rsa, Os) -> comp_pk(undefined, undefined, 'ssh-rsa', Os);
+comp_pk(undefined, undefined, PK, Os) ->
+ PKs = [PK | ssh_transport:supported_algorithms(public_key)--[PK]],
+ [{preferred_algorithms, [{public_key,PKs}] } | Os];
+
+comp_pk(undefined, PrefPKs, _, Os) when PrefPKs =/= undefined ->
+ PKs = [case PK of
+ ssh_dsa -> 'ssh-dss';
+ ssh_rsa -> 'ssh-rsa';
+ _ -> PK
+ end || PK <- PrefPKs],
+ [{preferred_algorithms, [{public_key,PKs}]} | Os].
+
+
handle_option([], SocketOptions, SshOptions) ->
{SocketOptions, SshOptions};
handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) ->
@@ -279,8 +320,6 @@ handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptio
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{user_interaction, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{public_key_alg, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{connect_timeout, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{user, _} = Opt | Rest], SocketOptions, SshOptions) ->
@@ -297,10 +336,6 @@ handle_option([{pwdfun, _} = 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) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{compression, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
%%Backwards compatibility
handle_option([{allow_user_interaction, Value} | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option({user_interaction, Value}) | SshOptions]);
@@ -310,6 +345,8 @@ handle_option([{connectfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{unexpectedfun, _} = 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([{ssh_msg_debug_fun, _} = Opt | Rest], SocketOptions, SshOptions) ->
@@ -331,7 +368,9 @@ 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([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) ->
+ handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{preferred_algorithms,_} = 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]);
@@ -351,6 +390,8 @@ handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, S
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
handle_option([{id_string, _ID} = Opt|Rest], SocketOptions, SshOptions) ->
handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
+handle_option([{profile, _ID} = 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).
@@ -358,28 +399,17 @@ handle_option([Opt | Rest], SocketOptions, SshOptions) ->
handle_ssh_option({minimal_remote_max_packet_size, Value} = Opt) when is_integer(Value), Value >=0 ->
Opt;
handle_ssh_option({system_dir, Value} = Opt) when is_list(Value) ->
- Opt;
+ check_dir(Opt);
handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) ->
- Opt;
+ check_dir(Opt);
handle_ssh_option({user_dir_fun, Value} = Opt) when is_function(Value) ->
Opt;
handle_ssh_option({silently_accept_hosts, Value} = Opt) when is_boolean(Value) ->
Opt;
handle_ssh_option({user_interaction, Value} = Opt) when is_boolean(Value) ->
Opt;
-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, NewOpts} ->
- NewOpts;
- _ ->
- throw({error, {eoptions, Opt}})
- end;
+handle_ssh_option({preferred_algorithms,[_|_]} = Opt) ->
+ handle_pref_algs(Opt);
handle_ssh_option({connect_timeout, Value} = Opt) when is_integer(Value); Value == infinity ->
Opt;
handle_ssh_option({max_sessions, Value} = Opt) when is_integer(Value), Value>0 ->
@@ -411,11 +441,20 @@ handle_ssh_option({exec, Function} = Opt) when is_function(Function) ->
Opt;
handle_ssh_option({auth_methods, Value} = Opt) when is_list(Value) ->
Opt;
+handle_ssh_option({auth_method_kb_interactive_data, {Name,Instruction,Prompt,Echo}} = Opt) when is_list(Name),
+ is_list(Instruction),
+ is_list(Prompt),
+ is_boolean(Echo) ->
+ Opt;
+handle_ssh_option({auth_method_kb_interactive_data, F} = Opt) when is_function(F,3) ->
+ Opt;
handle_ssh_option({infofun, Value} = Opt) when is_function(Value) ->
Opt;
handle_ssh_option({connectfun, Value} = Opt) when is_function(Value) ->
Opt;
-handle_ssh_option({disconnectfun , Value} = Opt) when is_function(Value) ->
+handle_ssh_option({disconnectfun, Value} = Opt) when is_function(Value) ->
+ Opt;
+handle_ssh_option({unexpectedfun, Value} = Opt) when is_function(Value,2) ->
Opt;
handle_ssh_option({failfun, Value} = Opt) when is_function(Value) ->
Opt;
@@ -449,6 +488,8 @@ handle_ssh_option({id_string, random}) ->
{id_string, {random,2,5}}; %% 2 - 5 random characters
handle_ssh_option({id_string, ID} = Opt) when is_list(ID) ->
Opt;
+handle_ssh_option({profile, Value} = Opt) when is_atom(Value) ->
+ Opt;
handle_ssh_option(Opt) ->
throw({error, {eoptions, Opt}}).
@@ -465,23 +506,83 @@ 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
+
+handle_pref_algs({preferred_algorithms,Algs}) ->
+ try alg_duplicates(Algs, [], []) of
+ [] ->
+ {preferred_algorithms,
+ [try ssh_transport:supported_algorithms(Key)
+ of
+ DefAlgs -> handle_pref_alg(Key,Vals,DefAlgs)
+ catch
+ _:_ -> throw({error, {{eoptions, {preferred_algorithms,Key}},
+ "Bad preferred_algorithms key"}})
+ end || {Key,Vals} <- Algs]
+ };
+
+ Dups ->
+ throw({error, {{eoptions, {preferred_algorithms,Dups}}, "Duplicates found"}})
+ catch
+ _:_ ->
+ throw({error, {{eoptions, preferred_algorithms}, "Malformed"}})
end.
+alg_duplicates([{K,V}|KVs], Ks, Dups0) ->
+ Dups =
+ case lists:member(K,Ks) of
+ true ->
+ [K|Dups0];
+ false ->
+ Dups0
+ end,
+ case V--lists:usort(V) of
+ [] ->
+ alg_duplicates(KVs, [K|Ks], Dups);
+ Ds ->
+ alg_duplicates(KVs, [K|Ks], Dups++Ds)
+ end;
+alg_duplicates([], _Ks, Dups) ->
+ Dups.
+
+handle_pref_alg(Key,
+ Vs=[{client2server,C2Ss=[_|_]},{server2client,S2Cs=[_|_]}],
+ [{client2server,Sup_C2Ss},{server2client,Sup_S2Cs}]
+ ) ->
+ chk_alg_vs(Key, C2Ss, Sup_C2Ss),
+ chk_alg_vs(Key, S2Cs, Sup_S2Cs),
+ {Key, Vs};
+
+handle_pref_alg(Key,
+ Vs=[{server2client,[_|_]},{client2server,[_|_]}],
+ Sup=[{client2server,_},{server2client,_}]
+ ) ->
+ handle_pref_alg(Key, lists:reverse(Vs), Sup);
+
+handle_pref_alg(Key,
+ Vs=[V|_],
+ Sup=[{client2server,_},{server2client,_}]
+ ) when is_atom(V) ->
+ handle_pref_alg(Key, [{client2server,Vs},{server2client,Vs}], Sup);
+
+handle_pref_alg(Key,
+ Vs=[V|_],
+ Sup=[S|_]
+ ) when is_atom(V), is_atom(S) ->
+ chk_alg_vs(Key, Vs, Sup),
+ {Key, Vs};
+
+handle_pref_alg(Key, Vs, _) ->
+ throw({error, {{eoptions, {preferred_algorithms,[{Key,Vs}]}}, "Badly formed list"}}).
+
+chk_alg_vs(OptKey, Values, SupportedValues) ->
+ case (Values -- SupportedValues) of
+ [] -> Values;
+ Bad -> throw({error, {{eoptions, {OptKey,Bad}}, "Unsupported value(s) found"}})
+ end.
+
handle_ip(Inet) -> %% Default to ipv4
case lists:member(inet, Inet) of
true ->
@@ -494,4 +595,31 @@ handle_ip(Inet) -> %% Default to ipv4
[inet | Inet]
end
end.
-
+
+check_dir({_,Dir} = Opt) ->
+ case directory_exist_readable(Dir) of
+ ok ->
+ Opt;
+ {error,Error} ->
+ throw({error, {eoptions,{Opt,Error}}})
+ end.
+
+directory_exist_readable(Dir) ->
+ case file:read_file_info(Dir) of
+ {ok, #file_info{type = directory,
+ access = Access}} ->
+ case Access of
+ read -> ok;
+ read_write -> ok;
+ _ -> {error, eacces}
+ end;
+
+ {ok, #file_info{}}->
+ {error, enotdir};
+
+ {error, Error} ->
+ {error, Error}
+ end.
+
+
+
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 0c4d34f89c..a02c87505d 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -31,6 +31,7 @@
-define(SSH_LENGHT_INDICATOR_SIZE, 4).
-define(REKEY_TIMOUT, 3600000).
-define(REKEY_DATA_TIMOUT, 60000).
+-define(DEFAULT_PROFILE, default).
-define(FALSE, 0).
-define(TRUE, 1).
@@ -127,8 +128,10 @@
user,
service,
userauth_quiet_mode, % boolean()
- userauth_supported_methods , %
- userauth_methods,
+ userauth_supported_methods, % string() eg "keyboard-interactive,password"
+ userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"]
+ kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
+ kb_data,
userauth_preference,
available_host_keys,
authenticated = false
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 34988f17b6..6c431af270 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -21,6 +21,8 @@
-module(ssh_acceptor).
+-include("ssh.hrl").
+
%% Internal application API
-export([start_link/5,
number_of_connections/1]).
@@ -82,8 +84,10 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
end.
handle_connection(Callback, Address, Port, Options, Socket) ->
- SystemSup = ssh_system_sup:system_supervisor(Address, Port),
SSHopts = proplists:get_value(ssh_opts, Options, []),
+ Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE),
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile),
+
MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity),
case number_of_connections(SystemSup) < MaxSessions of
true ->
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 46fdef07d0..e101ce8b39 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -26,7 +26,9 @@
-module(ssh_acceptor_sup).
-behaviour(supervisor).
--export([start_link/1, start_child/2, stop_child/3]).
+-include("ssh.hrl").
+
+-export([start_link/1, start_child/2, stop_child/4]).
%% Supervisor callback
-export([init/1]).
@@ -45,14 +47,16 @@ start_child(AccSup, ServerOpts) ->
{error, already_present} ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- stop_child(AccSup, Address, Port),
+ Profile = proplists:get_value(profile,
+ proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ stop_child(AccSup, Address, Port, Profile),
supervisor:start_child(AccSup, Spec);
Reply ->
Reply
end.
-stop_child(AccSup, Address, Port) ->
- Name = id(Address, Port),
+stop_child(AccSup, Address, Port, Profile) ->
+ Name = id(Address, Port, Profile),
case supervisor:terminate_child(AccSup, Name) of
ok ->
supervisor:delete_child(AccSup, Name);
@@ -77,7 +81,8 @@ child_spec(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT),
- Name = id(Address, Port),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ Name = id(Address, Port, Profile),
SocketOpts = proplists:get_value(socket_opts, ServerOpts),
StartFunc = {ssh_acceptor, start_link, [Port, Address,
[{active, false},
@@ -89,6 +94,11 @@ child_spec(ServerOpts) ->
Type = worker,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-id(Address, Port) ->
- {ssh_acceptor_sup, Address, Port}.
+id(Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {ssh_acceptor_sup, any, Port, Profile};
+ false ->
+ {ssh_acceptor_sup, Address, Port, Profile}
+ end.
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 45c4d52d7e..020fb06530 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -30,7 +30,8 @@
-export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1,
service_request_msg/1, init_userauth_request_msg/1,
userauth_request_msg/1, handle_userauth_request/3,
- handle_userauth_info_request/3, handle_userauth_info_response/2
+ handle_userauth_info_request/3, handle_userauth_info_response/2,
+ default_public_key_algorithms/0
]).
%%--------------------------------------------------------------------
@@ -115,33 +116,16 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
service = "ssh-connection",
method = "none",
data = <<>>},
- 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),
- Prefs = method_preference(FirstAlg, SecondAlg),
- 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),
- Prefs = method_preference(FirstAlg, SecondAlg),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"});
- _ ->
- Prefs = method_preference(FirstAlg),
- ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
- userauth_preference = Prefs,
- userauth_methods = none,
- service = "ssh-connection"})
- end
- end;
+
+
+ Algs = proplists:get_value(public_key,
+ proplists:get_value(preferred_algorithms, Opts, []),
+ default_public_key_algorithms()),
+ Prefs = method_preference(Algs),
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ userauth_preference = Prefs,
+ userauth_methods = none,
+ service = "ssh-connection"});
{error, no_user} ->
ErrStr = "Could not determine the users name",
throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
@@ -185,7 +169,8 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "password",
data = <<?FALSE, ?UINT32(Sz), BinPwd:Sz/binary>>}, _,
- #ssh{opts = Opts} = Ssh) ->
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
Password = unicode:characters_to_list(BinPwd),
case check_password(User, Password, Opts) of
true ->
@@ -194,7 +179,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
false ->
{not_authorized, {User, {error,"Bad user or password"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = "",
+ authentications = Methods,
partial_success = false}, Ssh)}
end;
@@ -207,7 +192,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
%% ?UINT32(Sz2), NewBinPwd:Sz2/binary
>>
}, _,
- Ssh) ->
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
%% Password change without us having sent SSH_MSG_USERAUTH_PASSWD_CHANGEREQ (because we never do)
%% RFC 4252 says:
%% SSH_MSG_USERAUTH_FAILURE without partial success - The password
@@ -216,7 +201,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
{not_authorized, {User, {error,"Password change not supported"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = "",
+ authentications = Methods,
partial_success = false}, Ssh)};
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -232,7 +217,9 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "publickey",
data = Data},
- SessionId, #ssh{opts = Opts} = Ssh) ->
+ SessionId,
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
<<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary,
?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data,
Alg = binary_to_list(BAlg),
@@ -247,7 +234,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
false ->
{not_authorized, {User, undefined},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications="publickey,password",
+ authentications = Methods,
partial_success = false}, Ssh)}
end;
?FALSE ->
@@ -259,6 +246,65 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
+ method = "keyboard-interactive",
+ data = _},
+ _, #ssh{opts = Opts,
+ kb_tries_left = KbTriesLeft,
+ userauth_supported_methods = Methods} = Ssh) ->
+ case KbTriesLeft of
+ N when N<1 ->
+ {not_authorized, {User, {authmethod, "keyboard-interactive"}},
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh)};
+
+ _ ->
+ %% RFC4256
+ %% The data field contains:
+ %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip
+ %% it for simplicity.
+ %% - submethods. "... the user can give a hint of which actual methods
+ %% he wants to use. ...". It's a "MAY use" so we skip
+ %% it. It also needs an understanding between the client
+ %% and the server.
+ %%
+ %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS,
+ %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message."
+ Default = {"SSH server",
+ "Enter password for \""++User++"\"",
+ "password: ",
+ false},
+
+ {Name, Instruction, Prompt, Echo} =
+ case proplists:get_value(auth_method_kb_interactive_data, Opts) of
+ undefined ->
+ Default;
+ {_,_,_,_}=V ->
+ V;
+ F when is_function(F) ->
+ {_,PeerName} = Ssh#ssh.peer,
+ F(PeerName, User, "ssh-connection")
+ end,
+ EchoEnc = case Echo of
+ true -> <<?TRUE>>;
+ false -> <<?FALSE>>
+ end,
+ Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name),
+ instruction = unicode:characters_to_list(Instruction),
+ language_tag = "",
+ num_prompts = 1,
+ data = <<?STRING(unicode:characters_to_binary(Prompt)),
+ EchoEnc/binary
+ >>
+ },
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ kb_data = Msg
+ })}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
method = Other}, _,
#ssh{userauth_supported_methods = Methods} = Ssh) ->
{not_authorized, {User, {authmethod, Other}},
@@ -280,6 +326,42 @@ handle_userauth_info_request(
#ssh_msg_userauth_info_response{num_responses = NumPrompts,
data = Responses}, Ssh)}.
+handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
+ data = <<?UINT32(Sz), Password:Sz/binary>>},
+ #ssh{opts = Opts,
+ kb_tries_left = KbTriesLeft0,
+ kb_data = InfoMsg,
+ user = User,
+ userauth_supported_methods = Methods} = Ssh) ->
+ KbTriesLeft = KbTriesLeft0 - 1,
+ case check_password(User, unicode:characters_to_list(Password), Opts) of
+ true ->
+ {authorized, User,
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+ false when KbTriesLeft > 0 ->
+ UserAuthInfoMsg =
+ InfoMsg#ssh_msg_userauth_info_request{
+ name = "",
+ instruction =
+ lists:concat(
+ ["Bad user or password, try again. ",
+ integer_to_list(KbTriesLeft),
+ " tries left."])
+ },
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(UserAuthInfoMsg,
+ Ssh#ssh{kb_tries_left = KbTriesLeft})};
+
+ false ->
+ {not_authorized, {User, {error,"Bad user or password"}},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = Methods,
+ partial_success = false},
+ Ssh#ssh{kb_data = undefined,
+ kb_tries_left = 0}
+ )}
+ end;
+
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
@@ -287,20 +369,20 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{},
"keyboard-interactive",
language = "en"}).
+
+default_public_key_algorithms() -> ?PREFERRED_PK_ALGS.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-method_preference(Alg1, Alg2) ->
- [{"publickey", ?MODULE, publickey_msg, [Alg1]},
- {"publickey", ?MODULE, publickey_msg,[Alg2]},
- {"password", ?MODULE, password_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ].
-method_preference(Alg1) ->
- [{"publickey", ?MODULE, publickey_msg, [Alg1]},
- {"password", ?MODULE, password_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ].
+method_preference(Algs) ->
+ lists:foldr(fun(A, Acc) ->
+ [{"publickey", ?MODULE, publickey_msg, [A]} | Acc]
+ end,
+ [{"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ],
+ Algs).
user_name(Opts) ->
Env = case os:type() of
@@ -418,27 +500,17 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
language = "en"}})
end.
-other_alg('ssh-rsa') ->
- 'ssh-dss';
-other_alg('ssh-dss') ->
- 'ssh-rsa'.
decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), BinE:Len1/binary,
- ?UINT32(Len2), BinN:Len2/binary>>
+ ?UINT32(Len1), E:Len1/big-signed-integer-unit:8,
+ ?UINT32(Len2), N:Len2/big-signed-integer-unit:8>>
,"ssh-rsa") ->
- E = ssh_bits:erlint(Len1, BinE),
- N = ssh_bits:erlint(Len2, BinN),
{ok, #'RSAPublicKey'{publicExponent = E, modulus = N}};
decode_public_key_v2(<<?UINT32(Len0), _:Len0/binary,
- ?UINT32(Len1), BinP:Len1/binary,
- ?UINT32(Len2), BinQ:Len2/binary,
- ?UINT32(Len3), BinG:Len3/binary,
- ?UINT32(Len4), BinY:Len4/binary>>
+ ?UINT32(Len1), P:Len1/big-signed-integer-unit:8,
+ ?UINT32(Len2), Q:Len2/big-signed-integer-unit:8,
+ ?UINT32(Len3), G:Len3/big-signed-integer-unit:8,
+ ?UINT32(Len4), Y:Len4/big-signed-integer-unit:8>>
, "ssh-dss") ->
- P = ssh_bits:erlint(Len1, BinP),
- Q = ssh_bits:erlint(Len2, BinQ),
- G = ssh_bits:erlint(Len3, BinG),
- Y = ssh_bits:erlint(Len4, BinY),
{ok, {Y, #'Dss-Parms'{p = P, q = Q, g = G}}};
decode_public_key_v2(_, _) ->
diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl
index 6cd8e6bf14..764c9f4246 100644
--- a/lib/ssh/src/ssh_auth.hrl
+++ b/lib/ssh/src/ssh_auth.hrl
@@ -23,7 +23,7 @@
-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
--define(PREFERRED_PK_ALG, 'ssh-rsa').
+-define(PREFERRED_PK_ALGS, ['ssh-rsa','ssh-dss']).
-define(SSH_MSG_USERAUTH_REQUEST, 50).
-define(SSH_MSG_USERAUTH_FAILURE, 51).
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
index 8aaff93b9f..d5f8df6fe4 100644
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -26,7 +26,7 @@
-include("ssh.hrl").
-export([encode/2]).
--export([mpint/1, erlint/2, string/1, name_list/1]).
+-export([mpint/1, string/1, name_list/1]).
-export([random/1]).
-define(name_list(X),
@@ -145,11 +145,7 @@ enc(Xs, ['...'| []], _Offset) ->
enc([], [],_) ->
[].
-erlint(Len, BinInt) ->
- Sz = Len*8,
- <<Int:Sz/big-signed-integer>> = BinInt,
- Int.
-
+
%%
%% Create a binary with constant bytes
%%
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 18841e3d2d..de6d246403 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -98,7 +98,7 @@ handle_ssh_msg({ssh_cm, ConnectionHandler,
Pty = Pty0#ssh_pty{width = Width, height = Height,
pixel_width = PixWidth,
pixel_height = PixHeight},
- {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
+ {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty, undefined),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{pty = Pty, buf = NewBuf}};
@@ -188,7 +188,7 @@ handle_msg({Group, tty_geometry}, #state{group = Group,
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
cm = ConnectionHandler,
channel = ChannelId} = State) ->
- {Chars, NewBuf} = io_request(Req, Buf, Pty),
+ {Chars, NewBuf} = io_request(Req, Buf, Pty, Group),
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{buf = NewBuf}};
@@ -263,40 +263,49 @@ eval(Error) ->
%%% displaying device...
%%% We are *not* really unicode aware yet, we just filter away characters
%%% beyond the latin1 range. We however handle the unicode binaries...
-io_request({window_change, OldTty}, Buf, Tty) ->
+io_request({window_change, OldTty}, Buf, Tty, _Group) ->
window_change(Tty, OldTty, Buf);
-io_request({put_chars, Cs}, Buf, Tty) ->
+io_request({put_chars, Cs}, Buf, Tty, _Group) ->
put_chars(bin_to_list(Cs), Buf, Tty);
-io_request({put_chars, unicode, Cs}, Buf, Tty) ->
+io_request({put_chars, unicode, Cs}, Buf, Tty, _Group) ->
put_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
-io_request({insert_chars, Cs}, Buf, Tty) ->
+io_request({insert_chars, Cs}, Buf, Tty, _Group) ->
insert_chars(bin_to_list(Cs), Buf, Tty);
-io_request({insert_chars, unicode, Cs}, Buf, Tty) ->
+io_request({insert_chars, unicode, Cs}, Buf, Tty, _Group) ->
insert_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
-io_request({move_rel, N}, Buf, Tty) ->
+io_request({move_rel, N}, Buf, Tty, _Group) ->
move_rel(N, Buf, Tty);
-io_request({delete_chars,N}, Buf, Tty) ->
+io_request({delete_chars,N}, Buf, Tty, _Group) ->
delete_chars(N, Buf, Tty);
-io_request(beep, Buf, _Tty) ->
+io_request(beep, Buf, _Tty, _Group) ->
{[7], Buf};
%% New in R12
-io_request({get_geometry,columns},Buf,Tty) ->
+io_request({get_geometry,columns},Buf,Tty, _Group) ->
{ok, Tty#ssh_pty.width, Buf};
-io_request({get_geometry,rows},Buf,Tty) ->
+io_request({get_geometry,rows},Buf,Tty, _Group) ->
{ok, Tty#ssh_pty.height, Buf};
-io_request({requests,Rs}, Buf, Tty) ->
- io_requests(Rs, Buf, Tty, []);
-io_request(tty_geometry, Buf, Tty) ->
- io_requests([{move_rel, 0}, {put_chars, unicode, [10]}], Buf, Tty, []);
+io_request({requests,Rs}, Buf, Tty, Group) ->
+ io_requests(Rs, Buf, Tty, [], Group);
+io_request(tty_geometry, Buf, Tty, Group) ->
+ io_requests([{move_rel, 0}, {put_chars, unicode, [10]}],
+ Buf, Tty, [], Group);
%{[], Buf};
-io_request(_R, Buf, _Tty) ->
+
+%% New in 18
+io_request({put_chars_sync, Class, Cs, Reply}, Buf, Tty, Group) ->
+ %% We handle these asynchronous for now, if we need output guarantees
+ %% we have to handle these synchronously
+ Group ! {reply, Reply},
+ io_request({put_chars, Class, Cs}, Buf, Tty, Group);
+
+io_request(_R, Buf, _Tty, _Group) ->
{[], Buf}.
-io_requests([R|Rs], Buf, Tty, Acc) ->
- {Chars, NewBuf} = io_request(R, Buf, Tty),
- io_requests(Rs, NewBuf, Tty, [Acc|Chars]);
-io_requests([], Buf, _Tty, Acc) ->
+io_requests([R|Rs], Buf, Tty, Acc, Group) ->
+ {Chars, NewBuf} = io_request(R, Buf, Tty, Group),
+ io_requests(Rs, NewBuf, Tty, [Acc|Chars], Group);
+io_requests([], Buf, _Tty, Acc, _Group) ->
{Acc, Buf}.
%%% return commands for cursor navigation, assume everything is ansi
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 654b9d4bde..d532d41009 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -196,15 +196,16 @@ reply_request(_,false, _, _) ->
%%--------------------------------------------------------------------
ptty_alloc(ConnectionHandler, Channel, Options) ->
ptty_alloc(ConnectionHandler, Channel, Options, infinity).
-ptty_alloc(ConnectionHandler, Channel, Options, TimeOut) ->
+ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) ->
+ Options = backwards_compatible(Options0, []),
{Width, PixWidth} = pty_default_dimensions(width, Options),
- {Hight, PixHight} = pty_default_dimensions(hight, Options),
+ {Height, PixHeight} = pty_default_dimensions(height, Options),
pty_req(ConnectionHandler, Channel,
- proplists:get_value(term, Options, default_term()),
+ proplists:get_value(term, Options, os:getenv("TERM", ?DEFAULT_TERMINAL)),
proplists:get_value(width, Options, Width),
- proplists:get_value(hight, Options, Hight),
+ proplists:get_value(height, Options, Height),
proplists:get_value(pixel_widh, Options, PixWidth),
- proplists:get_value(pixel_hight, Options, PixHight),
+ proplists:get_value(pixel_height, Options, PixHeight),
proplists:get_value(pty_opts, Options, []), TimeOut
).
%%--------------------------------------------------------------------
@@ -1340,10 +1341,11 @@ decode_ip(Addr) when is_binary(Addr) ->
{ok,A} -> A
end.
-default_term() ->
- case os:getenv("TERM") of
- false ->
- ?DEFAULT_TERMINAL;
- Str when is_list(Str)->
- Str
- end.
+backwards_compatible([], Acc) ->
+ Acc;
+backwards_compatible([{hight, Value} | Rest], Acc) ->
+ backwards_compatible(Rest, [{height, Value} | Acc]);
+backwards_compatible([{pixel_hight, Value} | Rest], Acc) ->
+ backwards_compatible(Rest, [{height, Value} | Acc]);
+backwards_compatible([Value| Rest], Acc) ->
+ backwards_compatible(Rest, [ Value | Acc]).
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 0f6162db60..e6e5749e07 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -33,7 +33,7 @@
-include("ssh_transport.hrl").
-include("ssh_auth.hrl").
-include("ssh_connect.hrl").
-
+-compile(export_all).
-export([start_link/3]).
%% Internal application API
@@ -70,6 +70,8 @@
undecoded_packet_length, % integer()
key_exchange_init_msg, % #ssh_msg_kexinit{}
renegotiate = false, % boolean()
+ last_size_rekey = 0,
+ event_queue = [],
connection_queue,
address,
port,
@@ -82,6 +84,11 @@
{next_state, state_name(), term(), timeout()} |
{stop, term(), term()}.
+-type gen_fsm_sync_return() :: {next_state, state_name(), term()} |
+ {next_state, state_name(), term(), timeout()} |
+ {reply, term(), state_name(), term()} |
+ {stop, term(), term(), term()}.
+
%%====================================================================
%% Internal application API
%%====================================================================
@@ -326,22 +333,25 @@ info(ConnectionHandler, ChannelProcess) ->
hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) ->
VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)),
send_msg(VsnMsg, State),
- {ok, [{recbuf, Size}]} = inet:getopts(Socket, [recbuf]),
- inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]),
- {next_state, hello, State#state{recbuf = Size}};
+ case getopt(recbuf, Socket) of
+ {ok, Size} ->
+ inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]),
+ {next_state, hello, State#state{recbuf = Size}};
+ {error, Reason} ->
+ {stop, {shutdown, Reason}, State}
+ end;
hello({info_line, _Line},#state{role = client, socket = Socket} = State) ->
%% The server may send info lines before the version_exchange
inet:setopts(Socket, [{active, once}]),
{next_state, hello, State};
-hello({info_line, _Line},#state{role = server} = State) ->
- DisconnectMsg =
- #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Did not receive expected protocol version exchange",
- language = "en"},
- handle_disconnect(DisconnectMsg, State);
+hello({info_line, _Line},#state{role = server,
+ socket = Socket,
+ transport_cb = Transport } = State) ->
+ %% as openssh
+ Transport:send(Socket, "Protocol mismatch."),
+ {stop, {shutdown,"Protocol mismatch in version exchange."}, State};
hello({version_exchange, Version}, #state{ssh_params = Ssh0,
socket = Socket,
@@ -432,9 +442,7 @@ key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg,
new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) ->
{ok, Ssh} = ssh_transport:handle_new_keys(Msg, Ssh0),
- {NextStateName, State} =
- after_new_keys(State0#state{ssh_params = Ssh}),
- {next_state, NextStateName, next_packet(State)}.
+ after_new_keys(next_packet(State0#state{ssh_params = Ssh})).
%%--------------------------------------------------------------------
-spec userauth(#ssh_msg_service_request{} | #ssh_msg_service_accept{} |
@@ -475,17 +483,22 @@ userauth(#ssh_msg_userauth_request{service = "ssh-connection",
service = "ssh-connection",
peer = {_, Address}} = Ssh0,
opts = Opts, starter = Pid} = State) ->
- case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
- {authorized, User, {Reply, Ssh}} ->
- send_msg(Reply, State),
- Pid ! ssh_connected,
- connected_fun(User, Address, Method, Opts),
- {next_state, connected,
- next_packet(State#state{auth_user = User, ssh_params = Ssh})};
- {not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Address, Reason, Opts),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ case lists:member(Method, Ssh0#ssh.userauth_methods) of
+ true ->
+ case ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
+ {authorized, User, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ Pid ! ssh_connected,
+ connected_fun(User, Address, Method, Opts),
+ {next_state, connected,
+ next_packet(State#state{auth_user = User, ssh_params = Ssh})};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Address, Reason, Opts),
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ end;
+ false ->
+ userauth(Msg#ssh_msg_userauth_request{method="none"}, State)
end;
userauth(#ssh_msg_userauth_info_request{} = Msg,
@@ -496,10 +509,21 @@ userauth(#ssh_msg_userauth_info_request{} = Msg,
{next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
userauth(#ssh_msg_userauth_info_response{} = Msg,
- #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response(Msg, Ssh0),
- send_msg(Reply, State),
- {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
+ #state{ssh_params = #ssh{role = server,
+ peer = {_, Address}} = Ssh0,
+ opts = Opts, starter = Pid} = State) ->
+ case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of
+ {authorized, User, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ Pid ! ssh_connected,
+ connected_fun(User, Address, "keyboard-interactive", Opts),
+ {next_state, connected,
+ next_packet(State#state{auth_user = User, ssh_params = Ssh})};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Address, Reason, Opts),
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ end;
userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh,
starter = Pid} = State) ->
@@ -558,11 +582,13 @@ userauth(#ssh_msg_userauth_banner{message = Msg},
-spec connected({#ssh_msg_kexinit{}, binary()}, %%| %% #ssh_msg_kexdh_init{},
#state{}) -> gen_fsm_state_return().
%%--------------------------------------------------------------------
-connected({#ssh_msg_kexinit{}, _Payload} = Event, State) ->
- kexinit(Event, State#state{renegotiate = true}).
-%% ;
-%% connected(#ssh_msg_kexdh_init{} = Event, State) ->
-%% key_exchange(Event, State#state{renegotiate = true}).
+connected({#ssh_msg_kexinit{}, _Payload} = Event, #state{ssh_params = Ssh0} = State0) ->
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
+ State = State0#state{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg,
+ renegotiate = true},
+ send_msg(SshPacket, State),
+ kexinit(Event, State).
%%--------------------------------------------------------------------
-spec handle_event(#ssh_msg_disconnect{} | #ssh_msg_ignore{} | #ssh_msg_debug{} |
@@ -591,33 +617,6 @@ handle_event(#ssh_msg_debug{always_display = Display, message = DbgMsg, language
handle_event(#ssh_msg_unimplemented{}, StateName, State) ->
{next_state, StateName, next_packet(State)};
-handle_event({adjust_window, ChannelId, Bytes}, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State =
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{recv_window_size = WinSize, remote_id = Id} = Channel ->
- ssh_channel:cache_update(Cache, Channel#channel{recv_window_size =
- WinSize + Bytes}),
- Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes),
- send_replies([{connection_reply, Msg}], State0);
- undefined ->
- State0
- end,
- {next_state, StateName, next_packet(State)};
-
-handle_event({reply_request, success, ChannelId}, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State0) ->
- State = case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{remote_id = RemoteId} ->
- Msg = ssh_connection:channel_success_msg(RemoteId),
- send_replies([{connection_reply, Msg}], State0);
- undefined ->
- State0
- end,
- {next_state, StateName, State};
-
handle_event(renegotiate, connected, #state{ssh_params = Ssh0}
= State) ->
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0),
@@ -629,13 +628,13 @@ handle_event(renegotiate, connected, #state{ssh_params = Ssh0}
renegotiate = true})};
handle_event(renegotiate, StateName, State) ->
- timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]),
- %% Allready in keyexcahange so ignore
+ %% Already in key-exchange so safe to ignore
{next_state, StateName, State};
%% Rekey due to sent data limit reached?
handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) ->
- {ok, [{send_oct,Sent}]} = inet:getstat(State#state.socket, [send_oct]),
+ {ok, [{send_oct,Sent0}]} = inet:getstat(State#state.socket, [send_oct]),
+ Sent = Sent0 - State#state.last_size_rekey,
MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000),
timer:apply_after(?REKEY_DATA_TIMOUT, gen_fsm, send_all_state_event, [self(), data_size]),
case Sent >= MaxSent of
@@ -645,11 +644,44 @@ handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) ->
{next_state, kexinit,
next_packet(State#state{ssh_params = Ssh,
key_exchange_init_msg = KeyInitMsg,
- renegotiate = true})};
+ renegotiate = true,
+ last_size_rekey = Sent0})};
_ ->
{next_state, connected, next_packet(State)}
end;
handle_event(data_size, StateName, State) ->
+ %% Already in key-exchange so safe to ignore
+ {next_state, StateName, State};
+
+handle_event(Event, StateName, State) when StateName /= connected ->
+ Events = [{event, Event} | State#state.event_queue],
+ {next_state, StateName, State#state{event_queue = Events}};
+
+handle_event({adjust_window, ChannelId, Bytes}, StateName,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ State =
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{recv_window_size = WinSize, remote_id = Id} = Channel ->
+ ssh_channel:cache_update(Cache, Channel#channel{recv_window_size =
+ WinSize + Bytes}),
+ Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes),
+ send_replies([{connection_reply, Msg}], State0);
+ undefined ->
+ State0
+ end,
+ {next_state, StateName, next_packet(State)};
+
+handle_event({reply_request, success, ChannelId}, StateName,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ State = case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = RemoteId} ->
+ Msg = ssh_connection:channel_success_msg(RemoteId),
+ send_replies([{connection_reply, Msg}], State0);
+ undefined ->
+ State0
+ end,
{next_state, StateName, State};
handle_event({request, ChannelPid, ChannelId, Type, Data}, StateName, State0) ->
@@ -680,8 +712,62 @@ handle_event({unknown, Data}, StateName, State) ->
sockname]} | {channel_info, channel_id(), [recv_window |
send_window]} |
{close, channel_id()} | stop, term(), state_name(), #state{})
- -> gen_fsm_state_return().
+ -> gen_fsm_sync_return().
%%--------------------------------------------------------------------
+handle_sync_event(get_print_info, _From, StateName, State) ->
+ Reply =
+ try
+ {inet:sockname(State#state.socket),
+ inet:peername(State#state.socket)
+ }
+ of
+ {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])};
+ _ -> {{"-",0},"-"}
+ catch
+ _:_ -> {{"?",0},"?"}
+ end,
+ {reply, Reply, StateName, State};
+
+handle_sync_event({connection_info, Options}, _From, StateName, State) ->
+ Info = ssh_info(Options, State, []),
+ {reply, Info, StateName, State};
+
+handle_sync_event({channel_info, ChannelId, Options}, _From, StateName,
+ #state{connection_state = #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{} = Channel ->
+ Info = ssh_channel_info(Options, Channel, []),
+ {reply, Info, StateName, State};
+ undefined ->
+ {reply, [], StateName, State}
+ end;
+
+handle_sync_event({info, ChannelPid}, _From, StateName,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ Result = ssh_channel:cache_foldl(
+ fun(Channel, Acc) when ChannelPid == all;
+ Channel#channel.user == ChannelPid ->
+ [Channel | Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Cache),
+ {reply, {ok, Result}, StateName, State};
+
+handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0,
+ role = Role} = State0) ->
+ {disconnect, _Reason, {{replies, Replies}, Connection}} =
+ ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "User closed down connection",
+ language = "en"}, Connection0, Role),
+ State = send_replies(Replies, State0),
+ {stop, normal, ok, State#state{connection_state = Connection}};
+
+
+handle_sync_event(Event, From, StateName, State) when StateName /= connected ->
+ Events = [{sync, Event, From} | State#state.event_queue],
+ {next_state, StateName, State#state{event_queue = Events}};
+
handle_sync_event({request, ChannelPid, ChannelId, Type, Data, Timeout}, From, StateName, State0) ->
{{replies, Replies}, State1} = handle_request(ChannelPid,
ChannelId, Type, Data,
@@ -784,46 +870,6 @@ handle_sync_event({recv_window, ChannelId}, _From, StateName,
end,
{reply, Reply, StateName, next_packet(State)};
-handle_sync_event(get_print_info, _From, StateName, State) ->
- Reply =
- try
- {inet:sockname(State#state.socket),
- inet:peername(State#state.socket)
- }
- of
- {{ok,Local}, {ok,Remote}} -> {{Local,Remote},io_lib:format("statename=~p",[StateName])};
- _ -> {{"-",0},"-"}
- catch
- _:_ -> {{"?",0},"?"}
- end,
- {reply, Reply, StateName, State};
-
-handle_sync_event({connection_info, Options}, _From, StateName, State) ->
- Info = ssh_info(Options, State, []),
- {reply, Info, StateName, State};
-
-handle_sync_event({channel_info, ChannelId, Options}, _From, StateName,
- #state{connection_state = #connection{channel_cache = Cache}} = State) ->
- case ssh_channel:cache_lookup(Cache, ChannelId) of
- #channel{} = Channel ->
- Info = ssh_channel_info(Options, Channel, []),
- {reply, Info, StateName, State};
- undefined ->
- {reply, [], StateName, State}
- end;
-
-handle_sync_event({info, ChannelPid}, _From, StateName,
- #state{connection_state =
- #connection{channel_cache = Cache}} = State) ->
- Result = ssh_channel:cache_foldl(
- fun(Channel, Acc) when ChannelPid == all;
- Channel#channel.user == ChannelPid ->
- [Channel | Acc];
- (_, Acc) ->
- Acc
- end, [], Cache),
- {reply, {ok, Result}, StateName, State};
-
handle_sync_event({close, ChannelId}, _, StateName,
#state{connection_state =
#connection{channel_cache = Cache}} = State0) ->
@@ -838,19 +884,7 @@ handle_sync_event({close, ChannelId}, _, StateName,
undefined ->
State0
end,
- {reply, ok, StateName, next_packet(State)};
-
-handle_sync_event(stop, _, _StateName, #state{connection_state = Connection0,
- role = Role,
- opts = Opts} = State0) ->
- {disconnect, Reason, {{replies, Replies}, Connection}} =
- ssh_connection:handle_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "User closed down connection",
- language = "en"}, Connection0, Role),
- State = send_replies(Replies, State0),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
- {stop, normal, ok, State#state{connection_state = Connection}}.
+ {reply, ok, StateName, next_packet(State)}.
%%--------------------------------------------------------------------
-spec handle_info({atom(), port(), binary()} | {atom(), port()} |
@@ -955,15 +989,38 @@ handle_info({check_cache, _ , _},
#connection{channel_cache = Cache}} = State) ->
{next_state, StateName, check_cache(State, Cache)};
-handle_info(UnexpectedMessage, StateName, #state{ssh_params = SshParams} = State) ->
- Msg = lists:flatten(io_lib:format(
- "Unexpected message '~p' received in state '~p'\n"
- "Role: ~p\n"
- "Peer: ~p\n"
- "Local Address: ~p\n", [UnexpectedMessage, StateName,
- SshParams#ssh.role, SshParams#ssh.peer,
- proplists:get_value(address, SshParams#ssh.opts)])),
- error_logger:info_report(Msg),
+handle_info(UnexpectedMessage, StateName, #state{opts = Opts,
+ ssh_params = SshParams} = State) ->
+ case unexpected_fun(UnexpectedMessage, Opts, SshParams) of
+ report ->
+ Msg = lists:flatten(
+ io_lib:format(
+ "Unexpected message '~p' received in state '~p'\n"
+ "Role: ~p\n"
+ "Peer: ~p\n"
+ "Local Address: ~p\n", [UnexpectedMessage, StateName,
+ SshParams#ssh.role, SshParams#ssh.peer,
+ proplists:get_value(address, SshParams#ssh.opts)])),
+ error_logger:info_report(Msg);
+
+ skip ->
+ ok;
+
+ Other ->
+ Msg = lists:flatten(
+ io_lib:format("Call to fun in 'unexpectedfun' failed:~n"
+ "Return: ~p\n"
+ "Message: ~p\n"
+ "Role: ~p\n"
+ "Peer: ~p\n"
+ "Local Address: ~p\n", [Other, UnexpectedMessage,
+ SshParams#ssh.role,
+ element(2,SshParams#ssh.peer),
+ proplists:get_value(address, SshParams#ssh.opts)]
+ )),
+
+ error_logger:error_report(Msg)
+ end,
{next_state, StateName, State}.
%%--------------------------------------------------------------------
@@ -1119,9 +1176,9 @@ init_ssh(client = Role, Vsn, Version, Options, Socket) ->
};
init_ssh(server = Role, Vsn, Version, Options, Socket) ->
-
AuthMethods = proplists:get_value(auth_methods, Options,
?SUPPORTED_AUTH_METHODS),
+ AuthMethodsAsList = string:tokens(AuthMethods, ","),
{ok, PeerAddr} = inet:peername(Socket),
KeyCb = proplists:get_value(key_cb, Options, ssh_file),
@@ -1132,60 +1189,46 @@ init_ssh(server = Role, Vsn, Version, Options, Socket) ->
io_cb = proplists:get_value(io_cb, Options, ssh_io),
opts = Options,
userauth_supported_methods = AuthMethods,
+ userauth_methods = AuthMethodsAsList,
+ kb_tries_left = 3,
peer = {undefined, PeerAddr},
available_host_keys = supported_host_keys(Role, KeyCb, Options)
}.
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
+ case proplists:get_value(public_key,
+ proplists:get_value(preferred_algorithms,Options,[])
+ ) of
+ undefined ->
+ ssh_auth:default_public_key_algorithms();
+ L ->
+ L -- (L--ssh_auth:default_public_key_algorithms())
end
+ of
+ [] ->
+ {stop, {shutdown, "No public key algs"}};
+ Algs ->
+ [atom_to_list(A) || A<-Algs]
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
- {error, _} ->
- Acc;
- Alg ->
- [Alg | Acc]
- end
- 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, _} ->
- Alg;
- Other ->
- Other
- end;
-available_host_key(KeyCb, "ssh-rsa" = Alg, Opts) ->
- case KeyCb:host_key('ssh-rsa', Opts) of
- {ok, _} ->
- Alg;
- Other ->
- Other
- end.
+ Algs=
+ [atom_to_list(A) || A <- proplists:get_value(public_key,
+ proplists:get_value(preferred_algorithms,Options,[]),
+ ssh_auth:default_public_key_algorithms()
+ ),
+ available_host_key(KeyCb, A, Options)
+ ],
+ Algs.
+
+
+%% Alg :: atom()
+available_host_key(KeyCb, Alg, Opts) ->
+ element(1, catch KeyCb:host_key(Alg, Opts)) == ok.
+
send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) ->
Transport:send(Socket, Msg).
@@ -1259,7 +1302,6 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName,
#state{
role = Role,
starter = User,
- opts = Opts,
renegotiate = Renegotiation,
connection_state = Connection0} = State0, EncData)
when Byte == ?SSH_MSG_GLOBAL_REQUEST;
@@ -1279,8 +1321,17 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName,
ConnectionMsg = ssh_message:decode(Msg),
State1 = generate_event_new_state(State0, EncData),
try ssh_connection:handle_msg(ConnectionMsg, Connection0, Role) of
- {{replies, Replies}, Connection} ->
- State = send_replies(Replies, State1#state{connection_state = Connection}),
+ {{replies, Replies0}, Connection} ->
+ if StateName == connected ->
+ Replies = Replies0,
+ State2 = State1;
+ true ->
+ {ConnReplies, Replies} =
+ lists:splitwith(fun not_connected_filter/1, Replies0),
+ Q = State1#state.event_queue ++ ConnReplies,
+ State2 = State1#state{ event_queue = Q }
+ end,
+ State = send_replies(Replies, State2#state{connection_state = Connection}),
{next_state, StateName, next_packet(State)};
{noreply, Connection} ->
{next_state, StateName, next_packet(State1#state{connection_state = Connection})};
@@ -1290,21 +1341,17 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName,
User ! {self(), not_connected, Reason},
{stop, {shutdown, normal},
next_packet(State#state{connection_state = Connection})};
- {disconnect, Reason, {{replies, Replies}, Connection}} ->
+ {disconnect, _Reason, {{replies, Replies}, Connection}} ->
State = send_replies(Replies, State1#state{connection_state = Connection}),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
{stop, {shutdown, normal}, State#state{connection_state = Connection}}
catch
_:Error ->
- {disconnect, Reason, {{replies, Replies}, Connection}} =
+ {disconnect, _Reason, {{replies, Replies}, Connection}} =
ssh_connection:handle_msg(
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
description = "Internal error",
language = "en"}, Connection0, Role),
State = send_replies(Replies, State1#state{connection_state = Connection}),
- SSHOpts = proplists:get_value(ssh_opts, Opts),
- disconnect_fun(Reason, SSHOpts),
{stop, {shutdown, Error}, State#state{connection_state = Connection}}
end;
@@ -1453,15 +1500,43 @@ next_packet(#state{socket = Socket} = State) ->
State.
after_new_keys(#state{renegotiate = true} = State) ->
- {connected, State#state{renegotiate = false}};
+ State1 = State#state{renegotiate = false, event_queue = []},
+ lists:foldr(fun after_new_keys_events/2, {next_state, connected, State1}, State#state.event_queue);
after_new_keys(#state{renegotiate = false,
ssh_params = #ssh{role = client} = Ssh0} = State) ->
{Msg, Ssh} = ssh_auth:service_request_msg(Ssh0),
send_msg(Msg, State),
- {userauth, State#state{ssh_params = Ssh}};
+ {next_state, userauth, State#state{ssh_params = Ssh}};
after_new_keys(#state{renegotiate = false,
ssh_params = #ssh{role = server}} = State) ->
- {userauth, State}.
+ {next_state, userauth, State}.
+
+after_new_keys_events({sync, _Event, From}, {stop, _Reason, _StateData}=Terminator) ->
+ gen_fsm:reply(From, {error, closed}),
+ Terminator;
+after_new_keys_events(_, {stop, _Reason, _StateData}=Terminator) ->
+ Terminator;
+after_new_keys_events({sync, Event, From}, {next_state, StateName, StateData}) ->
+ case handle_sync_event(Event, From, StateName, StateData) of
+ {reply, Reply, NextStateName, NewStateData} ->
+ gen_fsm:reply(From, Reply),
+ {next_state, NextStateName, NewStateData};
+ {next_state, NextStateName, NewStateData}->
+ {next_state, NextStateName, NewStateData};
+ {stop, Reason, Reply, NewStateData} ->
+ gen_fsm:reply(From, Reply),
+ {stop, Reason, NewStateData}
+ end;
+after_new_keys_events({event, Event}, {next_state, StateName, StateData}) ->
+ case handle_event(Event, StateName, StateData) of
+ {next_state, NextStateName, NewStateData}->
+ {next_state, NextStateName, NewStateData};
+ {stop, Reason, NewStateData} ->
+ {stop, Reason, NewStateData}
+ end;
+after_new_keys_events({connection_reply, _Data} = Reply, {StateName, State}) ->
+ NewState = send_replies([Reply], State),
+ {next_state, StateName, NewState}.
handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName,
State) ->
@@ -1523,12 +1598,14 @@ handle_disconnect(#ssh_msg_disconnect{} = DisconnectMsg, State, Error) ->
handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0, role = Role} = State0) ->
{disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
State = send_replies(disconnect_replies(Type, Msg, Replies), State0),
+ disconnect_fun(Desc, State#state.opts),
{stop, {shutdown, Desc}, State#state{connection_state = Connection}}.
handle_disconnect(Type, #ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0,
role = Role} = State0, ErrorMsg) ->
{disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
State = send_replies(disconnect_replies(Type, Msg, Replies), State0),
+ disconnect_fun(Desc, State#state.opts),
{stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}.
disconnect_replies(own, Msg, Replies) ->
@@ -1622,6 +1699,11 @@ log_error(Reason) ->
error_logger:error_report(Report),
"Internal error".
+not_connected_filter({connection_reply, _Data}) ->
+ true;
+not_connected_filter(_) ->
+ false.
+
send_replies([], State) ->
State;
send_replies([{connection_reply, Data} | Rest], #state{ssh_params = Ssh0} = State) ->
@@ -1642,6 +1724,8 @@ send_reply({flow_control, Cache, Channel, From, Msg}) ->
send_reply({flow_control, From, Msg}) ->
gen_fsm:reply(From, Msg).
+disconnect_fun({disconnect,Msg}, Opts) ->
+ disconnect_fun(Msg, Opts);
disconnect_fun(_, undefined) ->
ok;
disconnect_fun(Reason, Opts) ->
@@ -1652,6 +1736,15 @@ disconnect_fun(Reason, Opts) ->
catch Fun(Reason)
end.
+unexpected_fun(UnexpectedMessage, Opts, #ssh{peer={_,Peer}}) ->
+ case proplists:get_value(unexpectedfun, Opts) of
+ undefined ->
+ report;
+ Fun ->
+ catch Fun(UnexpectedMessage, Peer)
+ end.
+
+
check_cache(#state{opts = Opts} = State, Cache) ->
%% Check the number of entries in Cache
case proplists:get_value(size, ets:info(Cache)) of
@@ -1719,3 +1812,12 @@ start_timeout(_,_, infinity) ->
ok;
start_timeout(Channel, From, Time) ->
erlang:send_after(Time, self(), {timeout, {Channel, From}}).
+
+getopt(Opt, Socket) ->
+ case inet:getopts(Socket, [Opt]) of
+ {ok, [{Opt, Value}]} ->
+ {ok, Value};
+ Other ->
+ {error, {unexpected_getopts_return, Other}}
+ end.
+
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 30df32c4fd..fc8f564bc3 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -79,7 +79,7 @@ print_clients(D) ->
print_client(D, {undefined,Pid,supervisor,[ssh_connection_handler]}) ->
{{Local,Remote},_Str} = ssh_connection_handler:get_print_info(Pid),
- io:format(D, " Local=~s Remote=~s~n",[fmt_host_port(Local),fmt_host_port(Remote)]);
+ io:format(D, " Local=~s Remote=~s ConnectionRef=~p~n",[fmt_host_port(Local),fmt_host_port(Remote),Pid]);
print_client(D, Other) ->
io:format(D, " [[Other 1: ~p]]~n",[Other]).
@@ -134,10 +134,11 @@ walk_sups(D, StartPid) ->
io:format(D, "Start at ~p, ~s.~n",[StartPid,dead_or_alive(StartPid)]),
walk_sups(D, children(StartPid), _Indent=?inc(0)).
-walk_sups(D, [H={_,Pid,SupOrWorker,_}|T], Indent) ->
+walk_sups(D, [H={_,Pid,_,_}|T], Indent) ->
indent(D, Indent), io:format(D, '~200p ~p is ~s~n',[H,Pid,dead_or_alive(Pid)]),
- case SupOrWorker of
- supervisor -> walk_sups(D, children(Pid), ?inc(Indent));
+ case H of
+ {_,_,supervisor,[ssh_connection_handler]} -> ok;
+ {_,Pid,supervisor,_} -> walk_sups(D, children(Pid), ?inc(Indent));
_ -> ok
end,
walk_sups(D, T, Indent);
@@ -187,7 +188,7 @@ line(D, Len, Char) ->
datetime() ->
- {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(now()),
+ {{YYYY,MM,DD}, {H,M,S}} = calendar:now_to_universal_time(erlang:timestamp()),
lists:flatten(io_lib:format('~4w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w UTC',[YYYY,MM,DD, H,M,S])).
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 66e7717095..483c6cb4aa 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -421,8 +421,8 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
-decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/binary>>) ->
- #ssh_msg_kexdh_init{e = erlint(Len, E)
+decode(<<?BYTE(?SSH_MSG_KEXDH_INIT), ?UINT32(Len), E:Len/big-signed-integer-unit:8>>) ->
+ #ssh_msg_kexdh_init{e = E
};
decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST), ?UINT32(Min), ?UINT32(N), ?UINT32(Max)>>) ->
#ssh_msg_kex_dh_gex_request{
@@ -442,11 +442,11 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP),
g = Generator
};
decode(<<?BYTE(?SSH_MSG_KEXDH_REPLY), ?UINT32(Len0), Key:Len0/binary,
- ?UINT32(Len1), F:Len1/binary,
+ ?UINT32(Len1), F:Len1/big-signed-integer-unit:8,
?UINT32(Len2), Hashsign:Len2/binary>>) ->
#ssh_msg_kexdh_reply{
public_host_key = decode_host_key(Key),
- f = erlint(Len1, F),
+ f = F,
h_sig = decode_sign(Hashsign)
};
@@ -514,10 +514,7 @@ decode_kex_init(<<?UINT32(Len), Data:Len/binary, Rest/binary>>, Acc, N) ->
Names = string:tokens(unicode:characters_to_list(Data), ","),
decode_kex_init(Rest, [Names | Acc], N -1).
-erlint(MPIntSize, MPIntValue) ->
- Bits = MPIntSize * 8,
- <<Integer:Bits/integer>> = MPIntValue,
- Integer.
+
decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) ->
Signature.
@@ -525,18 +522,19 @@ decode_sign(<<?UINT32(Len), _Alg:Len/binary, ?UINT32(_), Signature/binary>>) ->
decode_host_key(<<?UINT32(Len), Alg:Len/binary, Rest/binary>>) ->
decode_host_key(Alg, Rest).
-decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/binary,
- ?UINT32(Len1), N:Len1/binary>>) ->
- #'RSAPublicKey'{publicExponent = erlint(Len0, E),
- modulus = erlint(Len1, N)};
+decode_host_key(<<"ssh-rsa">>, <<?UINT32(Len0), E:Len0/big-signed-integer-unit:8,
+ ?UINT32(Len1), N:Len1/big-signed-integer-unit:8>>) ->
+ #'RSAPublicKey'{publicExponent = E,
+ modulus = N};
decode_host_key(<<"ssh-dss">>,
- <<?UINT32(Len0), P:Len0/binary,
- ?UINT32(Len1), Q:Len1/binary,
- ?UINT32(Len2), G:Len2/binary,
- ?UINT32(Len3), Y:Len3/binary>>) ->
- {erlint(Len3, Y), #'Dss-Parms'{p = erlint(Len0, P), q = erlint(Len1, Q),
- g = erlint(Len2, G)}}.
+ <<?UINT32(Len0), P:Len0/big-signed-integer-unit:8,
+ ?UINT32(Len1), Q:Len1/big-signed-integer-unit:8,
+ ?UINT32(Len2), G:Len2/big-signed-integer-unit:8,
+ ?UINT32(Len3), Y:Len3/big-signed-integer-unit:8>>) ->
+ {Y, #'Dss-Parms'{p = P,
+ q = Q,
+ g = G}}.
encode_host_key(#'RSAPublicKey'{modulus = N, publicExponent = E}) ->
ssh_bits:encode(["ssh-rsa", E, N], [string, mpint, mpint]);
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index 613f8f25b2..bab688f226 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -111,7 +111,7 @@ start_channel(Cm, Opts) when is_pid(Cm) ->
TimeOut
end;
{error, Reason} ->
- {error, Reason};
+ {error, format_channel_start_error(Reason)};
ignore ->
{error, ignore}
end;
@@ -136,7 +136,7 @@ start_channel(Host, Port, Opts) ->
TimeOut
end;
{error, Reason} ->
- {error, Reason};
+ {error, format_channel_start_error(Reason)};
ignore ->
{error, ignore}
end;
@@ -491,9 +491,9 @@ init([Cm, ChannelId, Options]) ->
inf = new_inf(),
opts = Options}};
failure ->
- {stop, "server failed to start sftp subsystem"};
+ {stop, {shutdown, "server failed to start sftp subsystem"}};
Error ->
- {stop, Error}
+ {stop, {shutdown, Error}}
end.
%%--------------------------------------------------------------------
@@ -508,12 +508,12 @@ init([Cm, ChannelId, Options]) ->
%%--------------------------------------------------------------------
handle_call({{timeout, infinity}, wait_for_version_negotiation}, From,
#state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
- {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = {wait, From, undefined}}}};
handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From,
#state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
- timer:send_after(Timeout, {timeout, undefined, From}),
- {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+ TRef = erlang:send_after(Timeout, self(), {timeout, undefined, From}),
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = {wait, From, TRef}}}};
handle_call({_, wait_for_version_negotiation}, _, State) ->
{reply, ok, State};
@@ -865,7 +865,12 @@ do_handle_reply(#state{xf = Xf} = State,
case Xf#ssh_xfer.vsn of
undefined ->
ok;
- From ->
+ {wait, From, TRef} ->
+ if is_reference(TRef) ->
+ erlang:cancel_timer(TRef);
+ true ->
+ ok
+ end,
ssh_channel:reply(From, ok)
end,
State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest};
@@ -1412,3 +1417,8 @@ open_buf1(Pid, BufInfo0, FileOpTimeout, CryptoState, ChunkSize) ->
BufHandle = make_ref(),
call(Pid, {put_bufinf,BufHandle,BufInfo}, FileOpTimeout),
{ok,BufHandle}.
+
+format_channel_start_error({shutdown, Reason}) ->
+ Reason;
+format_channel_start_error(Reason) ->
+ Reason.
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 660fe8bb65..acf94b4b73 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -28,13 +28,15 @@
-behaviour(supervisor).
+-include("ssh.hrl").
+
-export([start_link/1, stop_listener/1,
- stop_listener/2, stop_system/1,
- stop_system/2, system_supervisor/2,
+ stop_listener/3, stop_system/1,
+ stop_system/3, system_supervisor/3,
subsystem_supervisor/1, channel_supervisor/1,
connection_supervisor/1,
- acceptor_supervisor/1, start_subsystem/2, restart_subsystem/2,
- restart_acceptor/2, stop_subsystem/2]).
+ acceptor_supervisor/1, start_subsystem/2, restart_subsystem/3,
+ restart_acceptor/3, stop_subsystem/2]).
%% Supervisor callback
-export([init/1]).
@@ -45,14 +47,15 @@
start_link(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- Name = make_name(Address, Port),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ Name = make_name(Address, Port, Profile),
supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]).
stop_listener(SysSup) ->
stop_acceptor(SysSup).
-stop_listener(Address, Port) ->
- Name = make_name(Address, Port),
+stop_listener(Address, Port, Profile) ->
+ Name = make_name(Address, Port, Profile),
stop_acceptor(whereis(Name)).
stop_system(SysSup) ->
@@ -60,12 +63,12 @@ stop_system(SysSup) ->
spawn(fun() -> sshd_sup:stop_child(Name) end),
ok.
-stop_system(Address, Port) ->
- spawn(fun() -> sshd_sup:stop_child(Address, Port) end),
+stop_system(Address, Port, Profile) ->
+ spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end),
ok.
-system_supervisor(Address, Port) ->
- Name = make_name(Address, Port),
+system_supervisor(Address, Port, Profile) ->
+ Name = make_name(Address, Port, Profile),
whereis(Name).
subsystem_supervisor(SystemSup) ->
@@ -103,9 +106,9 @@ stop_subsystem(SystemSup, SubSys) ->
end.
-restart_subsystem(Address, Port) ->
- SysSupName = make_name(Address, Port),
- SubSysName = id(ssh_subsystem_sup, Address, Port),
+restart_subsystem(Address, Port, Profile) ->
+ SysSupName = make_name(Address, Port, Profile),
+ SubSysName = id(ssh_subsystem_sup, Address, Port, Profile),
case supervisor:terminate_child(SysSupName, SubSysName) of
ok ->
supervisor:restart_child(SysSupName, SubSysName);
@@ -113,9 +116,9 @@ restart_subsystem(Address, Port) ->
Error
end.
-restart_acceptor(Address, Port) ->
- SysSupName = make_name(Address, Port),
- AcceptorName = id(ssh_acceptor_sup, Address, Port),
+restart_acceptor(Address, Port, Profile) ->
+ SysSupName = make_name(Address, Port, Profile),
+ AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile),
supervisor:restart_child(SysSupName, AcceptorName).
%%%=========================================================================
@@ -137,7 +140,8 @@ child_specs(ServerOpts) ->
ssh_acceptor_child_spec(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
Port = proplists:get_value(port, ServerOpts),
- Name = id(ssh_acceptor_sup, Address, Port),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ Name = id(ssh_acceptor_sup, Address, Port, Profile),
StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]},
Restart = transient,
Shutdown = infinity,
@@ -155,12 +159,23 @@ ssh_subsystem_child_spec(ServerOpts) ->
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-id(Sup, Address, Port) ->
- {Sup, Address, Port}.
-
-make_name(Address, Port) ->
- list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_sup",
- [Address, Port]))).
+id(Sup, Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {Sup, any, Port, Profile};
+ false ->
+ {Sup, Address, Port, Profile}
+ end.
+
+make_name(Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup",
+ [any, Port, Profile])));
+ false ->
+ list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_~p_sup",
+ [Address, Port, Profile])))
+ end.
ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) ->
Child;
@@ -178,3 +193,4 @@ stop_acceptor(Sup) ->
supervisor:which_children(Sup)],
supervisor:terminate_child(AcceptorSup, Name).
+
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 8669be570e..ea9bca2390 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -31,6 +31,8 @@
-export([versions/2, hello_version_msg/1]).
-export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3,
+ supported_algorithms/0, supported_algorithms/1,
+ default_algorithms/0, default_algorithms/1,
is_valid_mac/3,
handle_hello_version/1,
key_exchange_init_msg/1,
@@ -42,6 +44,68 @@
unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1,
sign/3, verify/4]).
+%%%----------------------------------------------------------------------------
+%%%
+%%% There is a difference between supported and default algorithms. The
+%%% SUPPORTED algorithms can be handled (maybe untested...). The DEFAULT ones
+%%% are announced in ssh_msg_kexinit and in ssh:default_algorithms/0 to the
+%%% user.
+%%%
+%%% A supported algorithm can be requested in the option 'preferred_algorithms',
+%%% but may give unexpected results because of being promoted to default.
+%%%
+%%% This makes it possible to add experimental algorithms (in supported_algorithms)
+%%% and test them without letting the default users know about them.
+%%%
+
+default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()].
+
+algo_classes() -> [kex, public_key, cipher, mac, compression].
+
+default_algorithms(compression) ->
+ %% Do not announce '[email protected]' because there seem to be problems
+ supported_algorithms(compression, same(['[email protected]']));
+default_algorithms(Alg) ->
+ supported_algorithms(Alg).
+
+
+supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()].
+
+supported_algorithms(kex) ->
+ ['diffie-hellman-group1-sha1'];
+supported_algorithms(public_key) ->
+ ssh_auth:default_public_key_algorithms();
+supported_algorithms(cipher) ->
+ Supports = crypto:supports(),
+ CipherAlgos = [{aes_ctr, 'aes128-ctr'}, {aes_cbc128, 'aes128-cbc'}, {des3_cbc, '3des-cbc'}],
+ Algs = [SshAlgo ||
+ {CryptoAlgo, SshAlgo} <- CipherAlgos,
+ lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))],
+ same(Algs);
+supported_algorithms(mac) ->
+ Supports = crypto:supports(),
+ HashAlgos = [{sha256, 'hmac-sha2-256'}, {sha, 'hmac-sha1'}],
+ Algs = [SshAlgo ||
+ {CryptoAlgo, SshAlgo} <- HashAlgos,
+ lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))],
+ same(Algs);
+supported_algorithms(compression) ->
+ same(['none','zlib','[email protected]']).
+
+
+supported_algorithms(Key, [{client2server,BL1},{server2client,BL2}]) ->
+ [{client2server,As1},{server2client,As2}] = supported_algorithms(Key),
+ [{client2server,As1--BL1},{server2client,As2--BL2}];
+supported_algorithms(Key, BlackList) ->
+ supported_algorithms(Key) -- BlackList.
+
+
+
+
+same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
+
+
+%%%----------------------------------------------------------------------------
versions(client, Options)->
Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION),
{Vsn, format_version(Vsn, software_version(Options))};
@@ -128,62 +192,45 @@ key_exchange_init_msg(Ssh0) ->
kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) ->
Random = ssh_bits:random(16),
- Compression = case proplists:get_value(compression, Opts, none) of
- openssh_zlib -> ["[email protected]", "none"];
- zlib -> ["zlib", "none"];
- none -> ["none", "zlib"]
- end,
- kexinit_messsage(Role, Random, Compression, HostKeyAlgs).
+ PrefAlgs =
+ case proplists:get_value(preferred_algorithms,Opts) of
+ undefined ->
+ default_algorithms();
+ Algs0 ->
+ Algs0
+ end,
+ kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs).
key_init(client, Ssh, Value) ->
Ssh#ssh{c_keyinit = Value};
key_init(server, Ssh, Value) ->
Ssh#ssh{s_keyinit = Value}.
-available_ssh_algos() ->
- Supports = crypto:supports(),
- CipherAlgos = [{aes_ctr, "aes128-ctr"}, {aes_cbc128, "aes128-cbc"}, {des3_cbc, "3des-cbc"}],
- Ciphers = [SshAlgo ||
- {CryptoAlgo, SshAlgo} <- CipherAlgos,
- lists:member(CryptoAlgo, proplists:get_value(ciphers, Supports, []))],
- HashAlgos = [{sha256, "hmac-sha2-256"}, {sha, "hmac-sha1"}],
- Hashs = [SshAlgo ||
- {CryptoAlgo, SshAlgo} <- HashAlgos,
- lists:member(CryptoAlgo, proplists:get_value(hashs, Supports, []))],
- {Ciphers, Hashs}.
-
-kexinit_messsage(client, Random, Compression, HostKeyAlgs) ->
- {CipherAlgs, HashAlgs} = available_ssh_algos(),
- #ssh_msg_kexinit{
- cookie = Random,
- kex_algorithms = ["diffie-hellman-group1-sha1"],
- server_host_key_algorithms = HostKeyAlgs,
- encryption_algorithms_client_to_server = CipherAlgs,
- encryption_algorithms_server_to_client = CipherAlgs,
- mac_algorithms_client_to_server = HashAlgs,
- mac_algorithms_server_to_client = HashAlgs,
- compression_algorithms_client_to_server = Compression,
- compression_algorithms_server_to_client = Compression,
- languages_client_to_server = [],
- languages_server_to_client = []
- };
-kexinit_messsage(server, Random, Compression, HostKeyAlgs) ->
- {CipherAlgs, HashAlgs} = available_ssh_algos(),
+kexinit_message(_Role, Random, Algs, HostKeyAlgs) ->
#ssh_msg_kexinit{
cookie = Random,
- kex_algorithms = ["diffie-hellman-group1-sha1"],
+ kex_algorithms = to_strings( get_algs(kex,Algs) ),
server_host_key_algorithms = HostKeyAlgs,
- encryption_algorithms_client_to_server = CipherAlgs,
- encryption_algorithms_server_to_client = CipherAlgs,
- mac_algorithms_client_to_server = HashAlgs,
- mac_algorithms_server_to_client = HashAlgs,
- compression_algorithms_client_to_server = Compression,
- compression_algorithms_server_to_client = Compression,
+ encryption_algorithms_client_to_server = c2s(cipher,Algs),
+ encryption_algorithms_server_to_client = s2c(cipher,Algs),
+ mac_algorithms_client_to_server = c2s(mac,Algs),
+ mac_algorithms_server_to_client = s2c(mac,Algs),
+ compression_algorithms_client_to_server = c2s(compression,Algs),
+ compression_algorithms_server_to_client = s2c(compression,Algs),
languages_client_to_server = [],
languages_server_to_client = []
}.
+c2s(Key, Algs) -> x2y(client2server, Key, Algs).
+s2c(Key, Algs) -> x2y(server2client, Key, Algs).
+
+x2y(DirectionKey, Key, Algs) -> to_strings(proplists:get_value(DirectionKey, get_algs(Key,Algs))).
+
+get_algs(Key, Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)).
+
+to_strings(L) -> lists:map(fun erlang:atom_to_list/1, L).
+
new_keys_message(Ssh0) ->
{SshPacket, Ssh} =
ssh_packet(#ssh_msg_newkeys{}, Ssh0),
@@ -240,20 +287,30 @@ key_exchange_first_msg('diffie-hellman-group-exchange-sha1', Ssh0) ->
handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) ->
{G, P} = dh_group1(),
- {Private, Public} = dh_gen_key(G, P, 1024),
- K = ssh_math:ipow(E, Private, P),
- Key = get_host_key(Ssh0),
- H = kex_h(Ssh0, Key, E, Public, K),
- H_SIG = sign_host_key(Ssh0, Key, H),
- {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = Key,
- f = Public,
- h_sig = H_SIG
- }, Ssh0),
-
- {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
- shared_secret = K,
- exchanged_hash = H,
- session_id = sid(Ssh1, H)}}.
+ if
+ 1=<E, E=<(P-1) ->
+ {Private, Public} = dh_gen_key(G, P, 1024),
+ K = ssh_math:ipow(E, Private, P),
+ Key = get_host_key(Ssh0),
+ H = kex_h(Ssh0, Key, E, Public, K),
+ H_SIG = sign_host_key(Ssh0, Key, H),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = Key,
+ f = Public,
+ h_sig = H_SIG
+ }, Ssh0),
+
+ {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
+ shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}};
+ true ->
+ Error = {error,bad_e_from_peer},
+ Disconnect = #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'f' out of bounds",
+ language = "en"},
+ throw({Error, Disconnect})
+ end.
handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
{Private, Public} = dh_gen_key(G,P,1024),
@@ -277,7 +334,7 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
%% %% Select algorithms
handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, f = F,
h_sig = H_SIG},
- #ssh{keyex_key = {{Private, Public}, {_G, P}}} = Ssh0) ->
+ #ssh{keyex_key = {{Private, Public}, {_G, P}}} = Ssh0) when 1=<F, F=<(P-1)->
K = ssh_math:ipow(F, Private, P),
H = kex_h(Ssh0, HostKey, Public, F, K),
@@ -293,7 +350,15 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, f = F,
description = "Key exchange failed",
language = "en"},
throw({Error, Disconnect})
- end.
+ end;
+handle_kexdh_reply(#ssh_msg_kexdh_reply{}, _SSH) ->
+ Error = {error,bad_f_from_peer},
+ Disconnect = #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed, 'f' out of bounds",
+ language = "en"},
+ throw({Error, Disconnect}).
+
handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = _Min,
n = _NBits,
@@ -430,6 +495,7 @@ select_algorithm(Role, Client, Server) ->
decompress = Decompression,
c_lng = C_Lng,
s_lng = S_Lng},
+%%ct:pal("~p~n Client=~p~n Server=~p~n Alg=~p~n",[Role,Client,Server,Alg]),
{ok, Alg}.
select_encrypt_decrypt(client, Client, Server) ->
@@ -519,10 +585,15 @@ alg_final(SSH0) ->
{ok,SSH6} = decompress_final(SSH5),
SSH6.
-select_all(CL, SL) ->
+select_all(CL, SL) when length(CL) + length(SL) < 50 ->
A = CL -- SL, %% algortihms only used by client
%% algorithms used by client and server (client pref)
- lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)).
+ lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A));
+select_all(_CL, _SL) ->
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Too many algorithms",
+ language = "en"}).
+
select([], []) ->
none;
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index 60222f5172..e879629ccb 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -26,8 +26,10 @@
-behaviour(supervisor).
+-include("ssh.hrl").
+
-export([start_link/1, start_child/1, stop_child/1,
- stop_child/2, system_name/1]).
+ stop_child/3, system_name/1]).
%% Supervisor callback
-export([init/1]).
@@ -40,13 +42,14 @@ start_link(Servers) ->
start_child(ServerOpts) ->
Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- case ssh_system_sup:system_supervisor(Address, Port) of
+ Port = proplists:get_value(port, ServerOpts),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ case ssh_system_sup:system_supervisor(Address, Port, Profile) of
undefined ->
Spec = child_spec(Address, Port, ServerOpts),
case supervisor:start_child(?MODULE, Spec) of
{error, already_present} ->
- Name = id(Address, Port),
+ Name = id(Address, Port, Profile),
supervisor:delete_child(?MODULE, Name),
supervisor:start_child(?MODULE, Spec);
Reply ->
@@ -60,8 +63,8 @@ start_child(ServerOpts) ->
stop_child(Name) ->
supervisor:terminate_child(?MODULE, Name).
-stop_child(Address, Port) ->
- Name = id(Address, Port),
+stop_child(Address, Port, Profile) ->
+ Name = id(Address, Port, Profile),
stop_child(Name).
system_name(SysSup) ->
@@ -87,7 +90,8 @@ init([Servers]) ->
%%% Internal functions
%%%=========================================================================
child_spec(Address, Port, ServerOpts) ->
- Name = id(Address, Port),
+ Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ Name = id(Address, Port,Profile),
StartFunc = {ssh_system_sup, start_link, [ServerOpts]},
Restart = temporary,
Shutdown = infinity,
@@ -95,8 +99,13 @@ child_spec(Address, Port, ServerOpts) ->
Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
-id(Address, Port) ->
- {server, ssh_system_sup, Address, Port}.
+id(Address, Port, Profile) ->
+ case is_list(Address) of
+ true ->
+ {server, ssh_system_sup, any, Port, Profile};
+ false ->
+ {server, ssh_system_sup, Address, Port, Profile}
+ end.
system_name([], _ ) ->
undefined;
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 740dbd0235..50efc33f98 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -32,15 +32,18 @@ VSN=$(GS_VSN)
MODULES= \
ssh_test_lib \
+ ssh_sup_SUITE \
ssh_basic_SUITE \
ssh_to_openssh_SUITE \
ssh_sftp_SUITE \
ssh_sftpd_SUITE \
ssh_sftpd_erlclient_SUITE \
+ ssh_upgrade_SUITE \
ssh_connection_SUITE \
ssh_echo_server \
ssh_peername_sockname_server \
- ssh_test_cli
+ ssh_test_cli \
+ ssh_relay
HRL_FILES_NEEDED_IN_TEST= \
$(ERL_TOP)/lib/ssh/src/ssh.hrl \
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index fa7b426545..e62feb6857 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -23,12 +23,14 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
+-include_lib("kernel/include/file.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
-define(NEWLINE, <<"\r\n">>).
+-define(REKEY_DATA_TMO, 65000).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
@@ -44,9 +46,13 @@ all() ->
{group, dsa_pass_key},
{group, rsa_pass_key},
{group, internal_error},
+ connectfun_disconnectfun_server,
+ connectfun_disconnectfun_client,
+ {group, renegotiate},
daemon_already_started,
server_password_option,
server_userpassword_option,
+ {group, dir_options},
double_close,
ssh_connect_timeout,
ssh_connect_arg4_timeout,
@@ -54,13 +60,19 @@ all() ->
ssh_daemon_minimal_remote_max_packet_size_option,
ssh_msg_debug_fun_option_client,
ssh_msg_debug_fun_option_server,
+ disconnectfun_option_server,
+ disconnectfun_option_client,
+ unexpectedfun_option_server,
+ unexpectedfun_option_client,
+ preferred_algorithms,
id_string_no_opt_client,
id_string_own_string_client,
id_string_random_client,
id_string_no_opt_server,
id_string_own_string_server,
id_string_random_server,
- {group, hardening_tests}
+ {group, hardening_tests},
+ ssh_info_print
].
groups() ->
@@ -69,6 +81,7 @@ groups() ->
{dsa_pass_key, [], [pass_phrase]},
{rsa_pass_key, [], [pass_phrase]},
{internal_error, [], [internal_error]},
+ {renegotiate, [], [rekey, rekey_limit, renegotiate1, renegotiate2]},
{hardening_tests, [], [ssh_connect_nonegtimeout_connected_parallel,
ssh_connect_nonegtimeout_connected_sequential,
ssh_connect_negtimeout_parallel,
@@ -77,19 +90,21 @@ groups() ->
max_sessions_ssh_connect_sequential,
max_sessions_sftp_start_channel_parallel,
max_sessions_sftp_start_channel_sequential
- ]}
+ ]},
+ {dir_options, [], [user_dir_option,
+ system_dir_option]}
].
basic_tests() ->
[send, close, peername_sockname,
exec, exec_compressed, shell, cli, known_hosts,
- idle_time, rekey, openssh_zlib_basic_test,
- misc_ssh_options, inet_option].
+ idle_time, openssh_zlib_basic_test, misc_ssh_options, inet_option].
%%--------------------------------------------------------------------
init_per_suite(Config) ->
+ catch crypto:stop(),
case catch crypto:start() of
ok ->
Config;
@@ -128,6 +143,48 @@ init_per_group(internal_error, Config) ->
ssh_test_lib:setup_dsa(DataDir, PrivDir),
file:delete(filename:join(PrivDir, "system/ssh_host_dsa_key")),
Config;
+init_per_group(dir_options, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ %% Make unreadable dir:
+ Dir_unreadable = filename:join(PrivDir, "unread"),
+ ok = file:make_dir(Dir_unreadable),
+ {ok,F1} = file:read_file_info(Dir_unreadable),
+ ok = file:write_file_info(Dir_unreadable,
+ F1#file_info{mode = F1#file_info.mode band (bnot 8#00444)}),
+ %% Make readable file:
+ File_readable = filename:join(PrivDir, "file"),
+ ok = file:write_file(File_readable, <<>>),
+
+ %% Check:
+ case {file:read_file_info(Dir_unreadable),
+ file:read_file_info(File_readable)} of
+ {{ok, Id=#file_info{type=directory, access=Md}},
+ {ok, If=#file_info{type=regular, access=Mf}}} ->
+ AccessOK =
+ case {Md, Mf} of
+ {read, _} -> false;
+ {read_write, _} -> false;
+ {_, read} -> true;
+ {_, read_write} -> true;
+ _ -> false
+ end,
+
+ case AccessOK of
+ true ->
+ %% Save:
+ [{unreadable_dir, Dir_unreadable},
+ {readable_file, File_readable}
+ | Config];
+ false ->
+ ct:log("File#file_info : ~p~n"
+ "Dir#file_info : ~p",[If,Id]),
+ {skip, "File or dir mode settings failed"}
+ end;
+
+ NotDirFile ->
+ ct:log("{Dir,File} -> ~p",[NotDirFile]),
+ {skip, "File/Dir creation failed"}
+ end;
init_per_group(_, Config) ->
Config.
@@ -287,7 +344,7 @@ exec_compressed(Config) when is_list(Config) ->
UserDir = ?config(priv_dir, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
- {compression, zlib},
+ {preferred_algorithms,[{compression, [zlib]}]},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
@@ -333,25 +390,175 @@ idle_time(Config) ->
rekey() ->
[{doc, "Idle timeout test"}].
rekey(Config) ->
- SystemDir = filename:join(?config(priv_dir, Config), system),
+ SystemDir = ?config(data_dir, Config),
UserDir = ?config(priv_dir, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {user_dir, UserDir},
+ {user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{"simon", "says"}]},
{rekey_limit, 0}]),
+
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
+ {user, "simon"},
+ {password, "says"},
{user_interaction, false},
{rekey_limit, 0}]),
receive
- after 200000 ->
+ after ?REKEY_DATA_TMO ->
%%By this time rekeying would have been done
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid)
end.
%%--------------------------------------------------------------------
+rekey_limit() ->
+ [{doc, "Test rekeying by data volume"}].
+rekey_limit(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "rekey.data"),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords,
+ [{"simon", "says"}]}]),
+ {ok, SftpPid, ConnectionRef} =
+ ssh_sftp:start_channel(Host, Port, [{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user, "simon"},
+ {password, "says"},
+ {rekey_limit, 2500},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
+
+ Kex1 = get_kex_init(ConnectionRef),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex1 = get_kex_init(ConnectionRef),
+
+ Data = lists:duplicate(9000,1),
+ ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex2 = get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex2 = get_kex_init(ConnectionRef),
+
+ ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex2 = get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ timer:sleep(?REKEY_DATA_TMO),
+ Kex2 = get_kex_init(ConnectionRef),
+
+
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+renegotiate1() ->
+ [{doc, "Test rekeying with simulataneous send request"}].
+renegotiate1(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate1.data"),
+
+ {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords,
+ [{"simon", "says"}]}]),
+ RPort = ssh_test_lib:inet_port(),
+
+ {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
+
+ {ok, SftpPid, ConnectionRef} =
+ ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user, "simon"},
+ {password, "says"},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
+
+ Kex1 = get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, 1000),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+
+ timer:sleep(2000),
+
+ Kex2 = get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+renegotiate2() ->
+ [{doc, "Test rekeying with inflight messages from peer"}].
+renegotiate2(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ UserDir = ?config(priv_dir, Config),
+ DataFile = filename:join(UserDir, "renegotiate1.data"),
+
+ {Pid, Host, DPort} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords,
+ [{"simon", "says"}]}]),
+ RPort = ssh_test_lib:inet_port(),
+
+ {ok,RelayPid} = ssh_relay:start_link({0,0,0,0}, RPort, Host, DPort),
+
+ {ok, SftpPid, ConnectionRef} =
+ ssh_sftp:start_channel(Host, RPort, [{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user, "simon"},
+ {password, "says"},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
+
+ Kex1 = get_kex_init(ConnectionRef),
+
+ {ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
+
+ ok = ssh_sftp:write(SftpPid, Handle, "hi\n"),
+
+ ssh_relay:hold(RelayPid, rx, 20, infinity),
+ spawn(fun() -> ok=ssh_sftp:write(SftpPid, Handle, "another hi\n") end),
+ %% need a small pause here to ensure ssh_sftp:write is executed
+ ct:sleep(10),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ ssh_relay:release(RelayPid, rx),
+
+ timer:sleep(2000),
+
+ Kex2 = get_kex_init(ConnectionRef),
+
+ false = (Kex2 == Kex1),
+
+ ssh_relay:stop(RelayPid),
+ ssh_sftp:stop_channel(SftpPid),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
shell() ->
[{doc, "Test that ssh:shell/2 works"}].
shell(Config) when is_list(Config) ->
@@ -496,6 +703,48 @@ server_userpassword_option(Config) when is_list(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+system_dir_option(Config) ->
+ DirUnread = proplists:get_value(unreadable_dir,Config),
+ FileRead = proplists:get_value(readable_file,Config),
+
+ case ssh_test_lib:daemon([{system_dir, DirUnread}]) of
+ {error,{eoptions,{{system_dir,DirUnread},eacces}}} ->
+ ok;
+ {Pid1,_Host1,Port1} when is_pid(Pid1),is_integer(Port1) ->
+ ssh:stop_daemon(Pid1),
+ ct:fail("Didn't detect that dir is unreadable", [])
+ end,
+
+ case ssh_test_lib:daemon([{system_dir, FileRead}]) of
+ {error,{eoptions,{{system_dir,FileRead},enotdir}}} ->
+ ok;
+ {Pid2,_Host2,Port2} when is_pid(Pid2),is_integer(Port2) ->
+ ssh:stop_daemon(Pid2),
+ ct:fail("Didn't detect that option is a plain file", [])
+ end.
+
+
+user_dir_option(Config) ->
+ DirUnread = proplists:get_value(unreadable_dir,Config),
+ FileRead = proplists:get_value(readable_file,Config),
+ %% Any port will do (beware, implementation knowledge!):
+ Port = 65535,
+
+ case ssh:connect("localhost", Port, [{user_dir, DirUnread}]) of
+ {error,{eoptions,{{user_dir,DirUnread},eacces}}} ->
+ ok;
+ {error,econnrefused} ->
+ ct:fail("Didn't detect that dir is unreadable", [])
+ end,
+
+ case ssh:connect("localhost", Port, [{user_dir, FileRead}]) of
+ {error,{eoptions,{{user_dir,FileRead},enotdir}}} ->
+ ok;
+ {error,econnrefused} ->
+ ct:fail("Didn't detect that option is a plain file", [])
+ end.
+
+%%--------------------------------------------------------------------
ssh_msg_debug_fun_option_client() ->
[{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}].
ssh_msg_debug_fun_option_client(Config) ->
@@ -538,6 +787,74 @@ ssh_msg_debug_fun_option_client(Config) ->
end.
%%--------------------------------------------------------------------
+connectfun_disconnectfun_server(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ Ref = make_ref(),
+ ConnFun = fun(_,_,_) -> Parent ! {connect,Ref} end,
+ DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {disconnectfun, DiscFun},
+ {connectfun, ConnFun}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ receive
+ {connect,Ref} ->
+ ssh:close(ConnectionRef),
+ receive
+ {disconnect,Ref,R} ->
+ ct:log("Disconnect result: ~p",[R]),
+ ssh:stop_daemon(Pid)
+ after 2000 ->
+ {fail, "No disconnectfun action"}
+ end
+ after 2000 ->
+ {fail, "No connectfun action"}
+ end.
+
+%%--------------------------------------------------------------------
+connectfun_disconnectfun_client(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ Ref = make_ref(),
+ DiscFun = fun(R) -> Parent ! {disconnect,Ref,R} end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ _ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {disconnectfun, DiscFun},
+ {user_interaction, false}]),
+ ssh:stop_daemon(Pid),
+ receive
+ {disconnect,Ref,R} ->
+ ct:log("Disconnect result: ~p",[R])
+ after 2000 ->
+ {fail, "No disconnectfun action"}
+ end.
+
+%%--------------------------------------------------------------------
ssh_msg_debug_fun_option_server() ->
[{doc, "validate client that uses the 'ssh_msg_debug_fun' option"}].
ssh_msg_debug_fun_option_server(Config) ->
@@ -584,6 +901,157 @@ ssh_msg_debug_fun_option_server(Config) ->
end.
%%--------------------------------------------------------------------
+disconnectfun_option_server(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {disconnectfun, DisConnFun}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ ssh:close(ConnectionRef),
+ receive
+ {disconnect,Reason} ->
+ ct:log("Server detected disconnect: ~p",[Reason]),
+ ssh:stop_daemon(Pid),
+ ok
+ after 3000 ->
+ receive
+ X -> ct:log("received ~p",[X])
+ after 0 -> ok
+ end,
+ {fail,"Timeout waiting for disconnect"}
+ end.
+
+%%--------------------------------------------------------------------
+disconnectfun_option_client(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ DisConnFun = fun(Reason) -> Parent ! {disconnect,Reason} end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ _ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {user_interaction, false},
+ {disconnectfun, DisConnFun}]),
+ ssh:stop_daemon(Pid),
+ receive
+ {disconnect,Reason} ->
+ ct:log("Client detected disconnect: ~p",[Reason]),
+ ok
+ after 3000 ->
+ receive
+ X -> ct:log("received ~p",[X])
+ after 0 -> ok
+ end,
+ {fail,"Timeout waiting for disconnect"}
+ end.
+
+%%--------------------------------------------------------------------
+unexpectedfun_option_server(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ ConnFun = fun(_,_,_) -> Parent ! {connection_pid,self()} end,
+ UnexpFun = fun(Msg,Peer) ->
+ Parent ! {unexpected,Msg,Peer,self()},
+ skip
+ end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {connectfun, ConnFun},
+ {unexpectedfun, UnexpFun}]),
+ _ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ receive
+ {connection_pid,Server} ->
+ %% Beware, implementation knowledge:
+ Server ! unexpected_message,
+ receive
+ {unexpected, unexpected_message, {{_,_,_,_},_}, _} -> ok;
+ {unexpected, unexpected_message, Peer, _} -> ct:fail("Bad peer ~p",[Peer]);
+ M = {unexpected, _, _, _} -> ct:fail("Bad msg ~p",[M])
+ after 3000 ->
+ ssh:stop_daemon(Pid),
+ {fail,timeout2}
+ end
+ after 3000 ->
+ ssh:stop_daemon(Pid),
+ {fail,timeout1}
+ end.
+
+%%--------------------------------------------------------------------
+unexpectedfun_option_client(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ UnexpFun = fun(Msg,Peer) ->
+ Parent ! {unexpected,Msg,Peer,self()},
+ skip
+ end,
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {user_interaction, false},
+ {unexpectedfun, UnexpFun}]),
+ %% Beware, implementation knowledge:
+ ConnectionRef ! unexpected_message,
+
+ receive
+ {unexpected, unexpected_message, {{_,_,_,_},_}, ConnectionRef} ->
+ ok;
+ {unexpected, unexpected_message, Peer, ConnectionRef} ->
+ ct:fail("Bad peer ~p",[Peer]);
+ M = {unexpected, _, _, _} ->
+ ct:fail("Bad msg ~p",[M])
+ after 3000 ->
+ ssh:stop_daemon(Pid),
+ {fail,timeout}
+ end.
+
+%%--------------------------------------------------------------------
known_hosts() ->
[{doc, "check that known_hosts is updated correctly"}].
known_hosts(Config) when is_list(Config) ->
@@ -813,7 +1281,7 @@ ssh_connect_arg4_timeout(_Config) ->
%% try to connect with a timeout, but "supervise" it
Client = spawn(fun() ->
- T0 = now(),
+ T0 = erlang:monotonic_time(),
Rc = ssh:connect("localhost",Port,[],Timeout),
ct:log("Client ssh:connect got ~p",[Rc]),
Parent ! {done,self(),Rc,T0}
@@ -822,13 +1290,12 @@ ssh_connect_arg4_timeout(_Config) ->
%% Wait for client reaction on the connection try:
receive
{done, Client, {error,timeout}, T0} ->
- Msp = ms_passed(T0, now()),
+ Msp = ms_passed(T0),
exit(Server,hasta_la_vista___baby),
Low = 0.9*Timeout,
High = 1.1*Timeout,
ct:log("Timeout limits: ~.4f - ~.4f ms, timeout "
"was ~.4f ms, expected ~p ms",[Low,High,Msp,Timeout]),
- %%ct:log("Timeout limits: ~p--~p, my timeout was ~p, expected ~p",[Low,High,Msp0,Timeout]),
if
Low<Msp, Msp<High -> ok;
true -> {fail, "timeout not within limits"}
@@ -847,12 +1314,12 @@ ssh_connect_arg4_timeout(_Config) ->
{fail, "Didn't timeout"}
end.
-%% Help function
-%% N2-N1
-ms_passed(N1={_,_,M1}, N2={_,_,M2}) ->
- {0,{0,Min,Sec}} = calendar:time_difference(calendar:now_to_local_time(N1),
- calendar:now_to_local_time(N2)),
- 1000 * (Min*60 + Sec + (M2-M1)/1000000).
+%% Help function, elapsed milliseconds since T0
+ms_passed(T0) ->
+ %% OTP 18
+ erlang:convert_time_unit(erlang:monotonic_time() - T0,
+ native,
+ micro_seconds) / 1000.
%%--------------------------------------------------------------------
packet_size_zero(Config) ->
@@ -913,57 +1380,114 @@ ssh_daemon_minimal_remote_max_packet_size_option(Config) ->
ssh:stop_daemon(Server).
%%--------------------------------------------------------------------
+%% This test try every algorithm by connecting to an Erlang server
+preferred_algorithms(Config) ->
+ SystemDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+
+ {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"vego", "morot"}]},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ Available = ssh:default_algorithms(),
+ Tests = [[{Tag,[Alg]}] || {Tag, SubAlgs} <- Available,
+ is_atom(hd(SubAlgs)),
+ Alg <- SubAlgs]
+ ++ [[{Tag,[{T1,[A1]},{T2,[A2]}]}] || {Tag, [{T1,As1},{T2,As2}]} <- Available,
+ A1 <- As1,
+ A2 <- As2],
+ ct:log("TESTS: ~p",[Tests]),
+ [connect_exec_channel(Host,Port,PrefAlgs) || PrefAlgs <- Tests],
+ ssh:stop_daemon(Server).
+
+
+connect_exec_channel(_Host, Port, Algs) ->
+ ct:log("Try ~p",[Algs]),
+ ConnectionRef = ssh_test_lib:connect(Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, "vego"},
+ {password, "morot"},
+ {preferred_algorithms,Algs}
+ ]),
+ chan_exec(ConnectionRef, "2*21.", <<"42\n">>),
+ ssh:close(ConnectionRef).
+
+chan_exec(ConnectionRef, Cmnd, Expected) ->
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0,Cmnd, infinity),
+ Data0 = {ssh_cm, ConnectionRef, {data, ChannelId0, 0, Expected}},
+ case ssh_test_lib:receive_exec_result(Data0) of
+ expected ->
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId0);
+ {unexpected_msg,{ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}}
+ = ExitStatus0} ->
+ ct:pal("0: Collected data ~p", [ExitStatus0]),
+ ssh_test_lib:receive_exec_result(Data0,
+ ConnectionRef, ChannelId0);
+ Other0 ->
+ ct:fail(Other0)
+ end.
+
+%%--------------------------------------------------------------------
id_string_no_opt_client(Config) ->
- {Server, Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect(Host, Port, []),
+ {Server, _Host, Port} = fake_daemon(Config),
+ {error,_} = ssh:connect("localhost", Port, [], 1000),
receive
{id,Server,"SSH-2.0-Erlang/"++Vsn} ->
true = expected_ssh_vsn(Vsn);
{id,Server,Other} ->
ct:fail("Unexpected id: ~s.",[Other])
+ after 5000 ->
+ {fail,timeout}
end.
%%--------------------------------------------------------------------
id_string_own_string_client(Config) ->
- {Server, Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect(Host, Port, [{id_string,"Pelle"}]),
+ {Server, _Host, Port} = fake_daemon(Config),
+ {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}], 1000),
receive
{id,Server,"SSH-2.0-Pelle\r\n"} ->
ok;
{id,Server,Other} ->
ct:fail("Unexpected id: ~s.",[Other])
+ after 5000 ->
+ {fail,timeout}
end.
%%--------------------------------------------------------------------
id_string_random_client(Config) ->
- {Server, Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect(Host, Port, [{id_string,random}]),
+ {Server, _Host, Port} = fake_daemon(Config),
+ {error,_} = ssh:connect("localhost", Port, [{id_string,random}], 1000),
receive
{id,Server,Id="SSH-2.0-Erlang"++_} ->
ct:fail("Unexpected id: ~s.",[Id]);
{id,Server,Rnd="SSH-2.0-"++_} ->
- ct:log("Got ~s.",[Rnd]);
+ ct:log("Got correct ~s",[Rnd]);
{id,Server,Id} ->
ct:fail("Unexpected id: ~s.",[Id])
+ after 5000 ->
+ {fail,timeout}
end.
%%--------------------------------------------------------------------
id_string_no_opt_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, []),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
+ {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
{ok,"SSH-2.0-Erlang/"++Vsn} = gen_tcp:recv(S1, 0, 2000),
true = expected_ssh_vsn(Vsn).
%%--------------------------------------------------------------------
id_string_own_string_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, [{id_string,"Olle"}]),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
+ {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
{ok,"SSH-2.0-Olle\r\n"} = gen_tcp:recv(S1, 0, 2000).
%%--------------------------------------------------------------------
id_string_random_server(Config) ->
{_Server, Host, Port} = std_daemon(Config, [{id_string,random}]),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false}]),
+ {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
{ok,"SSH-2.0-"++Rnd} = gen_tcp:recv(S1, 0, 2000),
case Rnd of
"Erlang"++_ -> ct:log("Id=~p",[Rnd]),
@@ -989,8 +1513,10 @@ ssh_connect_negtimeout(Config, Parallel) ->
{failfun, fun ssh_test_lib:failfun/2}]),
{ok,Socket} = gen_tcp:connect(Host, Port, []),
- ct:pal("And now sleeping 1.2*NegTimeOut (~p ms)...", [round(1.2 * NegTimeOut)]),
- receive after round(1.2 * NegTimeOut) -> ok end,
+
+ Factor = 2,
+ ct:pal("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]),
+ ct:sleep(round(Factor * NegTimeOut)),
case inet:sockname(Socket) of
{ok,_} -> ct:fail("Socket not closed");
@@ -1033,8 +1559,11 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) ->
ct:pal("---Erlang shell start: ~p~n", [ErlShellStart]),
one_shell_op(IO, NegTimeOut),
one_shell_op(IO, NegTimeOut),
- ct:pal("And now sleeping 1.2*NegTimeOut (~p ms)...", [round(1.2 * NegTimeOut)]),
- receive after round(1.2 * NegTimeOut) -> ok end,
+
+ Factor = 2,
+ ct:pal("And now sleeping ~p*NegTimeOut (~p ms)...", [Factor, round(Factor * NegTimeOut)]),
+ ct:sleep(round(Factor * NegTimeOut)),
+
one_shell_op(IO, NegTimeOut)
end,
exit(Shell, kill).
@@ -1076,12 +1605,15 @@ openssh_zlib_basic_test(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
+ {preferred_algorithms,[{compression, ['[email protected]']}]},
{failfun, fun ssh_test_lib:failfun/2}]),
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false},
- {compression, openssh_zlib}]),
+ {preferred_algorithms,[{compression, ['[email protected]',
+ none]}]}
+ ]),
ok = ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
@@ -1159,6 +1691,7 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) ->
%% This is expected
%% Now stop one connection and try to open one more
ok = ssh:close(hd(Connections)),
+ receive after 250 -> ok end, % sleep so the supervisor has time to count down. Not nice...
try Connect(Host,Port)
of
_ConnectionRef1 ->
@@ -1181,6 +1714,74 @@ max_sessions(Config, ParallelLogin, Connect0) when is_function(Connect0,2) ->
end.
%%--------------------------------------------------------------------
+ssh_info_print(Config) ->
+ %% Just check that ssh_print:info() crashes
+ PrivDir = ?config(priv_dir, Config),
+ PrintFile = filename:join(PrivDir,info),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = ?config(data_dir, Config),
+
+ Parent = self(),
+ UnexpFun = fun(Msg,_Peer) ->
+ Parent ! {unexpected,Msg,self()},
+ skip
+ end,
+ ConnFun = fun(_,_,_) -> Parent ! {connect,self()} end,
+
+ {DaemonRef, Host, Port} =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {unexpectedfun, UnexpFun},
+ {connectfun, ConnFun},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ClientConnRef1 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {unexpectedfun, UnexpFun},
+ {user_interaction, false}]),
+ ClientConnRef2 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir},
+ {unexpectedfun, UnexpFun},
+ {user_interaction, false}]),
+ receive
+ {connect,DaemonConnRef} ->
+ ct:log("DaemonRef=~p, DaemonConnRef=~p, ClientConnRefs=~p",[DaemonRef, DaemonConnRef,
+ [ClientConnRef1,ClientConnRef2]
+ ])
+ after 2000 ->
+ ok
+ end,
+
+ {ok,D} = file:open(PrintFile, write),
+ ssh_info:print(D),
+ ok = file:close(D),
+
+ {ok,Bin} = file:read_file(PrintFile),
+ ct:log("~s",[Bin]),
+
+ receive
+ {unexpected, Msg, Pid} ->
+ ct:log("~p got unexpected msg ~p",[Pid,Msg]),
+ ct:log("process_info(~p) = ~n~p",[Pid,process_info(Pid)]),
+ ok = ssh:close(ClientConnRef1),
+ ok = ssh:close(ClientConnRef2),
+ ok = ssh:stop_daemon(DaemonRef),
+ {fail,"unexpected msg"}
+ after 1000 ->
+ ok = ssh:close(ClientConnRef1),
+ ok = ssh:close(ClientConnRef2),
+ ok = ssh:stop_daemon(DaemonRef)
+ end.
+
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -1274,13 +1875,14 @@ expected_ssh_vsn(Str) ->
_:_ -> true %% ssh not started so we dont't know
end.
-
+
fake_daemon(_Config) ->
Parent = self(),
%% start the server
Server = spawn(fun() ->
- {ok,Sl} = gen_tcp:listen(0,[]),
+ {ok,Sl} = gen_tcp:listen(0,[{packet,line}]),
{ok,{Host,Port}} = inet:sockname(Sl),
+ ct:log("fake_daemon listening on ~p:~p~n",[Host,Port]),
Parent ! {sockname,self(),Host,Port},
Rsa = gen_tcp:accept(Sl),
ct:log("Server gen_tcp:accept got ~p",[Rsa]),
@@ -1294,3 +1896,18 @@ fake_daemon(_Config) ->
{sockname,Server,ServerHost,ServerPort} -> {Server, ServerHost, ServerPort}
end.
+%% get_kex_init - helper function to get key_exchange_init_msg
+get_kex_init(Conn) ->
+ %% First, validate the key exchange is complete (StateName == connected)
+ {connected,S} = sys:get_state(Conn),
+ %% Next, walk through the elements of the #state record looking
+ %% for the #ssh_msg_kexinit record. This method is robust against
+ %% changes to either record. The KEXINIT message contains a cookie
+ %% unique to each invocation of the key exchange procedure (RFC4253)
+ SL = tuple_to_list(S),
+ case lists:keyfind(ssh_msg_kexinit, 1, SL) of
+ false ->
+ throw(not_found);
+ KexInit ->
+ KexInit
+ end.
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index e3871b3feb..f0c337cf2f 100644
--- a/lib/ssh/test/ssh_connection_SUITE.erl
+++ b/lib/ssh/test/ssh_connection_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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,6 +21,7 @@
-module(ssh_connection_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("ssh/src/ssh_connect.hrl").
-compile(export_all).
@@ -64,6 +65,7 @@ ptty() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
+ catch crypto:stop(),
case catch crypto:start() of
ok ->
Config;
@@ -75,12 +77,13 @@ end_per_suite(_Config) ->
crypto:stop().
%%--------------------------------------------------------------------
-init_per_group(openssh, _Config) ->
+init_per_group(openssh, Config) ->
case gen_tcp:connect("localhost", 22, []) of
{error,econnrefused} ->
{skip,"No openssh deamon"};
{ok, Socket} ->
- gen_tcp:close(Socket)
+ gen_tcp:close(Socket),
+ ssh_test_lib:openssh_sanity_check(Config)
end;
init_per_group(_, Config) ->
Config.
@@ -92,7 +95,7 @@ end_per_group(_, Config) ->
init_per_testcase(_TestCase, Config) ->
%% To make sure we start clean as it is not certain that
%% end_per_testcase will be run!
- ssh:stop(),
+ end_per_testcase(Config),
ssh:start(),
Config.
@@ -269,7 +272,7 @@ ptty_alloc(Config) when is_list(Config) ->
{user_interaction, false}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
- [{term, default_term()}, {width, 70}, {high, 20}]),
+ [{term, os:getenv("TERM", ?DEFAULT_TERMINAL)}, {width, 70}, {height, 20}]),
ssh:close(ConnectionRef).
@@ -282,7 +285,7 @@ ptty_alloc_pixel(Config) when is_list(Config) ->
{user_interaction, false}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
- [{term, default_term()}, {pixel_widh, 630}, {pixel_hight, 470}]),
+ [{term, os:getenv("TERM", ?DEFAULT_TERMINAL)}, {pixel_widh, 630}, {pixel_hight, 470}]),
ssh:close(ConnectionRef).
%%--------------------------------------------------------------------
@@ -647,11 +650,3 @@ ssh_exec(Cmd) ->
spawn(fun() ->
io:format(Cmd ++ "\n")
end).
-
-default_term() ->
- case os:getenv("TERM") of
- false ->
- "vt100";
- Str when is_list(Str)->
- Str
- end.
diff --git a/lib/ssh/test/ssh_relay.erl b/lib/ssh/test/ssh_relay.erl
new file mode 100644
index 0000000000..a4f2bad2e2
--- /dev/null
+++ b/lib/ssh/test/ssh_relay.erl
@@ -0,0 +1,407 @@
+%%%-------------------------------------------------------------------
+%%% @author Simon Cornish <[email protected]>
+%%% @copyright (C) 2015, Simon Cornish
+%%% @doc
+%%% Provide manipulatable TCP-level relaying for testing SSH
+%%% @end
+%%% Created : 7 May 2015 by Simon Cornish <[email protected]>
+%%%-------------------------------------------------------------------
+-module(ssh_relay).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/4]).
+-export([stop/1]).
+-export([hold/4, release/2, release_next/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(hold, {
+ port,
+ n,
+ tmo,
+ tref,
+ q = []
+ }).
+
+-record(state, {
+ local_addr,
+ local_port,
+ peer_addr,
+ peer_port,
+ lpid,
+ local,
+ peer,
+ tx_hold,
+ rx_hold
+ }).
+
+-define(ACCEPT_TMO, 200).
+%%%===================================================================
+%%% API
+%%%===================================================================
+%%--------------------------------------------------------------------
+%% @doc
+%% Hold N (or 'all') messages in given direction.
+%% Messages will be released after the N+1th message or
+%% Tmo ms or 'infinity'
+%%
+%% Dir is 'tx' for direction local -> peer
+%% and 'rx' for direction peer -> local
+%%
+%% An Error, ealready, is returned if there is already a hold
+%% in the given direction
+%%
+%% @spec hold(Srv, Dir, N, Tmo) -> ok | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+hold(Srv, Dir, N, Tmo) ->
+ gen_server:call(Srv, {hold, Dir, N, Tmo}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Release all held messages in given direction.
+%%
+%% An Error, enoent, is returned if there is no hold
+%% in the given direction
+%%
+%% @spec release(Srv, Dir) -> ok | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+release(Srv, Dir) ->
+ gen_server:call(Srv, {release, Dir}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Release all held messages in given direction after the
+%% next message in the trigger direction
+%%
+%% An Error, enoent, is returned if there is no hold
+%% in the given direction
+%%
+%% @spec release_next(Srv, Dir, TriggerDir) -> ok | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+release_next(Srv, Dir, TriggerDir) ->
+ gen_server:call(Srv, {release_next, Dir, TriggerDir}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link(ListenAddr, ListenPort, PeerAddr, PeerPort) ->
+ gen_server:start_link(?MODULE, [ListenAddr, ListenPort, PeerAddr, PeerPort], []).
+
+stop(Srv) ->
+ unlink(Srv),
+ Srv ! stop.
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([ListenAddr, ListenPort, PeerAddr, PeerPort | Options]) ->
+ IfAddr = case ListenAddr of
+ {0,0,0,0} ->
+ [];
+ _ ->
+ [{ifaddr, ListenAddr}]
+ end,
+ case gen_tcp:listen(ListenPort, [{reuseaddr, true}, {backlog, 1}, {active, false}, binary | IfAddr]) of
+ {ok, LSock} ->
+ Parent = self(),
+ {LPid, _LMod} = spawn_monitor(fun() -> listen(Parent, LSock) end),
+ S = #state{local_addr = ListenAddr,
+ local_port = ListenPort,
+ lpid = LPid,
+ peer_addr = PeerAddr,
+ peer_port = PeerPort
+ },
+ {ok, S};
+ Error ->
+ {stop, Error}
+ end.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call({hold, Dir, N, Tmo}, _From, State) ->
+ case Dir of
+ tx ->
+ do_hold(#state.tx_hold, State#state.peer, N, Tmo, State);
+ rx ->
+ do_hold(#state.rx_hold, State#state.local, N, Tmo, State);
+ _ ->
+ {reply, {error, einval}, State}
+ end;
+handle_call({release, Dir}, _From, State) ->
+ case Dir of
+ tx ->
+ do_release(#state.tx_hold, State);
+ rx ->
+ do_release(#state.rx_hold, State);
+ _ ->
+ {reply, {error, einval}, State}
+ end;
+handle_call({release_next, _Dir, _TriggerDir}, _From, State) ->
+ {reply, {error, nyi}, State};
+
+handle_call(Request, _From, State) ->
+ Reply = {unhandled, Request},
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info({tcp, Local, Data}, S) when S#state.local == Local ->
+ S1 = do_local(Data, S),
+ {noreply, S1};
+
+handle_info({tcp_error, Local, Error}, S) when S#state.local == Local ->
+ S1 = do_local({error, Error}, S),
+ {noreply, S1};
+
+handle_info({tcp_closed, Local}, S) when S#state.local == Local ->
+ S1 = do_local(closed, S),
+ {noreply, S1};
+
+handle_info({tcp, Peer, Data}, S) when S#state.peer == Peer ->
+ S1 = do_peer(Data, S),
+ {noreply, S1};
+
+handle_info({tcp_error, Peer, Error}, S) when S#state.peer == Peer ->
+ S1 = do_peer({error, Error}, S),
+ {noreply, S1};
+
+handle_info({tcp_closed, Peer}, S) when S#state.peer == Peer ->
+ S1 = do_peer(closed, S),
+ {noreply, S1};
+
+handle_info({accept, Local}, S) ->
+ S1 = do_accept(Local, S),
+ {noreply, S1};
+
+handle_info({activate, Local}, State) ->
+ inet:setopts(Local, [{active, true}]),
+ {noreply, State};
+
+handle_info({release, Pos}, S) ->
+ {reply, _, S1} = do_release(Pos,S),
+ {noreply, S1};
+
+handle_info(stop, State) ->
+ {stop, normal, State};
+
+handle_info({'DOWN', _Ref, _process, LPid, Reason}, S) when S#state.lpid == LPid ->
+ io:format("Acceptor has finished: ~p~n", [Reason]),
+ {noreply, S};
+
+handle_info(_Info, State) ->
+ io:format("Unhandled info: ~p~n", [_Info]),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+do_hold(Pos, _Port, _N, _Tmo, S) when element(Pos, S) /= undefined ->
+ {reply, {error, ealready}, S};
+do_hold(Pos, Port, N, Tmo, S) ->
+ TRef = if is_integer(Tmo) andalso Tmo > 0 ->
+ erlang:send_after(Tmo, self(), {release, Pos});
+ true ->
+ undefined
+ end,
+ Hold = #hold{port = Port, n = N, tmo = Tmo, tref = TRef},
+ {reply, ok, setelement(Pos, S, Hold)}.
+
+do_release(HPos, S) when element(HPos, S) == undefined ->
+ {reply, {error, enoent}, S};
+do_release(HPos, S) ->
+ #hold{port = Port, tref = TRef, q = Q} = element(HPos, S),
+ lists:foreach(fun(M) -> gen_tcp:send(Port, M), erlang:yield() end, Q),
+ catch erlang:cancel_timer(TRef),
+ receive
+ {release, HPos} -> ok
+ after 0 ->
+ ok
+ end,
+ {reply, ok, setelement(HPos, S, undefined)}.
+
+listen(Parent, LSock) ->
+ monitor(process, Parent),
+ do_listen(Parent, LSock).
+
+do_listen(Parent, LSock) ->
+ %% So annoying there is no select-like sematic for this
+ case gen_tcp:accept(LSock, ?ACCEPT_TMO) of
+ {ok, Sock} ->
+ Parent ! {accept, Sock},
+ gen_tcp:controlling_process(Sock, Parent),
+ Parent ! {activate, Sock},
+ do_flush(Parent, Sock),
+ gen_tcp:close(LSock);
+ {error, timeout} ->
+ receive
+ DOWN when element(1, DOWN) == 'DOWN' ->
+ ok;
+ stop ->
+ ok
+ after 1 ->
+ do_listen(Parent, LSock)
+ end;
+ Error ->
+ gen_tcp:close(LSock),
+ exit({accept,Error})
+ end.
+
+do_flush(Parent, Sock) ->
+ receive
+ {Tcp, Sock, _} = Msg when Tcp == tcp; Tcp == tcp_error ->
+ Parent ! Msg,
+ do_flush(Parent, Sock);
+ {tcp_closed, Sock} = Msg ->
+ Parent ! Msg,
+ do_flush(Parent, Sock)
+ after 1 ->
+ ok
+ end.
+
+do_accept(Local, S) ->
+ case gen_tcp:connect(S#state.peer_addr, S#state.peer_port, [{active, true}, binary]) of
+ {ok, Peer} ->
+ S#state{local = Local, peer = Peer};
+ Error ->
+ exit({connect, Error})
+ end.
+
+do_local(Data, S) when is_binary(Data) ->
+ TxH = S#state.tx_hold,
+ if TxH == undefined ->
+ gen_tcp:send(S#state.peer, Data),
+ S;
+ TxH#hold.n == 0 ->
+ lists:foreach(fun(M) -> gen_tcp:send(S#state.peer, M) end, TxH#hold.q),
+ gen_tcp:send(S#state.peer, Data),
+ catch erlang:cancel_timer(TxH#hold.tref),
+ TxP = #state.tx_hold,
+ receive
+ {release, TxP} ->
+ ok
+ after 0 ->
+ ok
+ end,
+ S#state{tx_hold = undefined};
+ true ->
+ Q = TxH#hold.q ++ [Data],
+ N = if is_integer(TxH#hold.n) ->
+ TxH#hold.n -1;
+ true ->
+ TxH#hold.n
+ end,
+ S#state{tx_hold = TxH#hold{q = Q, n = N}}
+ end;
+do_local(Error, _S) ->
+ exit({local, Error}).
+
+do_peer(Data, S) when is_binary(Data) ->
+ RxH = S#state.rx_hold,
+ if RxH == undefined ->
+ gen_tcp:send(S#state.local, Data),
+ S;
+ RxH#hold.n == 0 ->
+ lists:foreach(fun(M) -> gen_tcp:send(S#state.local, M) end, RxH#hold.q),
+ gen_tcp:send(S#state.local, Data),
+ catch erlang:cancel_timer(RxH#hold.tref),
+ RxP = #state.rx_hold,
+ receive
+ {release, RxP} ->
+ ok
+ after 0 ->
+ ok
+ end,
+ S#state{rx_hold = undefined};
+ true ->
+ Q = RxH#hold.q ++ [Data],
+ N = if is_integer(RxH#hold.n) ->
+ RxH#hold.n -1;
+ true ->
+ RxH#hold.n
+ end,
+ S#state{rx_hold = RxH#hold{q = Q, n = N}}
+ end;
+do_peer(Error, _S) ->
+ exit({peer, Error}).
+
diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index cb74a27638..850b1cbf6b 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -49,6 +49,7 @@ all() ->
init_per_suite(Config) ->
+ catch crypto:stop(),
case (catch crypto:start()) of
ok ->
ssh:start(),
diff --git a/lib/ssh/test/ssh_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 0ce8eec906..925b02a437 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -68,6 +68,7 @@ groups() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
+ catch crypto:stop(),
case (catch crypto:start()) of
ok ->
DataDir = ?config(data_dir, Config),
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
index cc34cc0793..eac7575486 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -52,6 +52,7 @@ groups() ->
init_per_suite(Config) ->
catch ssh:stop(),
+ catch crypto:stop(),
case catch crypto:start() of
ok ->
DataDir = ?config(data_dir, Config),
diff --git a/lib/ssh/test/ssh_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
new file mode 100644
index 0000000000..6e1595f9fa
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -0,0 +1,192 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2015. 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_sup_SUITE).
+-include_lib("common_test/include/ct.hrl").
+-include_lib("ssh/src/ssh.hrl").
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-define(WAIT_FOR_SHUTDOWN, 500).
+-define(USER, "Alladin").
+-define(PASSWD, "Sesame").
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+all() ->
+ [default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile].
+
+groups() ->
+ [].
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+init_per_suite(Config) ->
+ Port = ssh_test_lib:inet_port(node()),
+ PrivDir = ?config(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ [{userdir, UserDir},{port, Port}, {host, "localhost"}, {host_ip, any} | Config].
+
+end_per_suite(_) ->
+ ok.
+
+init_per_testcase(sshc_subtree, Config) ->
+ ssh:start(),
+ SystemDir = ?config(data_dir, Config),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]}]),
+ [{server, {Pid, Host, Port}} | Config];
+init_per_testcase(Case, Config) ->
+ end_per_testcase(Case, Config),
+ ssh:start(),
+ Config.
+end_per_testcase(sshc_subtree, Config) ->
+ {Pid,_,_} = ?config(server, Config),
+ ssh:stop_daemon(Pid),
+ ssh:stop();
+end_per_testcase(_, _Config) ->
+ ssh:stop().
+
+%%-------------------------------------------------------------------------
+%% Test cases
+%%-------------------------------------------------------------------------
+default_tree() ->
+ [{doc, "Makes sure the correct processes are started and linked,"
+ "in the default case."}].
+default_tree(Config) when is_list(Config) ->
+ TopSupChildren = supervisor:which_children(ssh_sup),
+ 2 = length(TopSupChildren),
+ {value, {sshc_sup, _, supervisor,[sshc_sup]}} =
+ lists:keysearch(sshc_sup, 1, TopSupChildren),
+ {value, {sshd_sup, _,supervisor,[sshd_sup]}} =
+ lists:keysearch(sshd_sup, 1, TopSupChildren),
+ [] = supervisor:which_children(sshc_sup),
+ [] = supervisor:which_children(sshd_sup).
+
+sshc_subtree() ->
+ [{doc, "Make sure the sshc subtree is correct"}].
+sshc_subtree(Config) when is_list(Config) ->
+ {_Pid, Host, Port} = ?config(server, Config),
+ UserDir = ?config(userdir, Config),
+
+ [] = supervisor:which_children(sshc_sup),
+ {ok, Pid1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
+ [{_, _,supervisor,[ssh_connection_handler]}] =
+ supervisor:which_children(sshc_sup),
+ {ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]),
+ [{_,_,supervisor,[ssh_connection_handler]},
+ {_,_,supervisor,[ssh_connection_handler]}] =
+ supervisor:which_children(sshc_sup),
+ ssh:close(Pid1),
+ [{_,_,supervisor,[ssh_connection_handler]}] =
+ supervisor:which_children(sshc_sup),
+ ssh:close(Pid2),
+ ct:sleep(?WAIT_FOR_SHUTDOWN),
+ [] = supervisor:which_children(sshc_sup).
+
+sshd_subtree() ->
+ [{doc, "Make sure the sshd subtree is correct"}].
+sshd_subtree(Config) when is_list(Config) ->
+ HostIP = ?config(host_ip, Config),
+ Port = ?config(port, Config),
+ SystemDir = ?config(data_dir, Config),
+ ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]}]),
+ [{{server,ssh_system_sup, HostIP, Port, ?DEFAULT_PROFILE},
+ Daemon, supervisor,
+ [ssh_system_sup]}] =
+ supervisor:which_children(sshd_sup),
+ check_sshd_system_tree(Daemon, Config),
+ ssh:stop_daemon(HostIP, Port),
+ ct:sleep(?WAIT_FOR_SHUTDOWN),
+ [] = supervisor:which_children(sshd_sup).
+
+sshd_subtree_profile() ->
+ [{doc, "Make sure the sshd subtree using profile option is correct"}].
+sshd_subtree_profile(Config) when is_list(Config) ->
+ HostIP = ?config(host_ip, Config),
+ Port = ?config(port, Config),
+ Profile = ?config(profile, Config),
+ SystemDir = ?config(data_dir, Config),
+
+ {ok, _} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]},
+ {profile, Profile}]),
+ [{{server,ssh_system_sup, HostIP,Port,Profile},
+ Daemon, supervisor,
+ [ssh_system_sup]}] =
+ supervisor:which_children(sshd_sup),
+ check_sshd_system_tree(Daemon, Config),
+ ssh:stop_daemon(HostIP, Port, Profile),
+ ct:sleep(?WAIT_FOR_SHUTDOWN),
+ [] = supervisor:which_children(sshd_sup).
+
+
+check_sshd_system_tree(Daemon, Config) ->
+ Host = ?config(host, Config),
+ Port = ?config(port, Config),
+ UserDir = ?config(userdir, Config),
+ {ok, Client} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
+
+ [{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
+ {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}]
+ = supervisor:which_children(Daemon),
+
+ [{{server,ssh_connection_sup, _,_},
+ ConnectionSup, supervisor,
+ [ssh_connection_sup]},
+ {{server,ssh_channel_sup,_ ,_},
+ ChannelSup,supervisor,
+ [ssh_channel_sup]}] = supervisor:which_children(SubSysSup),
+
+ [{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}] =
+ supervisor:which_children(AccSup),
+
+ [{_, _, worker,[ssh_connection_handler]}] =
+ supervisor:which_children(ConnectionSup),
+
+ [] = supervisor:which_children(ChannelSup),
+
+ ssh_sftp:start_channel(Client),
+
+ [{_, _,worker,[ssh_channel]}] =
+ supervisor:which_children(ChannelSup),
+ ssh:close(Client).
+
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/id_dsa b/lib/ssh/test/ssh_sup_SUITE_data/id_dsa
new file mode 100644
index 0000000000..d306f8b26e
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/id_dsa
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
+APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
+/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
+kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
+JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
+OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
+uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
+Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
+ZU8w8Q+H7z0j+a+70x2iAw==
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/id_rsa b/lib/ssh/test/ssh_sup_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
+DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
+zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
+AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
+TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
+CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
+SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
+z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
+WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
+sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
+xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
+dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
+ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_sup_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index b8abf5e80e..d08afdfb90 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2015. 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
@@ -358,3 +358,16 @@ do_inet_port(Node) ->
{ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
{ok, Port} = rpc:call(Node, inet, port, [Socket]),
{Port, Socket}.
+
+openssh_sanity_check(Config) ->
+ ssh:start(),
+ case ssh:connect("localhost", 22, [{password,""}]) of
+ {ok, Pid} ->
+ ssh:close(Pid),
+ ssh:stop(),
+ Config;
+ Err ->
+ Str = lists:append(io_lib:format("~p", [Err])),
+ ssh:stop(),
+ {skip, Str}
+ end.
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index af70eeb46c..277e3a1b08 100644
--- a/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ b/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2015. 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
@@ -60,13 +60,14 @@ groups() ->
].
init_per_suite(Config) ->
+ catch crypto:stop(),
case catch crypto:start() of
ok ->
case gen_tcp:connect("localhost", 22, []) of
{error,econnrefused} ->
{skip,"No openssh deamon"};
_ ->
- Config
+ ssh_test_lib:openssh_sanity_check(Config)
end;
_Else ->
{skip,"Could not start crypto!"}
@@ -166,9 +167,11 @@ erlang_client_openssh_server_exec_compressed() ->
[{doc, "Test that compression option works"}].
erlang_client_openssh_server_exec_compressed(Config) when is_list(Config) ->
+ CompressAlgs = [zlib, '[email protected]',none],
ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
{user_interaction, false},
- {compression, zlib}]),
+ {preferred_algorithms,
+ [{compression,CompressAlgs}]}]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId,
"echo testing", infinity),
@@ -326,8 +329,11 @@ erlang_server_openssh_client_exec_compressed(Config) when is_list(Config) ->
PrivDir = ?config(priv_dir, Config),
KnownHosts = filename:join(PrivDir, "known_hosts"),
+%% CompressAlgs = [zlib, '[email protected]'], % Does not work
+ CompressAlgs = [zlib],
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {compression, zlib},
+ {preferred_algorithms,
+ [{compression, CompressAlgs}]},
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
@@ -545,6 +551,7 @@ receive_hej() ->
receive_logout() ->
receive
<<"logout">> ->
+ extra_logout(),
receive
<<"Connection closed">> ->
ok
@@ -564,6 +571,14 @@ receive_normal_exit(Shell) ->
ct:fail({unexpected_msg, Other})
end.
+extra_logout() ->
+ receive
+ <<"logout">> ->
+ ok
+ after 500 ->
+ ok
+ end.
+
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Check if we have a "newer" ssh client that supports these test cases
diff --git a/lib/ssh/test/ssh_unicode_SUITE.erl b/lib/ssh/test/ssh_unicode_SUITE.erl
index cc916673b3..07d51335c6 100644
--- a/lib/ssh/test/ssh_unicode_SUITE.erl
+++ b/lib/ssh/test/ssh_unicode_SUITE.erl
@@ -55,6 +55,7 @@ all() ->
init_per_suite(Config) ->
+ catch crypto:stop(),
case {file:native_name_encoding(), (catch crypto:start())} of
{utf8, ok} ->
ssh:start(),
diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl
new file mode 100644
index 0000000000..861c7ab3dd
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE.erl
@@ -0,0 +1,206 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2014-2015. 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/.2
+%%
+%% 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_upgrade_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+-record(state, {
+ config,
+ root_dir,
+ server,
+ client,
+ connection,
+ soft
+ }).
+
+
+%%%================================================================
+%%%
+%%% CommonTest callbacks
+%%%
+all() ->
+ [
+ minor_upgrade,
+ major_upgrade
+ ].
+
+init_per_suite(Config0) ->
+ catch crypto:stop(),
+ try {crypto:start(), erlang:system_info({wordsize, internal}) ==
+ erlang:system_info({wordsize, external})} of
+ {ok, true} ->
+ case ct_release_test:init(Config0) of
+ {skip, Reason} ->
+ {skip, Reason};
+ Config ->
+ ssh:start(),
+ Config
+ end;
+ {ok, false} ->
+ {skip, "Test server will not handle halfwordemulator correctly. Skip as halfwordemulator is deprecated"}
+ catch _:_ ->
+ {skip, "Crypto did not start"}
+ end.
+
+end_per_suite(Config) ->
+ ct_release_test:cleanup(Config),
+ ssh:stop(),
+ crypto:stop(),
+ UserDir = ?config(priv_dir, Config),
+ ssh_test_lib:clean_rsa(UserDir).
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+end_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%%================================================================
+%%%
+%%% Test cases
+%%%
+major_upgrade(Config) when is_list(Config) ->
+ ct_release_test:upgrade(ssh, major,{?MODULE, #state{config = Config}}, Config).
+
+minor_upgrade(Config) when is_list(Config) ->
+ ct_release_test:upgrade(ssh, minor,{?MODULE, #state{config = Config}}, Config).
+
+%%%================================================================
+%%%
+%%% ct_release_test callbacks
+%%%
+
+%%%----------------------------------------------------------------
+%%% Initialyze system before upgrade test starts.
+%%% Called by ct_release_test:upgrade/4
+upgrade_init(CTData, State) ->
+ {ok, AppUp={_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssh),
+ ct:pal("AppUp: ~p", [AppUp]),
+ ct:pal("Up: ~p", [Up]),
+ case Soft = is_soft(Up) of
+ %% It is symmetrical, if upgrade is soft so is downgrade
+ true ->
+ setup_server_client(State#state{soft = Soft});
+ false ->
+ State#state{soft = Soft}
+ end.
+
+%%%----------------------------------------------------------------
+%%% Check that upgrade was successful
+%%% Called by ct_release_test:upgrade/4
+upgrade_upgraded(_, #state{soft=false} = State) ->
+ test_hard(State, "upgrade");
+
+upgrade_upgraded(_, State) ->
+ test_soft(State, "upgrade1").
+
+%%%----------------------------------------------------------------
+%%% Check that downgrade was successful.
+%%% Called by ct_release_test:upgrade/4
+upgrade_downgraded(_, #state{soft=false} = State) ->
+ test_hard(State, "downgrade");
+
+upgrade_downgraded(_, #state{soft=true} = State) ->
+ test_soft(State, "downgrade1").
+
+%%%================================================================
+%%%
+%%% Private functions
+%%%
+
+is_soft([{restart_application, ssh}]) ->
+ false;
+is_soft(_) ->
+ true.
+
+
+test_hard(State0, FileName) ->
+ ct:pal("test_hard State0=~p, FileName=~p",[State0, FileName]),
+ State = setup_server_client(State0),
+ test_connection(FileName, random_contents(), State).
+
+test_soft(State0, FileName) ->
+ ct:pal("test_soft State0=~p, FileName=~p",[State0, FileName]),
+ State = test_connection(FileName, random_contents(), State0),
+ setup_server_client( close(State) ).
+
+
+setup_server_client(#state{config=Config} = State) ->
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ FtpRootDir = filename:join(PrivDir, "ftp_root"),
+ catch file:make_dir(FtpRootDir),
+
+ SFTP = ssh_sftpd:subsystem_spec([{root,FtpRootDir},{cwd,FtpRootDir}]),
+
+ {Server,Host,Port} = ssh_test_lib:daemon([{system_dir,DataDir},
+ {user_passwords,[{"hej","hopp"}]},
+ {subsystems,[SFTP]}]),
+
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host, Port, [{user_interaction,false},
+ {silently_accept_hosts,true},
+ {user_dir,DataDir},
+ {user,"hej"},
+ {password,"hopp"}]),
+ State#state{server = Server,
+ client = ChannelPid,
+ connection = Connection}.
+
+
+test_connection(FileName, FileContents,
+ #state{client = ChannelPid,
+ root_dir = FtpRootDir} = State) ->
+ ct:pal("test_connection Writing with ssh_sftp:write_file",[]),
+ case ssh_sftp:write_file(ChannelPid, FileName, FileContents) of
+ ok ->
+ case ssh_sftp:read_file(ChannelPid, FileName) of
+ {ok,FileContents} ->
+ State;
+ {ok,Unexpected} ->
+ ct:fail("Expected ~p but got ~p from sftp:read_file(~p,..) in RootDir ~p",
+ [FileContents,Unexpected,FileName,FtpRootDir]
+ );
+ Other ->
+ ct:fail("ssh_sftp:read_file(~p,~p) -> ~p~n"
+ "ssh_sftp:list_dir(~p,\".\") -> ~p",
+ [ChannelPid,FileName,Other,
+ ChannelPid, catch ssh_sftp:list_dir(ChannelPid, ".")])
+ end;
+
+ Other ->
+ ct:fail("ssh_sftp:write_file(~p,~p,~p) -> ~p",[ChannelPid,FileName,FileContents,Other])
+ end.
+
+
+close(#state{server = Server,
+ connection = Connection} = State) ->
+ ssh:close(Connection),
+ ssh:stop_daemon(Server),
+ State#state{server = undefined,
+ client = undefined,
+ connection = undefined}.
+
+
+random_contents() -> list_to_binary( random_chars(3) ).
+
+random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)].
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa b/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa
new file mode 100644
index 0000000000..d306f8b26e
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/id_dsa
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvAIBAAKBgQDfi2flSTZZofwT4yQT0NikX/LGNT7UPeB/XEWe/xovEYCElfaQ
+APFixXvEgXwoojmZ5kiQRKzLM39wBP0jPERLbnZXfOOD0PDnw0haMh7dD7XKVMod
+/EigVgHf/qBdM2M8yz1s/rRF7n1UpLSypziKjkzCm7JoSQ2zbWIPdmBIXwIVAMgP
+kpr7Sq3O7sHdb8D601DRjoExAoGAMOQxDfB2Fd8ouz6G96f/UOzRMI/Kdv8kYYKW
+JIGY+pRYrLPyYzUeJznwZreOJgrczAX+luHnKFWJ2Dnk5CyeXk67Wsr7pJ/4MBMD
+OKeIS0S8qoSBN8+Krp79fgA+yS3IfqbkJLtLu4EBaCX4mKQIX4++k44d4U5lc8pt
++9hlEI8CgYEAznKxx9kyC6bVo7LUYKaGhofRFt0SYFc5PVmT2VUGRs1R6+6DPD+e
+uEO6IhFct7JFSRbP9p0JD4Uk+3zlZF+XX6b2PsZkeV8f/02xlNGUSmEzCSiNg1AX
+Cy/WusYhul0MncWCHMcOZB5rIvU/aP5EJJtn3xrRaz6u0SThF6AnT34CFQC63czE
+ZU8w8Q+H7z0j+a+70x2iAw==
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa b/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/id_rsa
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQD1OET+3O/Bvj/dtjxDTXmj1oiJt4sIph5kGy0RfjoPrZfaS+CU
+DhakCmS6t2ivxWFgtpKWaoGMZMJqWj6F6ZsumyFl3FPBtujwY/35cgifrI9Ns4Tl
+zR1uuengNBmV+WRQ5cd9F2qS6Z8aDQihzt0r8JUqLcK+VQbrmNzboCCQQwIDAQAB
+AoGAPQEyqPTt8JUT7mRXuaacjFXiweAXhp9NEDpyi9eLOjtFe9lElZCrsUOkq47V
+TGUeRKEm9qSodfTbKPoqc8YaBJGJPhUaTAcha+7QcDdfHBvIsgxvU7ePVnlpXRp3
+CCUEMPhlnx6xBoTYP+fRU0e3+xJIPVyVCqX1jAdUMkzfRoECQQD6ux7B1QJAIWyK
+SGkbDUbBilNmzCFNgIpOP6PA+bwfi5d16diTpra5AX09keQABAo/KaP1PdV8Vg0p
+z4P3A7G3AkEA+l+AKG6m0kQTTBMJDqOdVPYwe+5GxunMaqmhokpEbuGsrZBl5Dvd
+WpcBjR7jmenrhKZRIuA+Fz5HPo/UQJPl1QJBAKxstDkeED8j/S2XoFhPKAJ+6t39
+sUVICVTIZQeXdmzHJXCcUSkw8+WEhakqw/3SyW0oaK2FSWQJFWJUZ+8eJj8CQEh3
+xeduB5kKnS9CvzdeghZqX6QvVosSdtlUmfUYW/BgH5PpHKTP8wTaeld3XldZTpMJ
+dKiMkUw2+XYROVUrubUCQD+Na1LhULlpn4ISEtIEfqpdlUhxDgO15Wg8USmsng+x
+ICliVOSQtwaZjm8kwaFt0W7XnpnDxbRs37vIEbIMWak=
+-----END RSA PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts b/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/known_hosts
@@ -0,0 +1 @@
+
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8semM4q843337
+zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RWRWzjaxSB
+6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4QIDAQAB
+AoGANmvJzJO5hkLuvyDZHKfAnGTtpifcR1wtSa9DjdKUyn8vhKF0mIimnbnYQEmW
+NUUb3gXCZLi9PvkpRSVRrASDOZwcjoU/Kvww163vBUVb2cOZfFhyn6o2Sk88Tt++
+udH3hdjpf9i7jTtUkUe+QYPsia+wgvvrmn4QrahLAH86+kECQQDx5gFeXTME3cnW
+WMpFz3PPumduzjqgqMMWEccX4FtQkMX/gyGa5UC7OHFyh0N/gSWvPbRHa8A6YgIt
+n8DO+fh5AkEAzbqX4DOn8NY6xJIi42q7l/2jIA0RkB6P7YugW5NblhqBZ0XDnpA5
+sMt+rz+K07u9XZtxgh1xi7mNfwY6lEAMqQJBAJBEauCKmRj35Z6OyeQku59SPsnY
++SJEREVvSNw2lH9SOKQQ4wPsYlTGbvKtNVZgAcen91L5MmYfeckYE/fdIZECQQCt
+64zxsTnM1I8iFxj/gP/OYlJBikrKt8udWmjaghzvLMEw+T2DExJyb9ZNeT53+UMB
+m6O+B/4xzU/djvp+0hbhAkAemIt+rA5kTmYlFndhpvzkSSM8a2EXsO4XIPgGWCTT
+tQKS/tTly0ADMjN/TVy11+9d6zcqadNVuHXHGtR4W0GR
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_upgrade_SUITE_data/ssh_host_rsa_key.pub
@@ -0,0 +1,5 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCZX+4FBDwZIh9y/Uxee1VJnEXlowpz2yDKwj8
+semM4q843337zbNfxHmladB1lpz2NqyxI175xMIJuDxogyZdsOxGnFAzAnthR4dqL/RW
+RWzjaxSB6IAO9SPYVVlrpZ+1hsjLW79fwXK/yc8VdhRuWTeQiRgYY2ek8+OKbOqz4Q==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 40bda0c19f..cef9992f1b 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 3.2.3
+SSH_VSN = 4.0
APP_VSN = "ssh-$(SSH_VSN)"