aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/src/introduction.xml2
-rw-r--r--lib/ssh/doc/src/notes.xml480
-rw-r--r--lib/ssh/doc/src/ssh.xml167
-rw-r--r--lib/ssh/doc/src/ssh_app.xml55
-rw-r--r--lib/ssh/doc/src/ssh_channel.xml2
-rw-r--r--lib/ssh/doc/src/ssh_protocol.xml4
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml12
-rw-r--r--lib/ssh/doc/src/using_ssh.xml4
-rw-r--r--lib/ssh/src/Makefile5
-rw-r--r--lib/ssh/src/ssh.app.src8
-rw-r--r--lib/ssh/src/ssh.appup.src6
-rw-r--r--lib/ssh/src/ssh.erl1048
-rw-r--r--lib/ssh/src/ssh.hrl80
-rw-r--r--lib/ssh/src/ssh_acceptor.erl145
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl63
-rw-r--r--lib/ssh/src/ssh_auth.erl495
-rw-r--r--lib/ssh/src/ssh_bits.erl46
-rw-r--r--lib/ssh/src/ssh_channel.erl9
-rw-r--r--lib/ssh/src/ssh_cli.erl43
-rw-r--r--lib/ssh/src/ssh_connect.hrl6
-rw-r--r--lib/ssh/src/ssh_connection.erl73
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl814
-rw-r--r--lib/ssh/src/ssh_connection_sup.erl30
-rw-r--r--lib/ssh/src/ssh_dbg.erl222
-rw-r--r--lib/ssh/src/ssh_dbg.hrl27
-rw-r--r--lib/ssh/src/ssh_file.erl43
-rw-r--r--lib/ssh/src/ssh_info.erl2
-rw-r--r--lib/ssh/src/ssh_io.erl64
-rw-r--r--lib/ssh/src/ssh_message.erl48
-rw-r--r--lib/ssh/src/ssh_options.erl904
-rw-r--r--lib/ssh/src/ssh_sftp.erl225
-rw-r--r--lib/ssh/src/ssh_sftpd.erl83
-rw-r--r--lib/ssh/src/ssh_sftpd_file_api.erl4
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl77
-rw-r--r--lib/ssh/src/ssh_sup.erl77
-rw-r--r--lib/ssh/src/ssh_system_sup.erl196
-rw-r--r--lib/ssh/src/ssh_transport.erl870
-rw-r--r--lib/ssh/src/ssh_transport.hrl30
-rw-r--r--lib/ssh/src/sshc_sup.erl45
-rw-r--r--lib/ssh/src/sshd_sup.erl111
-rw-r--r--lib/ssh/test/Makefile8
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl92
-rw-r--r--lib/ssh/test/property_test/ssh_eqc_encode_decode.erl374
-rw-r--r--lib/ssh/test/ssh.cover1
-rw-r--r--lib/ssh/test/ssh.spec4
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE.erl270
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa3846
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384.pub1
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa5217
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521.pub1
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key3846
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384.pub1
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key5217
-rw-r--r--lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521.pub1
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl280
-rw-r--r--lib/ssh/test/ssh_bench.spec3
-rw-r--r--lib/ssh/test/ssh_bench_SUITE.erl252
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_dsa (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_dsa)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/id_rsa (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/id_rsa)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521 (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key)0
-rw-r--r--lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key.pub (renamed from lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key.pub)0
-rw-r--r--lib/ssh/test/ssh_bench_dev_null.erl58
-rw-r--r--lib/ssh/test/ssh_benchmark_SUITE.erl563
-rw-r--r--lib/ssh/test/ssh_connection_SUITE.erl206
-rw-r--r--lib/ssh/test/ssh_eqc_event_handler.erl43
-rw-r--r--lib/ssh/test/ssh_key_cb.erl6
-rw-r--r--lib/ssh/test/ssh_key_cb_options.erl4
-rw-r--r--lib/ssh/test/ssh_options_SUITE.erl160
-rw-r--r--lib/ssh/test/ssh_property_test_SUITE.erl15
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE.erl244
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test4
-rw-r--r--lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli5
-rw-r--r--lib/ssh/test/ssh_relay.erl7
-rw-r--r--lib/ssh/test/ssh_renegotiate_SUITE.erl40
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE.erl28
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_sftpd_SUITE.erl172
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl7
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa15
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_rsa_key16
-rw-r--r--lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_rsa_key.pub5
-rw-r--r--lib/ssh/test/ssh_sup_SUITE.erl144
-rw-r--r--lib/ssh/test/ssh_test_lib.erl294
-rw-r--r--lib/ssh/test/ssh_to_openssh_SUITE.erl234
-rw-r--r--lib/ssh/test/ssh_trpt_test_lib.erl47
-rw-r--r--lib/ssh/test/ssh_upgrade_SUITE.erl4
-rw-r--r--lib/ssh/vsn.mk2
102 files changed, 6510 insertions, 3773 deletions
diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml
index ca84528f3d..b7a73e2597 100644
--- a/lib/ssh/doc/src/introduction.xml
+++ b/lib/ssh/doc/src/introduction.xml
@@ -195,8 +195,6 @@
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> -
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index fd15c334a3..f93753f1d2 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2016</year>
+ <year>2004</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -30,6 +30,468 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The internal handling of SSH options is re-written.</p>
+ <p>
+ Previously there were no checks if a client option was
+ given to a daemon or vice versa. This is corrected now.
+ If your code has e.g. a client-only option in a call to
+ start a daemon, the call will fail.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-12872</p>
+ </item>
+ <item>
+ <p>
+ Modernization of key exchange algorithms. See
+ draft-ietf-curdle-ssh-kex-sha2 for a discussion.</p>
+ <p>
+ Removed an outdated weak algorithm and added stronger
+ replacements to keep interoperability with other modern
+ ssh clients and servers. The default ordering of the
+ algorithms is also adjusted.</p>
+ <p>
+ Retired: The nowadays unsecure key-exchange
+ <c>diffie-hellman-group1-sha1</c> is not enabled by
+ default, but can be enabled with the option
+ <c>preferred-algorithms</c>.</p>
+ <p>
+ Added: The new stronger key-exchange
+ <c>diffie-hellman-group16-sha512</c>,
+ <c>diffie-hellman-group18-sha512</c> and
+ <c>diffie-hellman-group14-sha256</c> are added and
+ enabled by default.</p>
+ <p>
+ The questionable [RFC 6194] sha1-based algorithms
+ <c>diffie-hellman-group-exchange-sha1</c> and
+ <c>diffie-hellman-group14-sha1</c> are however still kept
+ enabled by default for compatibility with ancient clients
+ and servers that lack modern key-exchange alternatives.
+ When the draft-ietf-curdle-ssh-kex-sha2 becomes an rfc,
+ those sha1-based algorithms and
+ <c>diffie-hellman-group1-sha1</c> will be deprecated by
+ IETF. They might then be removed from the default list in
+ Erlang/OTP.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14110</p>
+ </item>
+ <item>
+ <p>
+ Modernized internal representation of sftp by use of
+ maps.</p>
+ <p>
+ Own Id: OTP-14117</p>
+ </item>
+ <item>
+ <p>
+ The Extension Negotiation Mechanism and the extension
+ <c>server-sig-algs</c> in
+ draft-ietf-curdle-ssh-ext-info-05 are implemented.</p>
+ <p>
+ The related draft-ietf-curdle-rsa-sha2-05 is implemented
+ and introduces the signature algorithms
+ <c>rsa-sha2-256</c> and <c>rsa-sha2-512</c>.</p>
+ <p>
+ Own Id: OTP-14193</p>
+ </item>
+ <item>
+ <p>
+ The 'timeout' and 'connect_timeout' handling in
+ ssh_sftp:start_channel documentation is clarified.</p>
+ <p>
+ Own Id: OTP-14216</p>
+ </item>
+ <item>
+ <p>
+ The functions <c>ssh:connect</c>, <c>ssh:shell</c> and
+ <c>ssh:start_channel</c> now accept an IP-tuple as Host
+ destination argument.</p>
+ <p>
+ Own Id: OTP-14243</p>
+ </item>
+ <item>
+ <p>
+ The function <c>ssh:daemon_info/1</c> now returns Host
+ and Profile as well as the Port info in the property
+ list.</p>
+ <p>
+ Own Id: OTP-14259</p>
+ </item>
+ <item>
+ <p>
+ Removed the option <c>public_key_alg</c> which was
+ deprecated in 18.2. Use <c>pref_public_key_algs</c>
+ instead.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14263</p>
+ </item>
+ <item>
+ <p>
+ The SSH application is refactored regarding daemon
+ starting. The resolution of contradicting <c>Host</c>
+ argument and <c>ip</c> option were not described. There
+ were also strange corner cases when the <c>'any'</c>
+ value was used in <c>Host</c> argument or <c>ip</c>
+ option. This is (hopefully) resolved now, but it may
+ cause incompatibilities for code using both <c>Host</c>
+ and the <c>ip</c> option. The value 'loopback' has been
+ added for a correct way of naming those addresses.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14264</p>
+ </item>
+ <item>
+ <p>
+ The supervisor code is refactored. The naming of
+ listening IP-Port-Profile triples are slightly changed to
+ improve consistency in strange corner cases as resolved
+ by OTP-14264</p>
+ <p>
+ Own Id: OTP-14267 Aux Id: OTP-14266 </p>
+ </item>
+ <item>
+ <p>
+ The <c>idle_time</c> option can now be used in daemons.</p>
+ <p>
+ Own Id: OTP-14312</p>
+ </item>
+ <item>
+ <p>
+ Added test cases for IETF-CURDLE Extension Negotiation
+ (ext-info)</p>
+ <p>
+ Own Id: OTP-14361</p>
+ </item>
+ <item>
+ <p>
+ Testcases for IETF-CURDLE extension
+ <c>server-sig-algs</c> including <c>rsa-sha2-*</c></p>
+ <p>
+ Own Id: OTP-14362 Aux Id: OTP-14361 </p>
+ </item>
+ <item>
+ <p>
+ The option <c>auth_methods</c> can now also be used in
+ clients to select which authentication options that are
+ used and in which order.</p>
+ <p>
+ Own Id: OTP-14399</p>
+ </item>
+ <item>
+ <p>
+ Checks that a ECDSA public key (<c>ecdsa-sha2-nistp*</c>)
+ stored in a file has the correct size.</p>
+ <p>
+ Own Id: OTP-14410</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ ssh:daemon_info/1 crashed if the listening IP was not
+ 'any'</p>
+ <p>
+ Own Id: OTP-14298 Aux Id: seq13294 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix bug when opening connections. If the tcp setup
+ failed, that would in some cases not result in an error
+ return value.</p>
+ <p>
+ Own Id: OTP-14108</p>
+ </item>
+ <item>
+ <p>
+ Reduce information leakage in case of decryption errors.</p>
+ <p>
+ Own Id: OTP-14109</p>
+ </item>
+ <item>
+ <p>
+ The key exchange algorithm
+ diffie-hellman-group-exchange-sha* has a server-option
+ <c>{dh_gex_limits,{Min,Max}}</c>. There was a hostkey
+ signature validation error on the client side if the
+ option was used and the <c>Min</c> or the <c>Max</c>
+ differed from the corresponding values obtained from the
+ client.</p>
+ <p>
+ This bug is now corrected.</p>
+ <p>
+ Own Id: OTP-14166</p>
+ </item>
+ <item>
+ <p>
+ The sftpd server now correctly uses <c>root_dir</c> and
+ <c>cwd</c> when resolving file paths if both are
+ provided. The <c>cwd</c> handling is also corrected.</p>
+ <p>
+ Thanks to kape1395!</p>
+ <p>
+ Own Id: OTP-14225 Aux Id: PR-1331, PR-1335 </p>
+ </item>
+ <item>
+ <p>
+ Ssh_cli used a function that does not handle non-utf8
+ unicode correctly.</p>
+ <p>
+ Own Id: OTP-14230 Aux Id: ERL-364 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The implementation of the key exchange algorithms
+ diffie-hellman-group-exchange-sha* are optimized, up to a
+ factor of 11 for the slowest ( = biggest and safest)
+ group size.</p>
+ <p>
+ Own Id: OTP-14169 Aux Id: seq-13261 </p>
+ </item>
+ <item>
+ <p>
+ The ssh host key fingerprint generation now also takes a
+ list of algorithms and returns a list of corresponding
+ fingerprints. See
+ <c>public_key:ssh_hostkey_fingerprint/2</c> and the
+ option <c>silently_accept_hosts</c> in
+ <c>ssh:connect</c>.</p>
+ <p>
+ Own Id: OTP-14223</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A file read with an sftp client could loose data if the
+ packet_size is set to larger than 64k. This is corrected
+ now in such a way that the packet_size is silently
+ lowered if there is a risk for data loss.</p>
+ <p>
+ Own Id: OTP-13857 Aux Id: ERL-238, OTP-13858 </p>
+ </item>
+ <item>
+ <p>
+ When user defined SSH shell REPL process exits with
+ reason normal, the SSH channel callback module should
+ report successful exit status to the SSH client. This
+ provides simple way for SSH clients to check for
+ successful completion of executed commands. (Thanks to
+ isvilen)</p>
+ <p>
+ Own Id: OTP-13905 Aux Id: PR-1173 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Extended the option <c>silently_accept_hosts</c> for
+ <c>ssh:connect</c> to make it possible for the client to
+ check the SSH host key fingerprint string. Se the
+ reference manual for SSH.</p>
+ <p>
+ Own Id: OTP-13887 Aux Id: OTP-13888 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Re-negotiation problems with OpenSSH client solved.</p>
+ <p>
+ Own Id: OTP-13972</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ If a client illegaly sends an info-line and then
+ immediatly closes the TCP-connection, a badmatch
+ exception was raised.</p>
+ <p>
+ Own Id: OTP-13966</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Intermittent ssh ERROR REPORT mentioning
+ nonblocking_sender</p>
+ <p>
+ Own Id: OTP-13953 Aux Id: seq13199 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Handle all possible exit values that should be
+ interpreted as {error, closed}. Failing to do so could
+ lead to unexpected crashes for users of the ssh
+ application.</p>
+ <p>
+ Own Id: OTP-13932 Aux Id: seq13189 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Upgrade of an established client connection could crash
+ because the ssh client supervisors children had wrong
+ type. This is fixed now.</p>
+ <p>
+ Own Id: OTP-13782 Aux Id: seq13158 </p>
+ </item>
+ <item>
+ <p>
+ Partly checks the public key early in public key
+ authorization</p>
+ <p>
+ Own Id: OTP-13847 Aux Id:
+ defensics-ssh3.1.0-190243,205277,219318 </p>
+ </item>
+ <item>
+ <p>
+ Corrected handling of SHA for ECDSA (Elliptic curve
+ public keys)</p>
+ <p>
+ Own Id: OTP-13850 Aux Id: defensics-ssh3.1.0-214168 </p>
+ </item>
+ <item>
+ <p>
+ Problems found by test suites as well as by
+ Codenomicon/Defensics fixed: - reduce max random padding
+ to 15 bytes (Codenomicon/Defensics) - inclomplete pdu
+ handling (Codenomicon/Defensics) - badmatch in test suite
+ - non-blocking send fixes deadlock in
+ ssh_connection_SUITE:interrupted_send</p>
+ <p>
+ Own Id: OTP-13854</p>
+ </item>
+ <item>
+ <p>
+ Caller is now notified when a tcp close is received.</p>
+ <p>
+ Own Id: OTP-13859 Aux Id: seq13177 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Use application:ensure_all_started/2 instead of
+ hard-coding deps</p>
+ <p>
+ Own Id: OTP-13843 Aux Id: PR-1147 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSH client does not any longer retry a bad password given
+ as option to ssh:connect et al.</p>
+ <p>
+ Own Id: OTP-13674 Aux Id: TR-HU92273 </p>
+ </item>
+ <item>
+ <p>
+ Removed possible hanging risk for a certain timing
+ sequence when communicating client and server executes on
+ the same node.</p>
+ <p>
+ Own Id: OTP-13715</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.3</title>
<section><title>Improvements and New Features</title>
@@ -108,6 +570,22 @@
</section>
+<section><title>Ssh 4.2.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ SSH client does not any longer retry a bad password given
+ as option to ssh:connect et al.</p>
+ <p>
+ Own Id: OTP-13674 Aux Id: TR-HU92273 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index e6c54d27bf..ea7e975ef5 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2015</year>
+ <year>2004</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -153,7 +153,7 @@
<item>
<p>IP version to use.</p>
</item>
- <tag><c><![CDATA[{user_dir, string()}]]></c></tag>
+ <tag><marker id="opt_user_dir"></marker><c><![CDATA[{user_dir, string()}]]></c></tag>
<item>
<p>Sets the user directory, that is, the directory containing
<c>ssh</c> configuration files for the user, such as
@@ -175,12 +175,48 @@
supplied with this option.
</p>
</item>
- <tag><c><![CDATA[{silently_accept_hosts, boolean()}]]></c></tag>
+ <tag>
+ <c><![CDATA[{silently_accept_hosts, boolean()}]]></c> <br/>
+ <c><![CDATA[{silently_accept_hosts, CallbackFun}]]></c> <br/>
+ <c><![CDATA[{silently_accept_hosts, {HashAlgoSpec, CallbackFun} }]]></c> <br/>
+ <br/>
+ <c><![CDATA[HashAlgoSpec = crypto:digest_type() | [ crypto:digest_type() ] ]]></c><br/>
+ <c><![CDATA[CallbackFun = fun(PeerName, FingerPrint) -> boolean()]]></c><br/>
+ <c><![CDATA[PeerName = string()]]></c><br/>
+ <c><![CDATA[FingerPrint = string() | [ string() ] ]]></c>
+ </tag>
<item>
- <p>When <c>true</c>, hosts are added to the
- file <c><![CDATA[known_hosts]]></c> without asking the user.
- Defaults to <c>false</c>.
- </p>
+ <p>This option guides the <c>connect</c> function how to act when the connected server presents a Host
+ Key that the client has not seen before. The default is to ask the user with a question on stdio of whether to
+ accept or reject the new Host Key.
+ See also the option <seealso marker="#opt_user_dir"><c>user_dir</c></seealso>
+ for the path to the file <c>known_hosts</c> where previously accepted Host Keys are recorded.
+ </p>
+ <p>The option can be given in three different forms as seen above:</p>
+ <list>
+ <item>The value is a <c>boolean()</c>. The value <c>true</c> will make the client accept any unknown
+ Host Key without any user interaction. The value <c>false</c> keeps the default behaviour of asking the
+ the user on stdio.
+ </item>
+ <item>A <c>CallbackFun</c> will be called and the boolean return value <c>true</c> will make the client
+ accept the Host Key. A return value of <c>false</c> will make the client to reject the Host Key and therefore
+ also the connection will be closed. The arguments to the fun are:
+ <list type="bulleted">
+ <item><c>PeerName</c> - a string with the name or address of the remote host.</item>
+ <item><c>FingerPrint</c> - the fingerprint of the Host Key as
+ <seealso marker="public_key:public_key#ssh_hostkey_fingerprint-1">public_key:ssh_hostkey_fingerprint/1</seealso>
+ calculates it.
+ </item>
+ </list>
+ </item>
+ <item>A tuple <c>{HashAlgoSpec, CallbackFun}</c>. The <c>HashAlgoSpec</c> specifies which hash algorithm
+ shall be used to calculate the fingerprint used in the call of the <c>CallbackFun</c>. The <c>HashALgoSpec</c>
+ is either an atom or a list of atoms as the first argument in
+ <seealso marker="public_key:public_key#ssh_hostkey_fingerprint-2">public_key:ssh_hostkey_fingerprint/2</seealso>.
+ If it is a list of hash algorithm names, the <c>FingerPrint</c> argument in the <c>CallbackFun</c> will be
+ a list of fingerprints in the same order as the corresponding name in the <c>HashAlgoSpec</c> list.
+ </item>
+ </list>
</item>
<tag><c><![CDATA[{user_interaction, boolean()}]]></c></tag>
<item>
@@ -190,7 +226,7 @@
supplying a password. Defaults to <c>true</c>.
Even if user interaction is allowed it can be
suppressed by other options, such as <c>silently_accept_hosts</c>
- and <c>password</c>. However, those optins are not always desirable
+ and <c>password</c>. However, those options are not always desirable
to use from a security point of view.</p>
</item>
@@ -207,28 +243,15 @@
<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 will be removed in OTP 20, but is kept for compatibility. It is ignored if
- the preferred <c>pref_public_key_algs</c> option is used.</p>
- </note>
- <p>Sets the preferred public key algorithm to use for user
- authentication. If the preferred algorithm fails,
- the other algorithm is tried. If <c>{public_key_alg, 'ssh-rsa'}</c> is set, it is translated
- to <c>{pref_public_key_algs, ['ssh-rsa','ssh-dss']}</c>. If it is
- <c>{public_key_alg, 'ssh-dss'}</c>, it is translated
- to <c>{pref_public_key_algs, ['ssh-dss','ssh-rsa']}</c>.
- </p>
- </item>
-
<tag><c><![CDATA[{pref_public_key_algs, list()}]]></c></tag>
<item>
<p>List of user (client) public key algorithms to try to use.</p>
- <p>The default value is
- <c><![CDATA[['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521'] ]]></c>
+ <p>The default value is the <c>public_key</c> entry in
+ <seealso marker="#default_algorithms/0">ssh:default_algorithms/0</seealso>.
+ </p>
+ <p>If there is no public key of a specified type available, the corresponding entry is ignored.
+ Note that the available set is dependent on the underlying cryptolib and current user's public keys.
</p>
- <p>If there is no public key of a specified type available, the corresponding entry is ignored.</p>
</item>
<tag><c><![CDATA[{preferred_algorithms, algs_list()}]]></c></tag>
@@ -272,6 +295,15 @@
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[{auth_methods, string()}]]></c></tag>
+ <item>
+ <p>Comma-separated string that determines which
+ authentication methods that the client shall support and
+ in which order they are tried. Defaults to
+ <c><![CDATA["publickey,keyboard-interactive,password"]]></c></p>
+ </item>
+
<tag><c><![CDATA[{user, string()}]]></c></tag>
<item>
<p>Provides a username. If this option is not given, <c>ssh</c>
@@ -279,6 +311,7 @@
<c><![CDATA[USER]]></c> on UNIX,
<c><![CDATA[USERNAME]]></c> on Windows).</p>
</item>
+
<tag><c><![CDATA[{password, string()}]]></c></tag>
<item>
<p>Provides a password for password authentication.
@@ -286,6 +319,30 @@
password, if the password authentication method is
attempted.</p>
</item>
+
+ <!--tag><c><![CDATA[{send_ext_info, boolean()}]]></c></tag>
+ <item>
+ <p>Send a list of extensions to the server if the server has asked for it. See
+ <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info">Draft-ietf-curdle-ssh-ext-info (work in progress)</url> for details.
+ </p>
+ <p>Currently the client do not react on any extensions.
+ </p>
+ <p>Default value is <c>true</c>.
+ </p>
+ </item-->
+
+ <tag><c><![CDATA[{recv_ext_info, boolean()}]]></c></tag>
+ <item>
+ <p>Tell the server that the client accepts extension negotiation. See
+ <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info">Draft-ietf-curdle-ssh-ext-info (work in progress)</url> for details.
+ </p>
+ <p>Currently implemented extension is <c>server-sig-algs</c> which is the list of the server's preferred
+ user's public key algorithms.
+ </p>
+ <p>Default value is <c>true</c>.
+ </p>
+ </item>
+
<tag><c><![CDATA[{key_cb, key_cb()}]]></c></tag>
<item>
<p>Module implementing the behaviour <seealso
@@ -295,6 +352,7 @@
module via the options passed to it under the key 'key_cb_private'.
</p>
</item>
+
<tag><c><![CDATA[{quiet_mode, atom() = boolean()}]]></c></tag>
<item>
<p>If <c>true</c>, the client does not print anything on authorization.</p>
@@ -358,7 +416,7 @@
on the given port.</fsummary>
<type>
<v>Port = integer()</v>
- <v>HostAddress = ip_address() | any</v>
+ <v>HostAddress = ip_address() | any | loopback</v>
<v>Options = [{Option, Value}]</v>
<v>Option = atom()</v>
<v>Value = term()</v>
@@ -369,6 +427,26 @@
<p>Starts a server listening for SSH connections on the given
port. If the <c>Port</c> is 0, a random free port is selected. See
<seealso marker="#daemon_info/1">daemon_info/1</seealso> about how to find the selected port number.</p>
+
+ <p>Please note that by historical reasons both the <c>HostAddress</c> argument and the inet socket option
+ <c>ip</c> set the listening address. This is a source of possible inconsistent settings.</p>
+
+ <p>The rules for handling the two address passing options are:</p>
+ <list>
+ <item>if <c>HostAddress</c> is an IP-address, that IP-address is the listening address.
+ An 'ip'-option will be discarded if present.</item>
+
+ <item>if <c>HostAddress</c> is <c>loopback</c>, the listening address
+ is <c>loopback</c> and an loopback address will be choosen by the underlying layers.
+ An 'ip'-option will be discarded if present.</item>
+
+ <item>if <c>HostAddress</c> is <c>any</c> and no 'ip'-option is present, the listening address is
+ <c>any</c> and the socket will listen to all addresses</item>
+
+ <item>if <c>HostAddress</c> is <c>any</c> and an 'ip'-option is present, the listening address is
+ set to the value of the 'ip'-option</item>
+ </list>
+
<p>Options:</p>
<taglist>
<tag><c><![CDATA[{inet, inet | inet6}]]></c></tag>
@@ -425,6 +503,7 @@
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>
+ <p>Note that the client is free to use any order and to exclude methods.</p>
</item>
<tag><c><![CDATA[{auth_method_kb_interactive_data, PromptTexts}]]></c>
@@ -629,6 +708,27 @@
</p>
</item>
+ <tag><c><![CDATA[{send_ext_info, boolean()}]]></c></tag>
+ <item>
+ <p>Send a list of extensions to the client if the client has asked for it. See
+ <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info">Draft-ietf-curdle-ssh-ext-info (work in progress)</url> for details.
+ </p>
+ <p>Currently implemented extension is sending <c>server-sig-algs</c> which is the list of the server's preferred
+ user's public key algorithms.
+ </p>
+ <p>Default value is <c>true</c>.
+ </p>
+ </item>
+
+ <!--tag><c><![CDATA[{recv_ext_info, boolean()}]]></c></tag>
+ <item>
+ <p>Tell the client that the server accepts extension negotiation. See
+ <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info">Draft-ietf-curdle-ssh-ext-info (work in progress)</url> for details.
+ </p>
+ <p>Default value is <c>true</c>.
+ </p>
+ </item-->
+
<tag><c><![CDATA[{key_cb, key_cb()}]]></c></tag>
<item>
<p>Module implementing the behaviour <seealso
@@ -678,6 +778,12 @@
<p><c>Peer</c> is in the format of <c>{Host,Port}</c>.</p>
</item>
+ <tag><c><![CDATA[{idle_time, integer()}]]></c></tag>
+ <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>
@@ -690,9 +796,10 @@
</func>
<func>
- <name>daemon_info(Daemon) -> {ok, [{port,Port}]} | {error,Error}</name>
+ <name>daemon_info(Daemon) -> {ok, [DaemonInfo]} | {error,Error}</name>
<fsummary>Get info about a daemon</fsummary>
<type>
+ <v>DaemonInfo = {port,Port::pos_integer()} | {listen_address, any|ip_address()} | {profile,atom()}</v>
<v>Port = integer()</v>
<v>Error = bad_daemon_ref</v>
</type>
@@ -756,7 +863,7 @@
<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>
+ manual page in Kernel.</p>
</desc>
</func>
@@ -769,7 +876,7 @@
<desc>
<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>
+ manual page in Kernel.</p>
</desc>
</func>
diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml
index f6ce44c015..33ec7aaee0 100644
--- a/lib/ssh/doc/src/ssh_app.xml
+++ b/lib/ssh/doc/src/ssh_app.xml
@@ -4,7 +4,7 @@
<appref>
<header>
<copyright>
- <year>2012</year><year>2015</year>
+ <year>2012</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -109,7 +109,7 @@
</section>
<section>
<title>Host Keys</title>
- <p>RSA and DSA host keys are supported and are
+ <p>RSA, DSA and ECDSA host keys are supported and are
expected to be found in files named <c>ssh_host_rsa_key</c>,
<c>ssh_host_dsa_key</c> and <c>ssh_host_ecdsa_key</c>.
</p>
@@ -146,7 +146,10 @@
<item>diffie-hellman-group-exchange-sha1</item>
<item>diffie-hellman-group-exchange-sha256</item>
<item>diffie-hellman-group14-sha1</item>
- <item>diffie-hellman-group1-sha1</item>
+ <item>diffie-hellman-group14-sha256</item>
+ <item>diffie-hellman-group16-sha512</item>
+ <item>diffie-hellman-group18-sha512</item>
+ <item>(diffie-hellman-group1-sha1, retired: can be enabled with the <c>preferred_algorithms</c> option)</item>
</list>
</item>
@@ -158,6 +161,8 @@
<item>ecdsa-sha2-nistp521</item>
<item>ssh-rsa</item>
<item>ssh-dss</item>
+ <item>rsa-sha2-256</item>
+ <item>rsa-sha2-512</item>
</list>
</item>
@@ -173,21 +178,23 @@
<tag>Encryption algorithms (ciphers)</tag>
<item>
<list type="bulleted">
- <item>[email protected] (AEAD_AES_128_GCM)</item>
- <item>[email protected] (AEAD_AES_256_GCM)</item>
+ <item>[email protected]</item>
+ <item>[email protected]</item>
<item>aes128-ctr</item>
<item>aes192-ctr</item>
<item>aes256-ctr</item>
<item>aes128-cbc</item>
<item>3des-cbc</item>
+ <item>(AEAD_AES_128_GCM, not enabled per default)</item>
+ <item>(AEAD_AES_256_GCM, not enabled per default)</item>
</list>
+ <p>See the text at the description of <seealso marker="#rfc5647_note">the rfc 5647 further down</seealso>
+ for more information regarding AEAD_AES_*_GCM.
+ </p>
<p>Following the internet de-facto standard, the cipher and mac algorithm AEAD_AES_128_GCM is selected when the
cipher [email protected] is negotiated. The cipher and mac algorithm AEAD_AES_256_GCM is selected when the
cipher [email protected] is negotiated.
</p>
- <p>See the text at the description of <seealso marker="#rfc5647_note">the rfc 5647 further down</seealso>
- for more information.
- </p>
</item>
<tag>Compression algorithms</tag>
@@ -203,7 +210,7 @@
<section>
<title>Unicode support</title>
<p>Unicode filenames are supported 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.
+ <seealso marker="kernel:file">file</seealso> manual page in Kernel for information about this subject.
</p>
<p>The shell and the cli both support unicode.
</p>
@@ -232,7 +239,11 @@
</item>
<item><url href="https://tools.ietf.org/html/rfc4253">RFC 4253</url>, The Secure Shell (SSH) Transport Layer Protocol.
- <p></p>
+ <p>Except</p>
+ <list type="bulleted">
+ <item>8.1. diffie-hellman-group1-sha1. Disabled by default, can be enabled with the <c>preferred_algorithms</c> option.</item>
+ </list>
+ <p/>
</item>
<item><url href="https://tools.ietf.org/html/rfc4254">RFC 4254</url>, The Secure Shell (SSH) Connection Protocol.
@@ -306,6 +317,30 @@
<p>Comment: Defines hmac-sha2-256 and hmac-sha2-512
</p>
</item>
+
+ <item><url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-kex-sha2">Draft-ietf-curdle-ssh-kex-sha2 (work in progress)</url>, Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH).
+ <p>Deviations:</p>
+ <list type="bulleted">
+ <item>The <c>diffie-hellman-group1-sha1</c> is not enabled by default, but is still supported and can be enabled
+ with the option <c>preferred-algorithms</c></item>
+ <item>The questionable sha1-based algorithms <c>diffie-hellman-group-exchange-sha1</c> and
+ <c>diffie-hellman-group14-sha1</c> are still enabled by default for compatibility with ancient clients and servers.
+ They can be disabled with the option <c>preferred-algorithms</c></item>
+ </list>
+ <p/>
+ </item>
+
+ <item><url href="https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2">Draft-ietf-curdle-rsa-sha2 (work in progress)</url>, Use of RSA Keys with SHA-2 256 and 512 in Secure Shell (SSH).
+ </item>
+
+ <item><url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-ext-info">Draft-ietf-curdle-ssh-ext-info (work in progress)</url>, Extension Negotiation in Secure Shell (SSH).
+ <p>Implemented are:</p>
+ <list type="bulleted">
+ <item>The Extension Negotiation Mechanism</item>
+ <item>The extension <c>server-sig-algs</c></item>
+ </list>
+ <p/>
+ </item>
</list>
diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml
index 907b0b3bec..7b598494f7 100644
--- a/lib/ssh/doc/src/ssh_channel.xml
+++ b/lib/ssh/doc/src/ssh_channel.xml
@@ -139,7 +139,7 @@
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>.
+ marker="stdlib:proc_lib">proc_lib(3)</seealso> manual page in STDLIB.
The user is responsible for any initialization of the process
and must call <seealso marker = "#init-1">ssh_channel:init/1</seealso>.
</p>
diff --git a/lib/ssh/doc/src/ssh_protocol.xml b/lib/ssh/doc/src/ssh_protocol.xml
index 7288266cf7..a0032ab449 100644
--- a/lib/ssh/doc/src/ssh_protocol.xml
+++ b/lib/ssh/doc/src/ssh_protocol.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2013</year><year>2013</year>
+ <year>2013</year><year>2016</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -138,8 +138,6 @@
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> -
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index eb6f43d417..ed7fbf9cf3 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2005</year><year>2016</year>
+ <year>2005</year><year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -558,8 +558,14 @@
<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>
+ <p>There are two ways to set a timeout for the underlying ssh connection:</p>
+ <list>
+ <item>If the connection timeout option <c>connect_timeout</c> is set, that value
+ is used also for the negotiation timeout and this option (<c>timeout</c>) is ignored.</item>
+ <item>Otherwise, this option (<c>timeout</c>) is used as the negotiation timeout
+ only and there is no connection timeout set</item>
+ </list>
+ <p>The value defaults to <c>infinity</c>.</p>
</item>
<tag>
<c><![CDATA[{sftp_vsn, integer()}]]></c>
diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml
index 0861c641c7..ab307624e6 100644
--- a/lib/ssh/doc/src/using_ssh.xml
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -5,7 +5,7 @@
<header>
<copyright>
<year>2012</year>
- <year>2016</year>
+ <year>2017</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -305,7 +305,7 @@ ok = erl_tar:close(HandleRead),
<code type="erl" >
-module(ssh_echo_server).
--behaviour(ssh_subsystem).
+-behaviour(ssh_daemon_channel).
-record(state, {
n,
id,
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 69d5a47f83..9e8d80c71f 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2004-2016. All Rights Reserved.
+# Copyright Ericsson AB 2004-2017. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -51,6 +51,7 @@ MODULES= \
ssh_sup \
sshc_sup \
sshd_sup \
+ ssh_options \
ssh_connection_sup \
ssh_connection \
ssh_connection_handler \
@@ -96,7 +97,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 ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
+INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl ssh_dbg.hrl
# ----------------------------------------------------
# FLAGS
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 3245ba5197..974292fde1 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -7,6 +7,7 @@
ssh_app,
ssh_acceptor,
ssh_acceptor_sup,
+ ssh_options,
ssh_auth,
ssh_message,
ssh_bits,
@@ -41,11 +42,10 @@
{env, []},
{mod, {ssh_app, []}},
{runtime_dependencies, [
- "crypto-3.3",
+ "crypto-3.7.3",
"erts-6.0",
"kernel-3.0",
- "public_key-1.1",
- "stdlib-3.0"
+ "public_key-1.4",
+ "stdlib-3.3"
]}]}.
-
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index e38cecf226..2540720c41 100644
--- a/lib/ssh/src/ssh.appup.src
+++ b/lib/ssh/src/ssh.appup.src
@@ -1,7 +1,7 @@
%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -20,9 +20,13 @@
{"%VSN%",
[
+ {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []}
+ ]},
{<<".*">>, [{restart_application, ssh}]}
],
[
+ {<<"4.3.2">>, [{load_module, ssh_channel, soft_purge, soft_purge, []}
+ ]},
{<<".*">>, [{restart_application, ssh}]}
]
}.
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
index 0570853a9b..5ebab43c30 100644
--- a/lib/ssh/src/ssh.erl
+++ b/lib/ssh/src/ssh.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
-include("ssh_connect.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").
+-include_lib("kernel/include/inet.hrl").
-export([start/0, start/1, stop/0,
connect/2, connect/3, connect/4,
@@ -40,10 +41,24 @@
]).
%%% Type exports
--export_type([connection_ref/0,
- channel_id/0
+-export_type([ssh_daemon_ref/0,
+ ssh_connection_ref/0,
+ ssh_channel_id/0,
+ role/0,
+ subsystem_spec/0,
+ subsystem_name/0,
+ channel_callback/0,
+ channel_init_args/0,
+ algs_list/0,
+ alg_entry/0,
+ simple_algs/0,
+ double_algs/0
]).
+-opaque ssh_daemon_ref() :: daemon_ref() .
+-opaque ssh_connection_ref() :: connection_ref() .
+-opaque ssh_channel_id() :: channel_id().
+
%%--------------------------------------------------------------------
-spec start() -> ok | {error, term()}.
-spec start(permanent | transient | temporary) -> ok | {error, term()}.
@@ -52,16 +67,15 @@
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
- application:start(crypto),
- application:start(asn1),
- application:start(public_key),
- application:start(ssh).
+ start(temporary).
start(Type) ->
- application:start(crypto, Type),
- application:start(asn1),
- application:start(public_key, Type),
- application:start(ssh, Type).
+ case application:ensure_all_started(ssh, Type) of
+ {ok, _} ->
+ ok;
+ Other ->
+ Other
+ end.
%%--------------------------------------------------------------------
-spec stop() -> ok | {error, term()}.
@@ -72,55 +86,63 @@ stop() ->
application:stop(ssh).
%%--------------------------------------------------------------------
--spec connect(port(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
+-spec connect(inet:socket(), proplists:proplist()) -> ok_error(connection_ref()).
+
+-spec connect(inet:socket(), proplists:proplist(), timeout()) -> ok_error(connection_ref())
+ ; (string(), inet:port_number(), proplists:proplist()) -> ok_error(connection_ref()).
--spec connect(port(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}
- ; (string(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
+-spec connect(string(), inet:port_number(), proplists:proplist(), timeout()) -> ok_error(connection_ref()).
--spec connect(string(), integer(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}.
%%
%% Description: Starts an ssh connection.
%%--------------------------------------------------------------------
-connect(Socket, Options) ->
- connect(Socket, Options, infinity).
+connect(Socket, UserOptions) when is_port(Socket),
+ is_list(UserOptions) ->
+ connect(Socket, UserOptions, infinity).
-connect(Socket, Options, Timeout) when is_port(Socket) ->
- case handle_options(Options) of
+connect(Socket, UserOptions, Timeout) when is_port(Socket),
+ is_list(UserOptions) ->
+ case ssh_options:handle_options(client, UserOptions) of
{error, Error} ->
{error, Error};
- {_SocketOptions, SshOptions} ->
- case valid_socket_to_use(Socket, Options) of
- ok ->
+ Options ->
+ case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
+ ok ->
{ok, {Host,_Port}} = inet:sockname(Socket),
- Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions],
+ Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options),
ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
{error,SockError} ->
{error,SockError}
end
end;
-connect(Host, Port, Options) when is_integer(Port), Port>0 ->
- connect(Host, Port, Options, infinity).
+connect(Host, Port, UserOptions) when is_integer(Port),
+ Port>0,
+ is_list(UserOptions) ->
+ connect(Host, Port, UserOptions, infinity).
-connect(Host, Port, Options, Timeout) ->
- case handle_options(Options) of
+connect(Host0, Port, UserOptions, Timeout) when is_integer(Port),
+ Port>0,
+ is_list(UserOptions) ->
+ case ssh_options:handle_options(client, UserOptions) of
{error, _Reason} = Error ->
Error;
- {SocketOptions, SshOptions} ->
- {_, Transport, _} = TransportOpts =
- proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}),
- ConnectionTimeout = proplists:get_value(connect_timeout, Options, infinity),
- try Transport:connect(Host, Port, [ {active, false} | SocketOptions], ConnectionTimeout) of
+ Options ->
+ {_, Transport, _} = TransportOpts = ?GET_OPT(transport, Options),
+ ConnectionTimeout = ?GET_OPT(connect_timeout, Options),
+ SocketOpts = [{active,false} | ?GET_OPT(socket_options,Options)],
+ Host = mangle_connect_address(Host0, SocketOpts),
+ try Transport:connect(Host, Port, SocketOpts, ConnectionTimeout) of
{ok, Socket} ->
- Opts = [{user_pid,self()}, {host,Host} | SshOptions],
+ Opts = ?PUT_INTERNAL_OPT([{user_pid,self()}, {host,Host}], Options),
ssh_connection_handler:start_connection(client, Socket, Opts, Timeout);
{error, Reason} ->
{error, Reason}
catch
- exit:{function_clause, _} ->
+ exit:{function_clause, _F} ->
{error, {options, {transport, TransportOpts}}};
exit:badarg ->
- {error, {options, {socket_options, SocketOptions}}}
+ {error, {options, {socket_options, SocketOpts}}}
end
end.
@@ -128,93 +150,191 @@ connect(Host, Port, Options, Timeout) ->
-spec close(pid()) -> ok.
%%
%% Description: Closes an ssh connection.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
close(ConnectionRef) ->
ssh_connection_handler:stop(ConnectionRef).
%%--------------------------------------------------------------------
-spec connection_info(pid(), [atom()]) -> [{atom(), term()}].
%%
-%% Description: Retrieves information about a connection.
-%%--------------------------------------------------------------------
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
connection_info(ConnectionRef, Options) ->
ssh_connection_handler:connection_info(ConnectionRef, Options).
%%--------------------------------------------------------------------
-spec channel_info(pid(), channel_id(), [atom()]) -> [{atom(), term()}].
%%
-%% Description: Retrieves information about a connection.
-%%--------------------------------------------------------------------
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
channel_info(ConnectionRef, ChannelId, Options) ->
ssh_connection_handler:channel_info(ConnectionRef, ChannelId, Options).
%%--------------------------------------------------------------------
--spec daemon(integer()) -> {ok, pid()} | {error, term()}.
--spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
--spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}.
+-spec daemon(inet:port_number()) -> ok_error(daemon_ref()).
+-spec daemon(inet:port_number()|inet:socket(), proplists:proplist()) -> ok_error(daemon_ref()).
+-spec daemon(any | inet:ip_address(), inet:port_number(), proplists:proplist()) -> ok_error(daemon_ref())
+ ;(socket, inet:socket(), proplists:proplist()) -> ok_error(daemon_ref())
+ .
-%% Description: Starts a server listening for SSH connections
+%% Description: Starts a server listening for SSH connections
%% on the given port.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
daemon(Port) ->
daemon(Port, []).
-daemon(Port, Options) when is_integer(Port) ->
- daemon(any, Port, Options);
-daemon(Socket, Options0) when is_port(Socket) ->
- Options = daemon_shell_opt(Options0),
- start_daemon(Socket, Options).
+daemon(Socket, UserOptions) when is_port(Socket) ->
+ try
+ #{} = Options = ssh_options:handle_options(server, UserOptions),
+ case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
+ ok ->
+ {ok, {IP,Port}} = inet:sockname(Socket),
+ finalize_start(IP, Port, ?GET_OPT(profile, Options),
+ ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options),
+ fun(Opts, DefaultResult) ->
+ try ssh_acceptor:handle_established_connection(
+ IP, Port, Opts, Socket)
+ of
+ {error,Error} ->
+ {error,Error};
+ _ ->
+ DefaultResult
+ catch
+ C:R ->
+ {error,{could_not_start_connection,{C,R}}}
+ end
+ end);
+ {error,SockError} ->
+ {error,SockError}
+ end
+ catch
+ throw:bad_fd ->
+ {error,bad_fd};
+ throw:bad_socket ->
+ {error,bad_socket};
+ error:{badmatch,{error,Error}} ->
+ {error,Error};
+ error:Error ->
+ {error,Error};
+ _C:_E ->
+ {error,{cannot_start_daemon,_C,_E}}
+ end;
+
+daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 ->
+ daemon(any, Port, UserOptions).
+
+
+daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535,
+ Host0 == any ; Host0 == loopback ; is_tuple(Host0) ->
+ try
+ {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
+ #{} = Options0 = ssh_options:handle_options(server, UserOptions),
+
+ {{Host,Port}, ListenSocket} =
+ open_listen_socket(Host1, Port0, Options0),
+
+ %% Now Host,Port is what to use for the supervisor to register its name,
+ %% and ListenSocket is for listening on connections. But it is still owned
+ %% by self()...
+
+ finalize_start(Host, Port, ?GET_OPT(profile, Options0),
+ ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0),
+ fun(Opts, Result) ->
+ {_, Callback, _} = ?GET_OPT(transport, Opts),
+ receive
+ {request_control, ListenSocket, ReqPid} ->
+ ok = Callback:controlling_process(ListenSocket, ReqPid),
+ ReqPid ! {its_yours,ListenSocket},
+ Result
+ end
+ end)
+ catch
+ throw:bad_fd ->
+ {error,bad_fd};
+ throw:bad_socket ->
+ {error,bad_socket};
+ error:{badmatch,{error,Error}} ->
+ {error,Error};
+ error:Error ->
+ {error,Error};
+ _C:_E ->
+ {error,{cannot_start_daemon,_C,_E}}
+ end;
+
+daemon(_, _, _) ->
+ {error, badarg}.
+
-daemon(HostAddr, Port, Options0) ->
- Options1 = daemon_shell_opt(Options0),
- {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1),
- start_daemon(Host, Port, Options, Inet).
%%--------------------------------------------------------------------
+-spec daemon_info(daemon_ref()) -> ok_error( [{atom(), term()}] ).
+
daemon_info(Pid) ->
case catch ssh_system_sup:acceptor_supervisor(Pid) of
AsupPid when is_pid(AsupPid) ->
- [Port] =
- [Prt || {{ssh_acceptor_sup,any,Prt,default},
- _WorkerPid,worker,[ssh_acceptor]} <- supervisor:which_children(AsupPid)],
- {ok, [{port,Port}]};
-
+ [{IP,Port,Profile}] =
+ [{IP,Prt,Prf}
+ || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]}
+ <- supervisor:which_children(AsupPid),
+ IP <- [case inet:parse_strict_address(Hst) of
+ {ok,IP} -> IP;
+ _ -> Hst
+ end]
+ ],
+ {ok, [{port,Port},
+ {ip,IP},
+ {profile,Profile}
+ ]};
_ ->
{error,bad_daemon_ref}
end.
%%--------------------------------------------------------------------
--spec stop_listener(pid()) -> ok.
--spec stop_listener(inet:ip_address(), integer()) -> ok.
+-spec stop_listener(daemon_ref()) -> ok.
+-spec stop_listener(inet:ip_address(), inet:port_number()) -> ok.
%%
-%% Description: Stops the listener, but leaves
+%% Description: Stops the listener, but leaves
%% existing connections started by the listener up and running.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
stop_listener(SysSup) ->
ssh_system_sup:stop_listener(SysSup).
stop_listener(Address, Port) ->
stop_listener(Address, Port, ?DEFAULT_PROFILE).
+stop_listener(any, Port, Profile) ->
+ map_ip(fun(IP) ->
+ ssh_system_sup:stop_listener(IP, Port, Profile)
+ end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
stop_listener(Address, Port, Profile) ->
- ssh_system_sup:stop_listener(Address, Port, Profile).
+ map_ip(fun(IP) ->
+ ssh_system_sup:stop_listener(IP, Port, Profile)
+ end, {address,Address}).
%%--------------------------------------------------------------------
--spec stop_daemon(pid()) -> ok.
--spec stop_daemon(inet:ip_address(), integer()) -> ok.
+-spec stop_daemon(daemon_ref()) -> ok.
+-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
+-spec stop_daemon(inet:ip_address(), inet:port_number(), atom()) -> ok.
%%
-%% Description: Stops the listener and all connections started by
+%% Description: Stops the listener and all connections started by
%% the listener.
-%%--------------------------------------------------------------------
+%%--------------------------------------------------------------------
stop_daemon(SysSup) ->
ssh_system_sup:stop_system(SysSup).
stop_daemon(Address, Port) ->
- ssh_system_sup:stop_system(Address, Port, ?DEFAULT_PROFILE).
+ stop_daemon(Address, Port, ?DEFAULT_PROFILE).
+stop_daemon(any, Port, Profile) ->
+ map_ip(fun(IP) ->
+ ssh_system_sup:stop_system(IP, Port, Profile)
+ end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
stop_daemon(Address, Port, Profile) ->
- ssh_system_sup:stop_system(Address, Port, Profile).
+ map_ip(fun(IP) ->
+ ssh_system_sup:stop_system(IP, Port, Profile)
+ end, {address,Address}).
+
%%--------------------------------------------------------------------
--spec shell(port() | string()) -> _.
--spec shell(port() | string(), proplists:proplist()) -> _.
--spec shell(string(), integer(), proplists:proplist()) -> _.
+-spec shell(inet:socket() | string()) -> _.
+-spec shell(inet:socket() | string(), proplists:proplist()) -> _.
+-spec shell(string(), inet:port_number(), proplists:proplist()) -> _.
%% Host = string()
%% Port = integer()
@@ -243,7 +363,7 @@ start_shell({ok, ConnectionRef}) ->
case ssh_connection:session_channel(ConnectionRef, infinity) of
{ok,ChannelId} ->
success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []),
- Args = [{channel_cb, ssh_shell},
+ Args = [{channel_cb, ssh_shell},
{init_args,[ConnectionRef, ChannelId]},
{cm, ConnectionRef}, {channel_id, ChannelId}],
{ok, State} = ssh_channel:init([Args]),
@@ -255,715 +375,111 @@ start_shell(Error) ->
Error.
%%--------------------------------------------------------------------
+-spec default_algorithms() -> algs_list() .
%%--------------------------------------------------------------------
-default_algorithms() ->
+default_algorithms() ->
ssh_transport:default_algorithms().
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-valid_socket_to_use(Socket, Options) ->
- case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of
- {tcp,_,_} ->
- %% Is this tcp-socket a valid socket?
- case {is_tcp_socket(Socket),
- {ok,[{active,false}]} == inet:getopts(Socket, [active])
- }
- of
- {true, true} ->
- ok;
- {true, false} ->
- {error, not_passive_mode};
- _ ->
- {error, not_tcp_socket}
- end;
- {L4,_,_} ->
- {error, {unsupported,L4}}
- end.
-
-is_tcp_socket(Socket) -> {ok,[]} =/= inet:getopts(Socket, [delay_send]).
-
-
-
-daemon_shell_opt(Options) ->
- case proplists:get_value(shell, Options) of
- undefined ->
- [{shell, {shell, start, []}} | Options];
- _ ->
- Options
- end.
-
-daemon_host_inet_opt(HostAddr, Options1) ->
- case HostAddr of
- any ->
- {ok, Host0} = inet:gethostname(),
- {Host0, proplists:get_value(inet, Options1, inet), Options1};
- {_,_,_,_} ->
- {HostAddr, inet,
- [{ip, HostAddr} | Options1]};
- {_,_,_,_,_,_,_,_} ->
- {HostAddr, inet6,
- [{ip, HostAddr} | Options1]}
- end.
-
-
-start_daemon(Socket, Options) ->
- case handle_options(Options) of
- {error, Error} ->
- {error, Error};
- {SocketOptions, SshOptions} ->
- case valid_socket_to_use(Socket, Options) of
- ok ->
- try
- do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions)
- catch
- throw:bad_fd -> {error,bad_fd};
- _C:_E -> {error,{cannot_start_daemon,_C,_E}}
- end;
- {error,SockError} ->
- {error,SockError}
- end
- end.
-
-start_daemon(Host, Port, Options, Inet) ->
- case handle_options(Options) of
- {error, _Reason} = Error ->
- Error;
- {SocketOptions, SshOptions}->
- try
- do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions])
- catch
- throw:bad_fd -> {error,bad_fd};
- _C:_E -> {error,{cannot_start_daemon,_C,_E}}
- end
- end.
-
-do_start_daemon(Socket, SshOptions, SocketOptions) ->
- {ok, {IP,Port}} =
- try {ok,_} = inet:sockname(Socket)
- catch
- _:_ -> throw(bad_socket)
- end,
- Host = fmt_host(IP),
- Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE),
- Opts = [{asocket, Socket},
- {asock_owner,self()},
- {address, Host},
- {port, Port},
- {role, server},
- {socket_opts, SocketOptions},
- {ssh_opts, SshOptions}],
- {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}),
- 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
- %% monent. The name is a legacy name!
- try sshd_sup:start_child(Opts) of
- {error, {already_started, _}} ->
- {error, eaddrinuse};
- Result = {ok,_} ->
- ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket),
- Result;
- Result = {error, _} ->
- Result
- catch
- exit:{noproc, _} ->
- {error, ssh_not_started}
- end;
- Sup ->
- AccPid = ssh_system_sup:acceptor_supervisor(Sup),
- case ssh_acceptor_sup:start_child(AccPid, Opts) of
- {error, {already_started, _}} ->
- {error, eaddrinuse};
- {ok, _} ->
- ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket),
- {ok, Sup};
- Other ->
- Other
- end
- end.
+%% The handle_daemon_args/2 function basically only sets the ip-option in Opts
+%% so that it is correctly set when opening the listening socket.
-do_start_daemon(Host0, Port0, SshOptions, SocketOptions) ->
- {Host,Port1} =
- try
- case proplists:get_value(fd, SocketOptions) of
- undefined ->
- {Host0,Port0};
- Fd when Port0==0 ->
- find_hostport(Fd);
- _ ->
- {Host0,Port0}
- end
- catch
- _:_ -> throw(bad_fd)
- end,
- Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE),
- {Port, WaitRequestControl, Opts0} =
- case Port1 of
- 0 -> %% Allocate the socket here to get the port number...
- {_, Callback, _} =
- proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}),
- {ok,LSock} = ssh_acceptor:callback_listen(Callback, 0, SocketOptions),
- {ok,{_,LPort}} = inet:sockname(LSock),
- {LPort,
- {LSock,Callback},
- [{lsocket,LSock},{lsock_owner,self()}]
- };
- _ ->
- {Port1, false, []}
- end,
- Opts = [{address, Host},
- {port, Port},
- {role, server},
- {socket_opts, SocketOptions},
- {ssh_opts, SshOptions} | Opts0],
- 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
- %% monent. The name is a legacy name!
- try sshd_sup:start_child(Opts) of
- {error, {already_started, _}} ->
- {error, eaddrinuse};
- Result = {ok,_} ->
- sync_request_control(WaitRequestControl),
- Result;
- Result = {error, _} ->
- Result
- catch
- exit:{noproc, _} ->
- {error, ssh_not_started}
- end;
- Sup ->
- AccPid = ssh_system_sup:acceptor_supervisor(Sup),
- case ssh_acceptor_sup:start_child(AccPid, Opts) of
- {error, {already_started, _}} ->
- {error, eaddrinuse};
- {ok, _} ->
- sync_request_control(WaitRequestControl),
- {ok, Sup};
- Other ->
- Other
- end
- end.
+handle_daemon_args(any, Opts) ->
+ case proplists:get_value(ip, Opts) of
+ undefined -> {any, Opts};
+ IP -> {IP, Opts}
+ end;
-sync_request_control(false) ->
- ok;
-sync_request_control({LSock,Callback}) ->
- receive
- {request_control,LSock,ReqPid} ->
- ok = Callback:controlling_process(LSock, ReqPid),
- ReqPid ! {its_yours,LSock},
- ok
+handle_daemon_args(IPaddr, Opts) when is_tuple(IPaddr) ; IPaddr == loopback ->
+ case proplists:get_value(ip, Opts) of
+ undefined -> {IPaddr, [{ip,IPaddr}|Opts]};
+ IPaddr -> {IPaddr, Opts};
+ IP -> {IPaddr, [{ip,IPaddr}|Opts--[{ip,IP}]]} %% Backward compatibility
end.
-find_hostport(Fd) ->
- %% Using internal functions inet:open/8 and inet:close/0.
- %% Don't try this at home unless you know what you are doing!
- {ok,S} = inet:open(Fd, {0,0,0,0}, 0, [], tcp, inet, stream, inet_tcp),
- {ok, HostPort} = inet:sockname(S),
- ok = inet:close(S),
- HostPort.
-
-
-handle_options(Opts) ->
- try handle_option(algs_compatibility(proplists:unfold(Opts)), [], []) of
- {Inet, Ssh} ->
- {handle_ip(Inet), Ssh}
+%%%----------------------------------------------------------------
+valid_socket_to_use(Socket, {tcp,_,_}) ->
+ %% Is this tcp-socket a valid socket?
+ try {is_tcp_socket(Socket),
+ {ok,[{active,false}]} == inet:getopts(Socket, [active])
+ }
+ of
+ {true, true} -> ok;
+ {true, false} -> {error, not_passive_mode};
+ _ -> {error, not_tcp_socket}
catch
- throw:Error ->
- Error
- end.
-
+ _:_ -> {error, bad_socket}
+ end;
-algs_compatibility(Os0) ->
- %% Take care of old options 'public_key_alg' and 'pref_public_key_algs'
- case proplists:get_value(public_key_alg, Os0) of
- undefined ->
- Os0;
- A when is_atom(A) ->
- %% Skip public_key_alg if pref_public_key_algs is defined:
- Os = lists:keydelete(public_key_alg, 1, Os0),
- case proplists:get_value(pref_public_key_algs,Os) of
- undefined when A == 'ssh-rsa' ; A==ssh_rsa ->
- [{pref_public_key_algs,['ssh-rsa','ssh-dss']} | Os];
- undefined when A == 'ssh-dss' ; A==ssh_dsa ->
- [{pref_public_key_algs,['ssh-dss','ssh-rsa']} | Os];
- undefined ->
- throw({error, {eoptions, {public_key_alg,A} }});
- _ ->
- Os
- end;
- V ->
- throw({error, {eoptions, {public_key_alg,V} }})
- end.
+valid_socket_to_use(_, {L4,_,_}) ->
+ {error, {unsupported,L4}}.
-handle_option([], SocketOptions, SshOptions) ->
- {SocketOptions, SshOptions};
-handle_option([{system_dir, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{user_dir, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{user_dir_fun, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{silently_accept_hosts, _} = Opt | Rest], SocketOptions, SshOptions) ->
- 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([{connect_timeout, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{user, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{dsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{rsa_pass_phrase, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{password, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{key_cb, {Module, Options}} | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option({key_cb, Module}),
- handle_ssh_priv_option({key_cb_private, Options}) |
- SshOptions]);
-handle_option([{key_cb, Module} | Rest], SocketOptions, SshOptions) ->
- handle_option([{key_cb, {Module, []}} | Rest], SocketOptions, SshOptions);
-handle_option([{keyboard_interact_fun, _} = 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]);
-handle_option([{infofun, _} = Opt | Rest],SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-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) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-%%Backwards compatibility should not be underscore between ip and v6 in API
-handle_option([{ip_v6_disabled, Value} | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option({ipv6_disabled, Value}) | SshOptions]);
-handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{subsystems, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{ssh_cli, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{shell, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-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([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{dh_gex_groups,_} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{dh_gex_limits,_} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{max_sessions, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{max_channels, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{negotiation_timeout, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{parallel_login, _} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-%% (Is handled by proplists:unfold above:)
-%% handle_option([parallel_login|Rest], SocketOptions, SshOptions) ->
-%% handle_option(Rest, SocketOptions, [handle_ssh_option({parallel_login,true}) | SshOptions]);
-handle_option([{minimal_remote_max_packet_size, _} = Opt|Rest], SocketOptions, SshOptions) ->
- 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([{max_random_length_padding, _Bool} = Opt|Rest], SocketOptions, SshOptions) ->
- handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]);
-handle_option([{tstflg, _} = 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).
-
-
-handle_ssh_option({tstflg,_F} = Opt) -> Opt;
-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) ->
- check_dir(Opt);
-handle_ssh_option({user_dir, Value} = Opt) when is_list(Value) ->
- 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({preferred_algorithms,[_|_]} = Opt) ->
- handle_pref_algs(Opt);
-
-handle_ssh_option({dh_gex_groups,L0}) when is_list(L0) ->
- {dh_gex_groups,
- collect_per_size(
- lists:foldl(
- fun({N,G,P}, Acc) when is_integer(N),N>0,
- is_integer(G),G>0,
- is_integer(P),P>0 ->
- [{N,{G,P}} | Acc];
- ({N,{G,P}}, Acc) when is_integer(N),N>0,
- is_integer(G),G>0,
- is_integer(P),P>0 ->
- [{N,{G,P}} | Acc];
- ({N,GPs}, Acc) when is_list(GPs) ->
- lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0,
- is_integer(Pi),Pi>0 ->
- [{N,{Gi,Pi}} | Acci]
- end, Acc, GPs)
- end, [], L0))};
-
-handle_ssh_option({dh_gex_groups,{Tag,File=[C|_]}}=Opt) when is_integer(C), C>0,
- Tag == file ;
- Tag == ssh_moduli_file ->
- {ok,GroupDefs} =
- case Tag of
- file ->
- file:consult(File);
- ssh_moduli_file ->
- case file:open(File,[read]) of
- {ok,D} ->
- try
- {ok,Moduli} = read_moduli_file(D, 1, []),
- file:close(D),
- {ok, Moduli}
- catch
- _:_ ->
- throw({error, {{eoptions, Opt}, "Bad format in file "++File}})
- end;
- {error,enoent} ->
- throw({error, {{eoptions, Opt}, "File not found:"++File}});
- {error,Error} ->
- throw({error, {{eoptions, Opt}, io_lib:format("Error reading file ~s: ~p",[File,Error])}})
- end
- end,
+is_tcp_socket(Socket) ->
+ case inet:getopts(Socket, [delay_send]) of
+ {ok,[_]} -> true;
+ _ -> false
+ end.
+%%%----------------------------------------------------------------
+open_listen_socket(_Host0, Port0, Options0) ->
+ {ok,LSock} =
+ case ?GET_SOCKET_OPT(fd, Options0) of
+ undefined ->
+ ssh_acceptor:listen(Port0, Options0);
+ Fd when is_integer(Fd) ->
+ %% Do gen_tcp:listen with the option {fd,Fd}:
+ ssh_acceptor:listen(0, Options0)
+ end,
+ {ok,{LHost,LPort}} = inet:sockname(LSock),
+ {{LHost,LPort}, LSock}.
+
+%%%----------------------------------------------------------------
+finalize_start(Host, Port, Profile, Options0, F) ->
try
- handle_ssh_option({dh_gex_groups,GroupDefs})
+ sshd_sup:start_child(Host, Port, Profile, Options0)
+ of
+ {error, {already_started, _}} ->
+ {error, eaddrinuse};
+ {error, Error} ->
+ {error, Error};
+ Result = {ok,_} ->
+ F(Options0, Result)
catch
- _:_ ->
- throw({error, {{eoptions, Opt}, "Bad format in file: "++File}})
- end;
-
-
-handle_ssh_option({dh_gex_limits,{Min,Max}} = Opt) when is_integer(Min), Min>0,
- is_integer(Max), Max>=Min ->
- %% Server
- Opt;
-handle_ssh_option({dh_gex_limits,{Min,I,Max}} = Opt) when is_integer(Min), Min>0,
- is_integer(I), I>=Min,
- is_integer(Max), Max>=I ->
- %% Client
- Opt;
-handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 ->
- case handle_user_pref_pubkey_algs(Value, []) of
- {true, NewOpts} ->
- {pref_public_key_algs, NewOpts};
- _ ->
- throw({error, {eoptions, Opt}})
- end;
-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 ->
- Opt;
-handle_ssh_option({max_channels, Value} = Opt) when is_integer(Value), Value>0 ->
- Opt;
-handle_ssh_option({negotiation_timeout, Value} = Opt) when is_integer(Value); Value == infinity ->
- Opt;
-handle_ssh_option({parallel_login, Value} = Opt) when Value==true ; Value==false ->
- Opt;
-handle_ssh_option({user, Value} = Opt) when is_list(Value) ->
- Opt;
-handle_ssh_option({dsa_pass_phrase, Value} = Opt) when is_list(Value) ->
- Opt;
-handle_ssh_option({rsa_pass_phrase, Value} = Opt) when is_list(Value) ->
- Opt;
-handle_ssh_option({password, Value} = Opt) when is_list(Value) ->
- Opt;
-handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)->
- Opt;
-handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,2) ->
- Opt;
-handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value,4) ->
- Opt;
-handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) ->
- Opt;
-handle_ssh_option({key_cb, {CallbackMod, CallbackOptions}} = Opt) when is_atom(CallbackMod),
- is_list(CallbackOptions) ->
- Opt;
-handle_ssh_option({keyboard_interact_fun, Value} = Opt) when is_function(Value,3) ->
- Opt;
-handle_ssh_option({compression, Value} = Opt) when is_atom(Value) ->
- Opt;
-handle_ssh_option({exec, {Module, Function, _}} = Opt) when is_atom(Module),
- is_atom(Function) ->
- Opt;
-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) ->
- Opt;
-handle_ssh_option({unexpectedfun, Value} = Opt) when is_function(Value,2) ->
- Opt;
-handle_ssh_option({failfun, Value} = Opt) when is_function(Value) ->
- Opt;
-handle_ssh_option({ssh_msg_debug_fun, Value} = Opt) when is_function(Value,4) ->
- Opt;
-
-handle_ssh_option({ipv6_disabled, Value} = Opt) when is_boolean(Value) ->
- throw({error, {{ipv6_disabled, Opt}, option_no_longer_valid_use_inet_option_instead}});
-handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol),
- is_atom(Cb),
- is_atom(ClosTag) ->
- Opt;
-handle_ssh_option({subsystems, Value} = Opt) when is_list(Value) ->
- Opt;
-handle_ssh_option({ssh_cli, {Cb, _}}= Opt) when is_atom(Cb) ->
- Opt;
-handle_ssh_option({ssh_cli, no_cli} = Opt) ->
- Opt;
-handle_ssh_option({shell, {Module, Function, _}} = Opt) when is_atom(Module),
- is_atom(Function) ->
- Opt;
-handle_ssh_option({shell, Value} = Opt) when is_function(Value) ->
- Opt;
-handle_ssh_option({quiet_mode, Value} = Opt) when is_boolean(Value) ->
- Opt;
-handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 ->
- Opt;
-handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) ->
- Opt;
-handle_ssh_option({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({max_random_length_padding, Value} = Opt) when is_integer(Value),
- Value =< 255 ->
- Opt;
-handle_ssh_option({profile, Value} = Opt) when is_atom(Value) ->
- Opt;
-handle_ssh_option(Opt) ->
- throw({error, {eoptions, Opt}}).
-
-handle_ssh_priv_option({key_cb_private, Value} = Opt) when is_list(Value) ->
- Opt.
-
-handle_inet_option({active, _} = Opt) ->
- throw({error, {{eoptions, Opt}, "SSH has built in flow control, "
- "and active is handled internally, user is not allowed"
- "to specify this option"}});
-
-handle_inet_option({inet, Value}) when (Value == inet) or (Value == inet6) ->
- Value;
-handle_inet_option({reuseaddr, _} = Opt) ->
- throw({error, {{eoptions, Opt},"Is set internally, user is not allowed"
- "to specify this option"}});
-%% Option verified by inet
-handle_inet_option(Opt) ->
- Opt.
-
-
-%% Check preferred algs
-
-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 ->
- Inet;
- false ->
- case lists:member(inet6, Inet) of
- true ->
- Inet;
- false ->
- [inet | Inet]
- end
- end.
-
-check_dir({_,Dir} = Opt) ->
- case directory_exist_readable(Dir) of
- ok ->
- Opt;
- {error,Error} ->
- throw({error, {eoptions,{Opt,Error}}})
+ exit:{noproc, _} ->
+ {error, ssh_not_started}
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}
+%%%----------------------------------------------------------------
+map_ip(Fun, {address,IP}) when is_tuple(IP) ->
+ Fun(IP);
+map_ip(Fun, {address,Address}) ->
+ IPs = try {ok,#hostent{h_addr_list=IP0s}} = inet:gethostbyname(Address),
+ IP0s
+ catch
+ _:_ -> []
+ end,
+ map_ip(Fun, IPs);
+map_ip(Fun, IPs) ->
+ lists:map(Fun, IPs).
+
+%%%----------------------------------------------------------------
+mangle_connect_address(A, SockOpts) ->
+ mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
+
+loopback(true) -> {0,0,0,0,0,0,0,1};
+loopback(false) -> {127,0,0,1}.
+
+mangle_connect_address1( loopback, V6flg) -> loopback(V6flg);
+mangle_connect_address1( any, V6flg) -> loopback(V6flg);
+mangle_connect_address1({0,0,0,0}, _) -> loopback(false);
+mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true);
+mangle_connect_address1( IP, _) when is_tuple(IP) -> IP;
+mangle_connect_address1(A, _) ->
+ case catch inet:parse_address(A) of
+ {ok, {0,0,0,0}} -> loopback(false);
+ {ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
+ _ -> A
end.
-
-
-
-collect_per_size(L) ->
- lists:foldr(
- fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc];
- ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc]
- end, [], lists:sort(L)).
-
-read_moduli_file(D, I, Acc) ->
- case io:get_line(D,"") of
- {error,Error} ->
- {error,Error};
- eof ->
- {ok, Acc};
- "#" ++ _ -> read_moduli_file(D, I+1, Acc);
- <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc);
- Data ->
- Line = if is_binary(Data) -> binary_to_list(Data);
- is_list(Data) -> Data
- end,
- try
- [_Time,_Type,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"),
- M = {list_to_integer(Size),
- {list_to_integer(G), list_to_integer(P,16)}
- },
- read_moduli_file(D, I+1, [M|Acc])
- catch
- _:_ ->
- read_moduli_file(D, I+1, Acc)
- end
- end.
-
-handle_user_pref_pubkey_algs([], Acc) ->
- {true, lists:reverse(Acc)};
-handle_user_pref_pubkey_algs([H|T], Acc) ->
- case lists:member(H, ?SUPPORTED_USER_KEYS) of
- true ->
- handle_user_pref_pubkey_algs(T, [H| Acc]);
-
- false when H==ssh_dsa -> handle_user_pref_pubkey_algs(T, ['ssh-dss'| Acc]);
- false when H==ssh_rsa -> handle_user_pref_pubkey_algs(T, ['ssh-rsa'| Acc]);
-
- false ->
- false
- end.
-
-fmt_host({A,B,C,D}) ->
- lists:concat([A,".",B,".",C,".",D]);
-fmt_host(T={_,_,_,_,_,_,_,_}) ->
- lists:flatten(string:join([io_lib:format("~.16B",[A]) || A <- tuple_to_list(T)], ":")).
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 868f3a9181..d6d412db43 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,8 +33,11 @@
-define(REKEY_DATA_TIMOUT, 60000).
-define(DEFAULT_PROFILE, default).
+-define(DEFAULT_TRANSPORT, {tcp, gen_tcp, tcp_closed} ).
+
+-define(MAX_RND_PADDING_LEN, 15).
+
-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
--define(SUPPORTED_USER_KEYS, ['ssh-rsa','ssh-dss','ecdsa-sha2-nistp256','ecdsa-sha2-nistp384','ecdsa-sha2-nistp521']).
-define(FALSE, 0).
-define(TRUE, 1).
@@ -64,14 +67,73 @@
-define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ).
-define(binary(X), << ?STRING(X) >>).
+%% Cipher details
-define(SSH_CIPHER_NONE, 0).
-define(SSH_CIPHER_3DES, 3).
-define(SSH_CIPHER_AUTHFILE, ?SSH_CIPHER_3DES).
+%% Option access macros
+-define(do_get_opt(C,K,O), ssh_options:get_value(C,K,O, ?MODULE,?LINE)).
+-define(do_get_opt(C,K,O,D), ssh_options:get_value(C,K,O,?LAZY(D),?MODULE,?LINE)).
+
+-define(LAZY(D), fun()-> D end).
+
+-define(GET_OPT(Key,Opts), ?do_get_opt(user_options, Key,Opts ) ).
+-define(GET_OPT(Key,Opts,Def), ?do_get_opt(user_options, Key,Opts,Def) ).
+-define(GET_INTERNAL_OPT(Key,Opts), ?do_get_opt(internal_options,Key,Opts ) ).
+-define(GET_INTERNAL_OPT(Key,Opts,Def), ?do_get_opt(internal_options,Key,Opts,Def) ).
+-define(GET_SOCKET_OPT(Key,Opts), ?do_get_opt(socket_options, Key,Opts ) ).
+-define(GET_SOCKET_OPT(Key,Opts,Def), ?do_get_opt(socket_options, Key,Opts,Def) ).
+
+-define(do_put_opt(C,KV,O), ssh_options:put_value(C,KV,O, ?MODULE,?LINE)).
+
+-define(PUT_OPT(KeyVal,Opts), ?do_put_opt(user_options, KeyVal,Opts) ).
+-define(PUT_INTERNAL_OPT(KeyVal,Opts), ?do_put_opt(internal_options,KeyVal,Opts) ).
+-define(PUT_SOCKET_OPT(KeyVal,Opts), ?do_put_opt(socket_options, KeyVal,Opts) ).
+
+-define(do_del_opt(C,K,O), ssh_options:delete_key(C,K,O, ?MODULE,?LINE)).
+-define(DELETE_INTERNAL_OPT(Key,Opts), ?do_del_opt(internal_options,Key,Opts) ).
+
+
+%% Types
+-type role() :: client | server .
+-type ok_error(SuccessType) :: {ok, SuccessType} | {error, any()} .
+-type daemon_ref() :: pid() .
+
+-type subsystem_spec() :: {subsystem_name(), {channel_callback(), channel_init_args()}} .
+-type subsystem_name() :: string() .
+-type channel_callback() :: atom() .
+-type channel_init_args() :: list() .
+
+-type algs_list() :: list( alg_entry() ).
+-type alg_entry() :: {kex, simple_algs()}
+ | {public_key, simple_algs()}
+ | {cipher, double_algs()}
+ | {mac, double_algs()}
+ | {compression, double_algs()} .
+-type simple_algs() :: list( atom() ) .
+-type double_algs() :: list( {client2serverlist,simple_algs()} | {server2client,simple_algs()} )
+ | simple_algs() .
+
+-type options() :: #{socket_options := socket_options(),
+ internal_options := internal_options(),
+ option_key() => any()
+ }.
+
+-type socket_options() :: proplists:proplist().
+-type internal_options() :: #{option_key() => any()}.
+
+-type option_key() :: atom().
+
+
+
+%% Records
-record(ssh,
{
- role, %% client | server
- peer, %% string version of peer address
+ role :: client | role(),
+ peer :: undefined |
+ {inet:hostname(),
+ {inet:ip_address(),inet:port_number()}}, %% string version of peer address
c_vsn, %% client version {Major,Minor}
s_vsn, %% server version {Major,Minor}
@@ -82,6 +144,9 @@
c_keyinit, %% binary payload of kexinit packet
s_keyinit, %% binary payload of kexinit packet
+ send_ext_info, %% May send ext-info to peer
+ recv_ext_info, %% Expect ext-info from peer
+
algorithms, %% #alg{}
kex, %% key exchange algorithm
@@ -127,7 +192,7 @@
recv_sequence = 0,
keyex_key,
keyex_info,
- random_length_padding = 255, % From RFC 4253 section 6.
+ random_length_padding = ?MAX_RND_PADDING_LEN, % From RFC 4253 section 6.
%% User auth
user,
@@ -135,6 +200,7 @@
userauth_quiet_mode, % boolean()
userauth_methods, % list( string() ) eg ["keyboard-interactive", "password"]
userauth_supported_methods, % string() eg "keyboard-interactive,password"
+ userauth_pubkeys,
kb_tries_left = 0, % integer(), num tries left for "keyboard-interactive"
userauth_preference,
available_host_keys,
@@ -153,7 +219,9 @@
compress,
decompress,
c_lng,
- s_lng
+ s_lng,
+ send_ext_info,
+ recv_ext_info
}).
-record(ssh_key,
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index 9f3e60bd62..d66a34c58a 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -25,84 +25,86 @@
-include("ssh.hrl").
%% Internal application API
--export([start_link/5,
+-export([start_link/4,
number_of_connections/1,
- callback_listen/3,
- handle_connection/5]).
+ listen/2,
+ handle_established_connection/4]).
%% spawn export
--export([acceptor_init/6, acceptor_loop/6]).
+-export([acceptor_init/5, acceptor_loop/6]).
-define(SLEEP_TIME, 200).
%%====================================================================
%% Internal application API
%%====================================================================
-start_link(Port, Address, SockOpts, Opts, AcceptTimeout) ->
- Args = [self(), Port, Address, SockOpts, Opts, AcceptTimeout],
+start_link(Port, Address, Options, AcceptTimeout) ->
+ Args = [self(), Port, Address, Options, AcceptTimeout],
proc_lib:start_link(?MODULE, acceptor_init, Args).
+%%%----------------------------------------------------------------
+number_of_connections(SystemSup) ->
+ length([X ||
+ {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup),
+ is_pid(X),
+ is_reference(R)
+ ]).
+
+%%%----------------------------------------------------------------
+listen(Port, Options) ->
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ SockOpts = [{active, false}, {reuseaddr,true} | ?GET_OPT(socket_options, Options)],
+ case Callback:listen(Port, SockOpts) of
+ {error, nxdomain} ->
+ Callback:listen(Port, lists:delete(inet6, SockOpts));
+ {error, enetunreach} ->
+ Callback:listen(Port, lists:delete(inet6, SockOpts));
+ {error, eafnosupport} ->
+ Callback:listen(Port, lists:delete(inet6, SockOpts));
+ Other ->
+ Other
+ end.
+
+%%%----------------------------------------------------------------
+handle_established_connection(Address, Port, Options, Socket) ->
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ handle_connection(Callback, Address, Port, Options, Socket).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) ->
- {_, Callback, _} =
- proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}),
-
- SockOwner = proplists:get_value(lsock_owner, Opts),
- LSock = proplists:get_value(lsocket, Opts),
- UseExistingSocket =
- case catch inet:sockname(LSock) of
- {ok,{_,Port}} -> is_pid(SockOwner);
- _ -> false
- end,
-
- case UseExistingSocket of
- true ->
- proc_lib:init_ack(Parent, {ok, self()}),
- request_ownership(LSock, SockOwner),
- acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout);
-
- false ->
- case (catch do_socket_listen(Callback, Port, SockOpts)) of
- {ok, ListenSocket} ->
- proc_lib:init_ack(Parent, {ok, self()}),
- acceptor_loop(Callback,
- Port, Address, Opts, ListenSocket, AcceptTimeout);
- Error ->
- proc_lib:init_ack(Parent, Error),
- error
- end
+acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) ->
+ try
+ ?GET_INTERNAL_OPT(lsocket, Opts)
+ of
+ {LSock, SockOwner} ->
+ case inet:sockname(LSock) of
+ {ok,{_,Port}} -> % A usable, open LSock
+ proc_lib:init_ack(Parent, {ok, self()}),
+ request_ownership(LSock, SockOwner),
+ {_, Callback, _} = ?GET_OPT(transport, Opts),
+ acceptor_loop(Callback, Port, Address, Opts, LSock, AcceptTimeout);
+
+ {error,_} -> % Not open, a restart
+ {ok,NewLSock} = listen(Port, Opts),
+ proc_lib:init_ack(Parent, {ok, self()}),
+ Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts),
+ {_, Callback, _} = ?GET_OPT(transport, Opts1),
+ acceptor_loop(Callback, Port, Address, Opts1, NewLSock, AcceptTimeout)
+ end
+ catch
+ _:_ ->
+ {error,use_existing_socket_failed}
end.
+
request_ownership(LSock, SockOwner) ->
SockOwner ! {request_control,LSock,self()},
receive
{its_yours,LSock} -> ok
end.
-
-do_socket_listen(Callback, Port0, Opts) ->
- Port =
- case proplists:get_value(fd, Opts) of
- undefined -> Port0;
- _ -> 0
- end,
- callback_listen(Callback, Port, Opts).
-
-callback_listen(Callback, Port, Opts0) ->
- Opts = [{active, false}, {reuseaddr,true} | Opts0],
- case Callback:listen(Port, Opts) of
- {error, nxdomain} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- {error, enetunreach} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- {error, eafnosupport} ->
- Callback:listen(Port, lists:delete(inet6, Opts));
- Other ->
- Other
- end.
-
+%%%----------------------------------------------------------------
acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
case (catch Callback:accept(ListenSocket, AcceptTimeout)) of
{ok, Socket} ->
@@ -119,22 +121,24 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
ListenSocket, AcceptTimeout)
end.
+%%%----------------------------------------------------------------
handle_connection(Callback, Address, Port, Options, Socket) ->
- SSHopts = proplists:get_value(ssh_opts, Options, []),
- Profile = proplists:get_value(profile, SSHopts, ?DEFAULT_PROFILE),
+ Profile = ?GET_OPT(profile, Options),
SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile),
- MaxSessions = proplists:get_value(max_sessions,SSHopts,infinity),
+ MaxSessions = ?GET_OPT(max_sessions, Options),
case number_of_connections(SystemSup) < MaxSessions of
true ->
- {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options),
+ {ok, SubSysSup} =
+ ssh_system_sup:start_subsystem(SystemSup, server, Address, Port, Profile, Options),
ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup),
- Timeout = proplists:get_value(negotiation_timeout, SSHopts, 2*60*1000),
+ NegTimeout = ?GET_OPT(negotiation_timeout, Options),
ssh_connection_handler:start_connection(server, Socket,
- [{supervisors, [{system_sup, SystemSup},
- {subsystem_sup, SubSysSup},
- {connection_sup, ConnectionSup}]}
- | Options], Timeout);
+ ?PUT_INTERNAL_OPT(
+ {supervisors, [{system_sup, SystemSup},
+ {subsystem_sup, SubSysSup},
+ {connection_sup, ConnectionSup}]},
+ Options), NegTimeout);
false ->
Callback:close(Socket),
IPstr = if is_tuple(Address) -> inet:ntoa(Address);
@@ -150,7 +154,7 @@ handle_connection(Callback, Address, Port, Options, Socket) ->
{error,max_sessions}
end.
-
+%%%----------------------------------------------------------------
handle_error(timeout) ->
ok;
@@ -177,10 +181,3 @@ handle_error(Reason) ->
error_logger:error_report(String),
exit({accept_failed, String}).
-
-number_of_connections(SystemSup) ->
- length([X ||
- {R,X,supervisor,[ssh_subsystem_sup]} <- supervisor:which_children(SystemSup),
- is_pid(X),
- is_reference(R)
- ]).
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 129f85a3e0..a24664793b 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
-include("ssh.hrl").
--export([start_link/1, start_child/2, stop_child/4]).
+-export([start_link/4, start_child/5, stop_child/4]).
%% Supervisor callback
-export([init/1]).
@@ -41,20 +41,19 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Servers) ->
- supervisor:start_link(?MODULE, [Servers]).
+start_link(Address, Port, Profile, Options) ->
+ supervisor:start_link(?MODULE, [Address, Port, Profile, Options]).
-start_child(AccSup, ServerOpts) ->
- Spec = child_spec(ServerOpts),
+start_child(AccSup, Address, Port, Profile, Options) ->
+ Spec = child_spec(Address, Port, Profile, Options),
case supervisor:start_child(AccSup, Spec) of
{error, already_present} ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- Profile = proplists:get_value(profile,
- proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+ %% Is this ever called?
stop_child(AccSup, Address, Port, Profile),
supervisor:start_child(AccSup, Spec);
Reply ->
+ %% Reply = {ok,SystemSupPid} when the user calls ssh:daemon
+ %% after having called ssh:stop_listening
Reply
end.
@@ -70,35 +69,29 @@ stop_child(AccSup, Address, Port, Profile) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([ServerOpts]) ->
- RestartStrategy = one_for_one,
- MaxR = 10,
- MaxT = 3600,
- Children = [child_spec(ServerOpts)],
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init([Address, Port, Profile, Options]) ->
+ %% Initial start of ssh_acceptor_sup for this port or new start after
+ %% ssh:stop_daemon
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [child_spec(Address, Port, Profile, Options)],
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_spec(ServerOpts) ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT),
- 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, SocketOpts, ServerOpts, Timeout]},
- Restart = transient,
- Shutdown = brutal_kill,
- Modules = [ssh_acceptor],
- Type = worker,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+child_spec(Address, Port, Profile, Options) ->
+ Timeout = ?GET_INTERNAL_OPT(timeout, Options, ?DEFAULT_TIMEOUT),
+ #{id => id(Address, Port, Profile),
+ start => {ssh_acceptor, start_link, [Port, Address, Options, Timeout]},
+ restart => transient,
+ shutdown => 5500, %brutal_kill,
+ type => worker,
+ modules => [ssh_acceptor]
+ }.
id(Address, Port, Profile) ->
- case is_list(Address) of
- true ->
- {ssh_acceptor_sup, any, Port, Profile};
- false ->
- {ssh_acceptor_sup, Address, Port, Profile}
- end.
+ {ssh_acceptor_sup, Address, Port, Profile}.
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 49eec8072f..ac64a7bf14 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -28,57 +28,87 @@
-include("ssh_auth.hrl").
-include("ssh_transport.hrl").
--export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1,
+-export([get_public_key/2,
+ 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/2, handle_userauth_info_response/2
]).
%%--------------------------------------------------------------------
%%% Internal application API
%%--------------------------------------------------------------------
-publickey_msg([Alg, #ssh{user = User,
- session_id = SessionId,
- service = Service,
- opts = Opts} = Ssh]) ->
- Hash = sha, %% Maybe option?!
- KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
- case KeyCb:user_key(Alg, Opts) of
- {ok, PrivKey} ->
- StrAlgo = atom_to_list(Alg),
- case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of
- not_ok ->
- not_ok;
- PubKeyBlob ->
- SigData = build_sig_data(SessionId,
- User, Service, PubKeyBlob, StrAlgo),
- Sig = ssh_transport:sign(SigData, Hash, PrivKey),
- SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]),
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "publickey",
- data = [?TRUE,
- ?string(StrAlgo),
- ?binary(PubKeyBlob),
- ?binary(SigBlob)]},
- Ssh)
- end;
- _Error ->
- not_ok
+%%%----------------------------------------------------------------
+userauth_request_msg(#ssh{userauth_methods = ServerMethods,
+ userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients
+ userauth_preference = ClientMethods0
+ } = Ssh0) ->
+ case sort_select_mthds(ClientMethods0, UserPrefMethods, ServerMethods) of
+ [] ->
+ Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ description = "Unable to connect using the available authentication methods",
+ language = "en"},
+ {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh0)};
+
+ [{Pref,Module,Function,Args} | Prefs] ->
+ Ssh = case Pref of
+ "keyboard-interactive" -> Ssh0;
+ _ -> Ssh0#ssh{userauth_preference = Prefs}
+ end,
+ case Module:Function(Args ++ [Ssh]) of
+ {not_ok, Ssh1} ->
+ userauth_request_msg(Ssh1#ssh{userauth_preference = Prefs});
+ Result ->
+ {Pref,Result}
+ end
end.
+
+
+sort_select_mthds(Clients, undefined, Servers) ->
+ %% User has not expressed an opinion via option "auth_methods", use the server's prefs
+ sort_select_mthds1(Clients, Servers, string:tokens(?SUPPORTED_AUTH_METHODS,","));
+
+sort_select_mthds(Clients, Users0, Servers0) ->
+ %% The User has an opinion, use the intersection of that and the Servers whishes but
+ %% in the Users order
+ sort_select_mthds1(Clients, string:tokens(Users0,","), Servers0).
+
+
+sort_select_mthds1(Clients, Users0, Servers0) ->
+ Servers = unique(Servers0),
+ Users = unique(Users0),
+ [C || Key <- Users,
+ lists:member(Key, Servers),
+ C <- Clients,
+ element(1,C) == Key].
+
+unique(L) ->
+ lists:reverse(
+ lists:foldl(fun(E,Acc) ->
+ case lists:member(E,Acc) of
+ true -> Acc;
+ false -> [E|Acc]
+ end
+ end, [], L)).
+
+
+%%%---- userauth_request_msg "callbacks"
password_msg([#ssh{opts = Opts, io_cb = IoCb,
- user = User, service = Service} = Ssh]) ->
- Password = case proplists:get_value(password, Opts) of
- undefined ->
- user_interaction(IoCb, Ssh);
- PW ->
- PW
- end,
+ user = User, service = Service} = Ssh0]) ->
+ {Password,Ssh} =
+ case ?GET_OPT(password, Opts) of
+ undefined when IoCb == ssh_no_io ->
+ {not_ok, Ssh0};
+ undefined ->
+ {IoCb:read_password("ssh password: ",Opts), Ssh0};
+ PW ->
+ %% If "password" option is given it should not be tried again
+ {PW, Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}}
+ end,
case Password of
not_ok ->
- not_ok;
+ {not_ok, Ssh};
_ ->
ssh_transport:ssh_packet(
#ssh_msg_userauth_request{user = User,
@@ -90,84 +120,100 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb,
Ssh)
end.
-user_interaction(ssh_no_io, _) ->
- not_ok;
-user_interaction(IoCb, Ssh) ->
- IoCb:read_password("ssh password: ", Ssh).
-
-
%% See RFC 4256 for info on keyboard-interactive
keyboard_interactive_msg([#ssh{user = User,
+ opts = Opts,
service = Service} = Ssh]) ->
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_request{user = User,
- service = Service,
- method = "keyboard-interactive",
- data = << ?STRING(<<"">>),
- ?STRING(<<>>) >> },
- Ssh).
+ case ?GET_OPT(password, Opts) of
+ not_ok ->
+ {not_ok,Ssh}; % No need to use a failed pwd once more
+ _ ->
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "keyboard-interactive",
+ data = << ?STRING(<<"">>),
+ ?STRING(<<>>) >> },
+ Ssh)
+ end.
+
+get_public_key(SigAlg, #ssh{opts = Opts}) ->
+ KeyAlg = key_alg(SigAlg),
+ {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
+ UserOpts = ?GET_OPT(user_options, Opts),
+ case KeyCb:user_key(KeyAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ {ok, PrivKey} ->
+ try
+ Key = ssh_transport:extract_public_key(PrivKey),
+ public_key:ssh_encode(Key, ssh2_pubkey)
+ of
+ PubKeyBlob -> {ok,{PrivKey,PubKeyBlob}}
+ catch
+ _:_ ->
+ not_ok
+ end;
+ _Error ->
+ not_ok
+ end.
+
+
+publickey_msg([SigAlg, #ssh{user = User,
+ session_id = SessionId,
+ service = Service} = Ssh]) ->
+ case get_public_key(SigAlg, Ssh) of
+ {ok, {PrivKey,PubKeyBlob}} ->
+ SigAlgStr = atom_to_list(SigAlg),
+ SigData = build_sig_data(SessionId, User, Service,
+ PubKeyBlob, SigAlgStr),
+ Hash = ssh_transport:sha(SigAlg),
+ Sig = ssh_transport:sign(SigData, Hash, PrivKey),
+ SigBlob = list_to_binary([?string(SigAlgStr),
+ ?binary(Sig)]),
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "publickey",
+ data = [?TRUE,
+ ?string(SigAlgStr),
+ ?binary(PubKeyBlob),
+ ?binary(SigBlob)]},
+ Ssh);
+ _ ->
+ {not_ok, Ssh}
+ end.
+
+%%%----------------------------------------------------------------
service_request_msg(Ssh) ->
ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"},
Ssh#ssh{service = "ssh-userauth"}).
+%%%----------------------------------------------------------------
init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
- case user_name(Opts) of
- {ok, User} ->
- Msg = #ssh_msg_userauth_request{user = User,
- service = "ssh-connection",
- method = "none",
- data = <<>>},
- Algs0 = proplists:get_value(pref_public_key_algs, Opts, ?SUPPORTED_USER_KEYS),
- %% The following line is not strictly correct. The call returns the
- %% supported HOST key types while we are interested in USER keys. However,
- %% they "happens" to be the same (for now). This could change....
- %% There is no danger as long as the set of user keys is a subset of the set
- %% of host keys.
- CryptoSupported = ssh_transport:supported_algorithms(public_key),
- Algs = [A || A <- Algs0,
- lists:member(A, CryptoSupported)],
-
- 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} ->
+ %% Client side
+ case ?GET_OPT(user, Opts) of
+ undefined ->
ErrStr = "Could not determine the users name",
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
- description = ErrStr})
- end.
-
-userauth_request_msg(#ssh{userauth_preference = []} = Ssh) ->
- Msg = #ssh_msg_disconnect{code =
- ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
- description = "Unable to connect using the available"
- " authentication methods",
- language = "en"},
- {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)};
-
-userauth_request_msg(#ssh{userauth_methods = Methods,
- userauth_preference = [{Pref, Module,
- Function, Args} | Prefs]}
- = Ssh0) ->
- Ssh = Ssh0#ssh{userauth_preference = Prefs},
- case lists:member(Pref, Methods) of
- true ->
- case Module:Function(Args ++ [Ssh]) of
- not_ok ->
- userauth_request_msg(Ssh);
- Result ->
- {Pref,Result}
- end;
- false ->
- userauth_request_msg(Ssh)
+ description = ErrStr});
+
+ User ->
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ Ssh#ssh{user = User,
+ userauth_preference = method_preference(Ssh#ssh.userauth_pubkeys),
+ userauth_methods = none,
+ service = "ssh-connection"}
+ )
end.
-
-handle_userauth_request(#ssh_msg_service_request{name =
- Name = "ssh-userauth"},
+%%%----------------------------------------------------------------
+%%% called by server
+handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"},
_, Ssh) ->
{ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name},
Ssh#ssh{service = "ssh-connection"})};
@@ -223,32 +269,52 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "publickey",
- data = Data},
- SessionId,
+ data = <<?BYTE(?FALSE),
+ ?UINT32(ALen), BAlg:ALen/binary,
+ ?UINT32(KLen), KeyBlob:KLen/binary,
+ _/binary
+ >>
+ },
+ _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),
- case HaveSig of
- ?TRUE ->
- case verify_sig(SessionId, User, "ssh-connection", Alg,
- KeyBlob, SigWLen, Opts) of
- true ->
- {authorized, User,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_success{}, Ssh)};
- false ->
- {not_authorized, {User, undefined},
- ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
- authentications = Methods,
- partial_success = false}, Ssh)}
- end;
- ?FALSE ->
+
+ case pre_verify_sig(User, KeyBlob, Opts) of
+ true ->
{not_authorized, {User, undefined},
ssh_transport:ssh_packet(
- #ssh_msg_userauth_pk_ok{algorithm_name = Alg,
- key_blob = KeyBlob}, Ssh)}
+ #ssh_msg_userauth_pk_ok{algorithm_name = binary_to_list(BAlg),
+ key_blob = KeyBlob}, Ssh)};
+ false ->
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = Methods,
+ partial_success = false}, Ssh)}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "publickey",
+ data = <<?BYTE(?TRUE),
+ ?UINT32(ALen), BAlg:ALen/binary,
+ ?UINT32(KLen), KeyBlob:KLen/binary,
+ SigWLen/binary>>
+ },
+ SessionId,
+ #ssh{opts = Opts,
+ userauth_supported_methods = Methods} = Ssh) ->
+
+ case verify_sig(SessionId, User, "ssh-connection",
+ BAlg, KeyBlob, SigWLen, Opts) of
+ true ->
+ {authorized, User,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_success{}, Ssh)};
+ false ->
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = Methods,
+ partial_success = false}, Ssh)}
end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
@@ -283,7 +349,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
false},
{Name, Instruction, Prompt, Echo} =
- case proplists:get_value(auth_method_kb_interactive_data, Opts) of
+ case ?GET_OPT(auth_method_kb_interactive_data, Opts) of
undefined ->
Default;
{_,_,_,_}=V ->
@@ -319,31 +385,54 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
partial_success = false}, Ssh)}.
-
-handle_userauth_info_request(
- #ssh_msg_userauth_info_request{name = Name,
- instruction = Instr,
- num_prompts = NumPrompts,
- data = Data}, IoCb,
- #ssh{opts = Opts} = Ssh) ->
+%%%----------------------------------------------------------------
+%%% keyboard-interactive client
+handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name,
+ instruction = Instr,
+ num_prompts = NumPrompts,
+ data = Data},
+ #ssh{opts = Opts,
+ io_cb = IoCb
+ } = Ssh) ->
PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data),
- Responses = keyboard_interact_get_responses(IoCb, Opts,
- Name, Instr, PromptInfos),
- {ok,
- ssh_transport:ssh_packet(
- #ssh_msg_userauth_info_response{num_responses = NumPrompts,
- data = Responses}, Ssh)}.
+ case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of
+ not_ok ->
+ not_ok;
+ Responses ->
+ {ok,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_info_response{num_responses = NumPrompts,
+ data = Responses}, Ssh)}
+ end.
+%%%----------------------------------------------------------------
+%%% keyboard-interactive server
handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
data = <<?UINT32(Sz), Password:Sz/binary>>},
#ssh{opts = Opts,
kb_tries_left = KbTriesLeft,
user = User,
userauth_supported_methods = Methods} = Ssh) ->
+ SendOneEmpty =
+ (?GET_OPT(tstflg,Opts) == one_empty)
+ orelse
+ proplists:get_value(one_empty, ?GET_OPT(tstflg,Opts), false),
+
case check_password(User, unicode:characters_to_list(Password), Opts, Ssh) of
+ {true,Ssh1} when SendOneEmpty==true ->
+ Msg = #ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 0,
+ data = <<?BOOLEAN(?FALSE)>>
+ },
+ {authorized_but_one_more, User,
+ ssh_transport:ssh_packet(Msg, Ssh1)};
+
{true,Ssh1} ->
{authorized, User,
ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh1)};
+
{false,Ssh1} ->
{not_authorized, {User, {error,"Bad user or password"}},
ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
@@ -353,6 +442,11 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1,
)}
end;
+handle_userauth_info_response({extra,#ssh_msg_userauth_info_response{}},
+ #ssh{user = User} = Ssh) ->
+ {authorized, User,
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+
handle_userauth_info_response(#ssh_msg_userauth_info_response{},
_Auth) ->
ssh_connection_handler:disconnect(
@@ -364,41 +458,17 @@ handle_userauth_info_response(#ssh_msg_userauth_info_response{},
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-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, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []},
- {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
- ],
- Algs).
-
-user_name(Opts) ->
- Env = case os:type() of
- {win32, _} ->
- "USERNAME";
- {unix, _} ->
- "LOGNAME"
- end,
- case proplists:get_value(user, Opts, os:getenv(Env)) of
- false ->
- case os:getenv("USER") of
- false ->
- {error, no_user};
- User ->
- {ok, User}
- end;
- User ->
- {ok, User}
- end.
+method_preference(SigKeyAlgs) ->
+ %% PubKeyAlgs: List of user (client) public key algorithms to try to use.
+ %% All of the acceptable algorithms is the default values.
+ PubKeyDefs = [{"publickey", ?MODULE, publickey_msg, [A]} || A <- SigKeyAlgs],
+ NonPKmethods = [{"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ],
+ PubKeyDefs ++ NonPKmethods.
check_password(User, Password, Opts, Ssh) ->
- case proplists:get_value(pwdfun, Opts) of
+ case ?GET_OPT(pwdfun, Opts) of
undefined ->
Static = get_password_option(Opts, User),
{Password == Static, Ssh};
@@ -428,25 +498,37 @@ check_password(User, Password, Opts, Ssh) ->
end.
get_password_option(Opts, User) ->
- Passwords = proplists:get_value(user_passwords, Opts, []),
+ Passwords = ?GET_OPT(user_passwords, Opts),
case lists:keysearch(User, 1, Passwords) of
{value, {User, Pw}} -> Pw;
- false -> proplists:get_value(password, Opts, false)
+ false -> ?GET_OPT(password, Opts)
end.
-verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
- {ok, Key} = decode_public_key_v2(KeyBlob, Alg),
- KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
+pre_verify_sig(User, KeyBlob, Opts) ->
+ try
+ Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
+ {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
+ UserOpts = ?GET_OPT(user_options, Opts),
+ KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts])
+ catch
+ _:_ ->
+ false
+ end.
- case KeyCb:is_auth_key(Key, User, Opts) of
- true ->
- PlainText = build_sig_data(SessionId, User,
- Service, KeyBlob, Alg),
- <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
- <<?UINT32(AlgLen), _Alg:AlgLen/binary,
- ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
- ssh_transport:verify(PlainText, sha, Sig, Key);
- false ->
+verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, Opts) ->
+ try
+ Alg = binary_to_list(AlgBin),
+ {KeyCb,KeyCbOpts} = ?GET_OPT(key_cb, Opts),
+ UserOpts = ?GET_OPT(user_options, Opts),
+ Key = public_key:ssh_decode(KeyBlob, ssh2_pubkey), % or exception
+ true = KeyCb:is_auth_key(Key, User, [{key_cb_private,KeyCbOpts}|UserOpts]),
+ PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg),
+ <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
+ <<?UINT32(AlgLen), _Alg:AlgLen/binary,
+ ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
+ ssh_transport:verify(PlainText, ssh_transport:sha(Alg), Sig, Key)
+ catch
+ _:_ ->
false
end.
@@ -468,11 +550,14 @@ decode_keyboard_interactive_prompts(_NumPrompts, Data) ->
keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) ->
NumPrompts = length(PromptInfos),
- keyboard_interact_get_responses(proplists:get_value(user_interaction, Opts, true),
- proplists:get_value(keyboard_interact_fun, Opts),
- proplists:get_value(password, Opts, undefined), IoCb, Name,
+ keyboard_interact_get_responses(?GET_OPT(user_interaction, Opts),
+ ?GET_OPT(keyboard_interact_fun, Opts),
+ ?GET_OPT(password, Opts), IoCb, Name,
Instr, PromptInfos, Opts, NumPrompts).
+
+keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) ->
+ not_ok;
keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _,
1) when Password =/= undefined ->
[Password]; %% Password auth implemented with keyboard-interaction and passwd is known
@@ -486,17 +571,18 @@ keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos
keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts).
keyboard_interact(IoCb, Name, Instr, Prompts, Opts) ->
- if Name /= "" -> IoCb:format("~s~n", [Name]);
- true -> ok
- end,
- if Instr /= "" -> IoCb:format("~s~n", [Instr]);
- true -> ok
- end,
+ write_if_nonempty(IoCb, Name),
+ write_if_nonempty(IoCb, Instr),
lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts);
({Prompt, false}) -> IoCb:read_password(Prompt, Opts)
end,
Prompts).
+write_if_nonempty(_, "") -> ok;
+write_if_nonempty(_, <<>>) -> ok;
+write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]).
+
+
keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end,
PromptInfos),
@@ -512,18 +598,7 @@ keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) ->
language = "en"}})
end.
-decode_public_key_v2(Bin, _Type) ->
- try
- public_key:ssh_decode(Bin, ssh2_pubkey)
- of
- Key -> {ok, Key}
- catch
- _:_ -> {error, bad_format}
- end.
-encode_public_key(_Alg, Key) ->
- try
- public_key:ssh_encode(Key, ssh2_pubkey)
- catch
- _:_ -> not_ok
- end.
+key_alg('rsa-sha2-256') -> 'ssh-rsa';
+key_alg('rsa-sha2-512') -> 'ssh-rsa';
+key_alg(Alg) -> Alg.
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
index 8bedaaf0c5..3ce7758447 100644
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -30,39 +30,31 @@
-export([random/1]).
%%%----------------------------------------------------------------
-name_list([Name]) -> to_bin(Name);
-name_list([Name|Ns]) -> <<(to_bin(Name))/binary, ",", (name_list(Ns))/binary>>;
-name_list([]) -> <<>>.
-
-to_bin(A) when is_atom(A) -> list_to_binary(atom_to_list(A));
-to_bin(S) when is_list(S) -> list_to_binary(S);
-to_bin(B) when is_binary(B) -> B.
+name_list(NamesList) -> list_to_binary(lists:join($,, NamesList)).
%%%----------------------------------------------------------------
%%% Multi Precision Integer encoding
mpint(-1) -> <<0,0,0,1,16#ff>>;
mpint(0) -> <<0,0,0,0>>;
-mpint(X) when X < 0 -> mpint_neg(X,0,[]);
-mpint(X) -> mpint_pos(X,0,[]).
-
-mpint_neg(-1,I,Ds=[MSB|_]) ->
- if MSB band 16#80 =/= 16#80 ->
- <<?UINT32((I+1)), (list_to_binary([255|Ds]))/binary>>;
- true ->
- <<?UINT32(I), (list_to_binary(Ds))/binary>>
- end;
-mpint_neg(X,I,Ds) ->
- mpint_neg(X bsr 8,I+1,[(X band 255)|Ds]).
-
-mpint_pos(0,I,Ds=[MSB|_]) ->
- if MSB band 16#80 == 16#80 ->
- <<?UINT32((I+1)), (list_to_binary([0|Ds]))/binary>>;
- true ->
- <<?UINT32(I), (list_to_binary(Ds))/binary>>
+mpint(I) when I>0 ->
+ <<B1,V/binary>> = binary:encode_unsigned(I),
+ case B1 band 16#80 of
+ 16#80 ->
+ <<(size(V)+2):32/unsigned-big-integer, 0,B1,V/binary >>;
+ _ ->
+ <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >>
end;
-mpint_pos(X,I,Ds) ->
- mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]).
-
+mpint(N) when N<0 ->
+ Sxn = 8*size(binary:encode_unsigned(-N)),
+ Sxn1 = Sxn+8,
+ <<W:Sxn1>> = <<1, 0:Sxn>>,
+ <<B1,V/binary>> = binary:encode_unsigned(W+N),
+ case B1 band 16#80 of
+ 16#80 ->
+ <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >>;
+ _ ->
+ <<(size(V)+2):32/unsigned-big-integer, 255,B1,V/binary >>
+ end.
%%%----------------------------------------------------------------
%% random/1
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
index a8e6ebde16..85b31f3669 100644
--- a/lib/ssh/src/ssh_channel.erl
+++ b/lib/ssh/src/ssh_channel.erl
@@ -93,11 +93,16 @@ call(ChannelPid, Msg, TimeOute) ->
catch
exit:{noproc, _} ->
{error, closed};
+ exit:{normal, _} ->
+ {error, closed};
+ exit:{shutdown, _} ->
+ {error, closed};
+ exit:{{shutdown, _}, _} ->
+ {error, closed};
exit:{timeout, _} ->
{error, timeout}
end.
-
cast(ChannelPid, Msg) ->
gen_server:cast(ChannelPid, Msg).
@@ -256,7 +261,7 @@ handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager,
adjust_window(Msg),
{noreply, State#state{channel_state = ChannelState}, Timeout};
{stop, ChannelId, ChannelState} ->
- ssh_connection:close(ConnectionManager, ChannelId),
+ catch ssh_connection:close(ConnectionManager, ChannelId),
{stop, normal, State#state{close_sent = true,
channel_state = ChannelState}}
end;
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 74cd2e081a..62854346b0 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -208,8 +208,15 @@ handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
write_chars(ConnectionHandler, ChannelId, Chars),
{ok, State#state{buf = NewBuf}};
-handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
- channel = ChannelId} = State) ->
+handle_msg({'EXIT', Group, Reason}, #state{group = Group,
+ cm = ConnectionHandler,
+ channel = ChannelId} = State) ->
+ Status = case Reason of
+ normal -> 0;
+ _ -> -1
+ end,
+ ssh_connection:exit_status(ConnectionHandler, ChannelId, Status),
+ ssh_connection:send_eof(ConnectionHandler, ChannelId),
{stop, ChannelId, State};
handle_msg(_, State) ->
@@ -446,14 +453,20 @@ move_cursor(From, To, #ssh_pty{width=Width, term=Type}) ->
%% %%% make sure that there is data to send
%% %%% before calling ssh_connection:send
write_chars(ConnectionHandler, ChannelId, Chars) ->
- case erlang:iolist_size(Chars) of
- 0 ->
- ok;
- _ ->
- ssh_connection:send(ConnectionHandler, ChannelId,
- ?SSH_EXTENDED_DATA_DEFAULT, Chars)
+ case has_chars(Chars) of
+ false -> ok;
+ true -> ssh_connection:send(ConnectionHandler,
+ ChannelId,
+ ?SSH_EXTENDED_DATA_DEFAULT,
+ Chars)
end.
+has_chars([C|_]) when is_integer(C) -> true;
+has_chars([H|T]) when is_list(H) ; is_binary(H) -> has_chars(H) orelse has_chars(T);
+has_chars(<<_:8,_/binary>>) -> true;
+has_chars(_) -> false.
+
+
%%% tail, works with empty lists
tl1([_|A]) -> A;
tl1(_) -> [].
@@ -486,14 +499,12 @@ start_shell(ConnectionHandler, State) ->
[peer, user]),
ShellFun = case is_function(Shell) of
true ->
- User =
- proplists:get_value(user, ConnectionInfo),
+ User = proplists:get_value(user, ConnectionInfo),
case erlang:fun_info(Shell, arity) of
{arity, 1} ->
fun() -> Shell(User) end;
{arity, 2} ->
- {_, PeerAddr} =
- proplists:get_value(peer, ConnectionInfo),
+ {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
fun() -> Shell(User, PeerAddr) end;
_ ->
Shell
@@ -512,8 +523,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function
ConnectionInfo = ssh_connection_handler:connection_info(ConnectionHandler,
[peer, user]),
- User =
- proplists:get_value(user, ConnectionInfo),
+ User = proplists:get_value(user, ConnectionInfo),
ShellFun =
case erlang:fun_info(Shell, arity) of
{arity, 1} ->
@@ -521,8 +531,7 @@ start_shell(ConnectionHandler, Cmd, #state{exec=Shell} = State) when is_function
{arity, 2} ->
fun() -> Shell(Cmd, User) end;
{arity, 3} ->
- {_, PeerAddr} =
- proplists:get_value(peer, ConnectionInfo),
+ {_, PeerAddr} = proplists:get_value(peer, ConnectionInfo),
fun() -> Shell(Cmd, User, PeerAddr) end;
_ ->
Shell
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index 4fb6bc39f3..a8de5f9a2f 100644
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,9 +22,9 @@
%%% Description : SSH connection protocol
--type role() :: client | server .
--type connection_ref() :: pid().
-type channel_id() :: pos_integer().
+-type connection_ref() :: pid().
+
-define(DEFAULT_PACKET_SIZE, 65536).
-define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE).
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index d0f2d54c06..7e9ee78fd2 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -56,8 +56,8 @@
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
--spec session_channel(pid(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
--spec session_channel(pid(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
+-spec session_channel(connection_ref(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
+-spec session_channel(connection_ref(), integer(), integer(), timeout()) -> {ok, channel_id()} | {error, timeout | closed}.
%% Description: Opens a channel for a ssh session. A session is a
%% remote execution of a program. The program may be a shell, an
@@ -81,7 +81,7 @@ session_channel(ConnectionHandler, InitialWindowSize,
end.
%%--------------------------------------------------------------------
--spec exec(pid(), channel_id(), string(), timeout()) ->
+-spec exec(connection_ref(), channel_id(), string(), timeout()) ->
success | failure | {error, timeout | closed}.
%% Description: Will request that the server start the
@@ -92,7 +92,7 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) ->
true, [?string(Command)], TimeOut).
%%--------------------------------------------------------------------
--spec shell(pid(), channel_id()) -> _.
+-spec shell(connection_ref(), channel_id()) -> _.
%% Description: Will request that the user's default shell (typically
%% defined in /etc/passwd in UNIX systems) be started at the other
@@ -102,7 +102,7 @@ shell(ConnectionHandler, ChannelId) ->
ssh_connection_handler:request(ConnectionHandler, self(), ChannelId,
"shell", false, <<>>, 0).
%%--------------------------------------------------------------------
--spec subsystem(pid(), channel_id(), string(), timeout()) ->
+-spec subsystem(connection_ref(), channel_id(), string(), timeout()) ->
success | failure | {error, timeout | closed}.
%%
%% Description: Executes a predefined subsystem.
@@ -112,11 +112,11 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) ->
ChannelId, "subsystem",
true, [?string(SubSystem)], TimeOut).
%%--------------------------------------------------------------------
--spec send(pid(), channel_id(), iodata()) ->
+-spec send(connection_ref(), channel_id(), iodata()) ->
ok | {error, closed}.
--spec send(pid(), channel_id(), integer()| iodata(), timeout() | iodata()) ->
+-spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) ->
ok | {error, timeout} | {error, closed}.
--spec send(pid(), channel_id(), integer(), iodata(), timeout()) ->
+-spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) ->
ok | {error, timeout} | {error, closed}.
%%
%%
@@ -134,7 +134,7 @@ send(ConnectionHandler, ChannelId, Type, Data, TimeOut) ->
ssh_connection_handler:send(ConnectionHandler, ChannelId,
Type, Data, TimeOut).
%%--------------------------------------------------------------------
--spec send_eof(pid(), channel_id()) -> ok | {error, closed}.
+-spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}.
%%
%%
%% Description: Sends eof on the channel <ChannelId>.
@@ -143,7 +143,7 @@ send_eof(ConnectionHandler, Channel) ->
ssh_connection_handler:send_eof(ConnectionHandler, Channel).
%%--------------------------------------------------------------------
--spec adjust_window(pid(), channel_id(), integer()) -> ok | {error, closed}.
+-spec adjust_window(connection_ref(), channel_id(), integer()) -> ok | {error, closed}.
%%
%%
%% Description: Adjusts the ssh flowcontrol window.
@@ -152,7 +152,7 @@ adjust_window(ConnectionHandler, Channel, Bytes) ->
ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes).
%%--------------------------------------------------------------------
--spec setenv(pid(), channel_id(), string(), string(), timeout()) ->
+-spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) ->
success | failure | {error, timeout | closed}.
%%
%%
@@ -165,7 +165,7 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) ->
%%--------------------------------------------------------------------
--spec close(pid(), channel_id()) -> ok.
+-spec close(connection_ref(), channel_id()) -> ok.
%%
%%
%% Description: Sends a close message on the channel <ChannelId>.
@@ -174,7 +174,7 @@ close(ConnectionHandler, ChannelId) ->
ssh_connection_handler:close(ConnectionHandler, ChannelId).
%%--------------------------------------------------------------------
--spec reply_request(pid(), boolean(), success | failure, channel_id()) -> ok.
+-spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok.
%%
%%
%% Description: Send status replies to requests that want such replies.
@@ -185,9 +185,9 @@ reply_request(_,false, _, _) ->
ok.
%%--------------------------------------------------------------------
--spec ptty_alloc(pid(), channel_id(), proplists:proplist()) ->
+-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) ->
success | failiure | {error, closed}.
--spec ptty_alloc(pid(), channel_id(), proplists:proplist(), timeout()) ->
+-spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) ->
success | failiure | {error, timeout} | {error, closed}.
%%
@@ -197,16 +197,16 @@ reply_request(_,false, _, _) ->
ptty_alloc(ConnectionHandler, Channel, Options) ->
ptty_alloc(ConnectionHandler, Channel, Options, infinity).
ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) ->
- Options = backwards_compatible(Options0, []),
- {Width, PixWidth} = pty_default_dimensions(width, Options),
- {Height, PixHeight} = pty_default_dimensions(height, Options),
+ TermData = backwards_compatible(Options0, []), % FIXME
+ {Width, PixWidth} = pty_default_dimensions(width, TermData),
+ {Height, PixHeight} = pty_default_dimensions(height, TermData),
pty_req(ConnectionHandler, Channel,
- proplists:get_value(term, Options, os:getenv("TERM", ?DEFAULT_TERMINAL)),
- proplists:get_value(width, Options, Width),
- proplists:get_value(height, Options, Height),
- proplists:get_value(pixel_widh, Options, PixWidth),
- proplists:get_value(pixel_height, Options, PixHeight),
- proplists:get_value(pty_opts, Options, []), TimeOut
+ proplists:get_value(term, TermData, os:getenv("TERM", ?DEFAULT_TERMINAL)),
+ proplists:get_value(width, TermData, Width),
+ proplists:get_value(height, TermData, Height),
+ proplists:get_value(pixel_widh, TermData, PixWidth),
+ proplists:get_value(pixel_height, TermData, PixHeight),
+ proplists:get_value(pty_opts, TermData, []), TimeOut
).
%%--------------------------------------------------------------------
%% Not yet officialy supported! The following functions are part of the
@@ -287,6 +287,9 @@ handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
ssh_channel:cache_update(Cache, Channel#channel{
remote_id = RemoteId,
+ recv_packet_size = max(32768, % rfc4254/5.2
+ min(PacketSz, Channel#channel.recv_packet_size)
+ ),
send_window_size = WindowSz,
send_packet_size = PacketSz}),
{Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}),
@@ -414,7 +417,8 @@ handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
maximum_packet_size = PacketSz},
#connection{options = SSHopts} = Connection0,
server) ->
- MinAcceptedPackSz = proplists:get_value(minimal_remote_max_packet_size, SSHopts, 0),
+ MinAcceptedPackSz =
+ ?GET_OPT(minimal_remote_max_packet_size, SSHopts),
if
MinAcceptedPackSz =< PacketSz ->
@@ -571,7 +575,6 @@ handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
PixWidth, PixHeight, decode_pty_opts(Modes)},
Channel = ssh_channel:cache_lookup(Cache, ChannelId),
-
handle_cli_msg(Connection, Channel,
{pty, ChannelId, WantReply, PtyRequest});
@@ -688,7 +691,6 @@ handle_cli_msg(#connection{channel_cache = Cache} = Connection,
#channel{user = undefined,
remote_id = RemoteId,
local_id = ChannelId} = Channel0, Reply0) ->
-
case (catch start_cli(Connection, ChannelId)) of
{ok, Pid} ->
erlang:monitor(process, Pid),
@@ -816,7 +818,7 @@ start_channel(Cb, Id, Args, SubSysSup, Exec, Opts) ->
ssh_channel_sup:start_child(ChannelSup, ChildSpec).
assert_limit_num_channels_not_exceeded(ChannelSup, Opts) ->
- MaxNumChannels = proplists:get_value(max_channels, Opts, infinity),
+ MaxNumChannels = ?GET_OPT(max_channels, Opts),
NumChannels = length([x || {_,_,worker,[ssh_channel]} <-
supervisor:which_children(ChannelSup)]),
if
@@ -855,8 +857,8 @@ setup_session(#connection{channel_cache = Cache
check_subsystem("sftp"= SsName, Options) ->
- case proplists:get_value(subsystems, Options, no_subsys) of
- no_subsys ->
+ case ?GET_OPT(subsystems, Options) of
+ no_subsys -> % FIXME: Can 'no_subsys' ever be matched?
{SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]),
{Cb, Opts};
SubSystems ->
@@ -864,7 +866,7 @@ check_subsystem("sftp"= SsName, Options) ->
end;
check_subsystem(SsName, Options) ->
- Subsystems = proplists:get_value(subsystems, Options, []),
+ Subsystems = ?GET_OPT(subsystems, Options),
case proplists:get_value(SsName, Subsystems, {none, []}) of
Fun when is_function(Fun) ->
{Fun, []};
@@ -1019,12 +1021,13 @@ pty_req(ConnectionHandler, Channel, Term, Width, Height,
?uint32(PixWidth),?uint32(PixHeight),
encode_pty_opts(PtyOpts)], TimeOut).
-pty_default_dimensions(Dimension, Options) ->
- case proplists:get_value(Dimension, Options, 0) of
+pty_default_dimensions(Dimension, TermData) ->
+ case proplists:get_value(Dimension, TermData, 0) of
N when is_integer(N), N > 0 ->
{N, 0};
_ ->
- case proplists:get_value(list_to_atom("pixel_" ++ atom_to_list(Dimension)), Options, 0) of
+ PixelDim = list_to_atom("pixel_" ++ atom_to_list(Dimension)),
+ case proplists:get_value(PixelDim, TermData, 0) of
N when is_integer(N), N > 0 ->
{0, N};
_ ->
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index e952a333ff..8d3ddb09a4 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -60,7 +60,8 @@
]).
%%% Behaviour callbacks
--export([handle_event/4, terminate/3, format_status/2, code_change/4]).
+-export([init/1, callback_mode/0, handle_event/4, terminate/3,
+ format_status/2, code_change/4]).
%%% Exports not intended to be used :). They are used for spawning and tests
-export([init_connection_handler/3, % proc_lib:spawn needs this
@@ -75,11 +76,15 @@
%%--------------------------------------------------------------------
-spec start_link(role(),
inet:socket(),
- proplists:proplist()
+ ssh_options:options()
) -> {ok, pid()}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
start_link(Role, Socket, Options) ->
- {ok, proc_lib:spawn_link(?MODULE, init_connection_handler, [Role, Socket, Options])}.
+ {ok, proc_lib:spawn_opt(?MODULE,
+ init_connection_handler,
+ [Role, Socket, Options],
+ [link, {message_queue_data,off_heap}]
+ )}.
%%--------------------------------------------------------------------
@@ -98,12 +103,10 @@ stop(ConnectionHandler)->
%% Internal application API
%%====================================================================
--define(DefaultTransport, {tcp, gen_tcp, tcp_closed} ).
-
%%--------------------------------------------------------------------
-spec start_connection(role(),
inet:socket(),
- proplists:proplist(),
+ ssh_options:options(),
timeout()
) -> {ok, connection_ref()} | {error, term()}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -120,9 +123,8 @@ start_connection(client = Role, Socket, Options, Timeout) ->
end;
start_connection(server = Role, Socket, Options, Timeout) ->
- SSH_Opts = proplists:get_value(ssh_opts, Options, []),
try
- case proplists:get_value(parallel_login, SSH_Opts, false) of
+ case ?GET_OPT(parallel_login, Options) of
true ->
HandshakerPid =
spawn_link(fun() ->
@@ -335,8 +337,7 @@ renegotiate_data(ConnectionHandler) ->
transport_protocol :: atom(), % ex: tcp
transport_cb :: atom(), % ex: gen_tcp
transport_close_tag :: atom(), % ex: tcp_closed
- ssh_params :: #ssh{}
- | undefined,
+ ssh_params :: #ssh{},
socket :: inet:socket(),
decrypted_data_buffer = <<>> :: binary(),
encrypted_data_buffer = <<>> :: binary(),
@@ -345,7 +346,7 @@ renegotiate_data(ConnectionHandler) ->
| undefined,
last_size_rekey = 0 :: non_neg_integer(),
event_queue = [] :: list(),
- opts :: proplists:proplist(),
+% opts :: ssh_options:options(),
inet_initial_recbuf_size :: pos_integer()
| undefined
}).
@@ -356,107 +357,121 @@ renegotiate_data(ConnectionHandler) ->
%%--------------------------------------------------------------------
-spec init_connection_handler(role(),
inet:socket(),
- proplists:proplist()
+ ssh_options:options()
) -> no_return().
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
init_connection_handler(Role, Socket, Opts) ->
- process_flag(trap_exit, true),
- S0 = init_process_state(Role, Socket, Opts),
- try
- {Protocol, Callback, CloseTag} =
- proplists:get_value(transport, Opts, ?DefaultTransport),
- S0#data{ssh_params = init_ssh_record(Role, Socket, Opts),
- transport_protocol = Protocol,
- transport_cb = Callback,
- transport_close_tag = CloseTag
- }
- of
- S ->
- gen_statem:enter_loop(?MODULE,
- [], %%[{debug,[trace,log,statistics,debug]} || Role==server],
- handle_event_function,
- {hello,Role},
- S)
- catch
- _:Error ->
- gen_statem:enter_loop(?MODULE,
- [],
- handle_event_function,
- {init_error,Error},
- S0)
+ case init([Role, Socket, Opts]) of
+ {ok, StartState, D} ->
+ process_flag(trap_exit, true),
+ gen_statem:enter_loop(?MODULE,
+ [], %%[{debug,[trace,log,statistics,debug]} ], %% []
+ StartState,
+ D);
+
+ {stop, enotconn} ->
+ %% Handles the abnormal sequence:
+ %% SYN->
+ %% <-SYNACK
+ %% ACK->
+ %% RST->
+ exit({shutdown, "TCP connection to server was prematurely closed by the client"});
+
+ {stop, OtherError} ->
+ exit({shutdown, {init,OtherError}})
end.
-init_process_state(Role, Socket, Opts) ->
- D = #data{connection_state =
- C = #connection{channel_cache = ssh_channel:cache_create(),
- channel_id_seed = 0,
- port_bindings = [],
- requests = [],
- options = Opts},
- starter = proplists:get_value(user_pid, Opts),
- socket = Socket,
- opts = Opts
- },
- case Role of
- client ->
- %% Start the renegotiation timers
- timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
- timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
- cache_init_idle_timer(D);
- server ->
- D#data{connection_state = init_connection(Role, C, Opts)}
+
+init([Role,Socket,Opts]) ->
+ case inet:peername(Socket) of
+ {ok, PeerAddr} ->
+ {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts),
+ C = #connection{channel_cache = ssh_channel:cache_create(),
+ channel_id_seed = 0,
+ port_bindings = [],
+ requests = [],
+ options = Opts},
+ D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts),
+ connection_state = C,
+ socket = Socket,
+ transport_protocol = Protocol,
+ transport_cb = Callback,
+ transport_close_tag = CloseTag,
+ ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts)
+ },
+ D = case Role of
+ client ->
+ %% Start the renegotiation timers
+ timer:apply_after(?REKEY_TIMOUT, gen_statem, cast, [self(), renegotiate]),
+ timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
+ cache_init_idle_timer(D0);
+ server ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
+ cache_init_idle_timer(
+ D0#data{connection_state =
+ C#connection{cli_spec = ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}),
+ exec = ?GET_OPT(exec, Opts),
+ system_supervisor = proplists:get_value(system_sup, Sups),
+ sub_system_supervisor = proplists:get_value(subsystem_sup, Sups),
+ connection_supervisor = proplists:get_value(connection_sup, Sups)
+ }})
+ end,
+ {ok, {hello,Role}, D};
+
+ {error,Error} ->
+ {stop, Error}
end.
-init_connection(server, C = #connection{}, Opts) ->
- Sups = proplists:get_value(supervisors, Opts),
- SystemSup = proplists:get_value(system_sup, Sups),
- SubSystemSup = proplists:get_value(subsystem_sup, Sups),
- ConnectionSup = proplists:get_value(connection_sup, Sups),
- Shell = proplists:get_value(shell, Opts),
- Exec = proplists:get_value(exec, Opts),
- CliSpec = proplists:get_value(ssh_cli, Opts, {ssh_cli, [Shell]}),
- C#connection{cli_spec = CliSpec,
- exec = Exec,
- system_supervisor = SystemSup,
- sub_system_supervisor = SubSystemSup,
- connection_supervisor = ConnectionSup
- }.
-
init_ssh_record(Role, Socket, Opts) ->
- {ok, PeerAddr} = inet:peername(Socket),
- KeyCb = proplists:get_value(key_cb, Opts, ssh_file),
- AuthMethods = proplists:get_value(auth_methods, Opts, ?SUPPORTED_AUTH_METHODS),
+ %% Export of this internal function is
+ %% intended for low-level protocol test suites
+ {ok,PeerAddr} = inet:peername(Socket),
+ init_ssh_record(Role, Socket, PeerAddr, Opts).
+
+init_ssh_record(Role, _Socket, PeerAddr, Opts) ->
+ KeyCb = ?GET_OPT(key_cb, Opts),
+ AuthMethods = ?GET_OPT(auth_methods, Opts),
S0 = #ssh{role = Role,
key_cb = KeyCb,
opts = Opts,
userauth_supported_methods = AuthMethods,
available_host_keys = supported_host_keys(Role, KeyCb, Opts),
- random_length_padding = proplists:get_value(max_random_length_padding,
- Opts,
- (#ssh{})#ssh.random_length_padding)
+ random_length_padding = ?GET_OPT(max_random_length_padding, Opts)
},
{Vsn, Version} = ssh_transport:versions(Role, Opts),
case Role of
client ->
- PeerName = proplists:get_value(host, Opts),
- S0#ssh{c_vsn = Vsn,
- c_version = Version,
- io_cb = case proplists:get_value(user_interaction, Opts, true) of
- true -> ssh_io;
- false -> ssh_no_io
- end,
- userauth_quiet_mode = proplists:get_value(quiet_mode, Opts, false),
- peer = {PeerName, PeerAddr}
- };
+ PeerName = case ?GET_INTERNAL_OPT(host, Opts) of
+ PeerIP when is_tuple(PeerIP) ->
+ inet_parse:ntoa(PeerIP);
+ PeerName0 when is_atom(PeerName0) ->
+ atom_to_list(PeerName0);
+ PeerName0 when is_list(PeerName0) ->
+ PeerName0
+ end,
+ S1 =
+ S0#ssh{c_vsn = Vsn,
+ c_version = Version,
+ io_cb = case ?GET_OPT(user_interaction, Opts) of
+ true -> ssh_io;
+ false -> ssh_no_io
+ end,
+ userauth_quiet_mode = ?GET_OPT(quiet_mode, Opts),
+ peer = {PeerName, PeerAddr}
+ },
+ S1#ssh{userauth_pubkeys = [K || K <- ?GET_OPT(pref_public_key_algs, Opts),
+ is_usable_user_pubkey(K, S1)
+ ]
+ };
server ->
S0#ssh{s_vsn = Vsn,
s_version = Version,
- io_cb = proplists:get_value(io_cb, Opts, ssh_io),
+ io_cb = ?GET_INTERNAL_OPT(io_cb, Opts, ssh_io),
userauth_methods = string:tokens(AuthMethods, ","),
kb_tries_left = 3,
peer = {undefined, PeerAddr}
@@ -474,51 +489,53 @@ init_ssh_record(Role, Socket, Opts) ->
-type renegotiate_flag() :: init | renegotiate.
-type state_name() ::
- {init_error,any()}
- | {hello, role()}
- | {kexinit, role(), renegotiate_flag()}
- | {key_exchange, role(), renegotiate_flag()}
- | {key_exchange_dh_gex_init, server, renegotiate_flag()}
+ {hello, role() }
+ | {kexinit, role(), renegotiate_flag()}
+ | {key_exchange, role(), renegotiate_flag()}
+ | {key_exchange_dh_gex_init, server, renegotiate_flag()}
| {key_exchange_dh_gex_reply, client, renegotiate_flag()}
- | {new_keys, role()}
- | {service_request, role()}
- | {userauth, role()}
- | {userauth_keyboard_interactive, role()}
- | {connected, role()}
+ | {new_keys, role(), renegotiate_flag()}
+ | {ext_info, role(), renegotiate_flag()}
+ | {service_request, role() }
+ | {userauth, role() }
+ | {userauth_keyboard_interactive, role() }
+ | {userauth_keyboard_interactive_extra, server }
+ | {userauth_keyboard_interactive_info_response, client }
+ | {connected, role() }
.
--type handle_event_result() :: gen_statem:handle_event_result().
+%% The state names must fulfill some rules regarding
+%% where the role() and the renegotiate_flag() is placed:
+
+-spec role(state_name()) -> role().
+role({_,Role}) -> Role;
+role({_,Role,_}) -> Role.
+
+-spec renegotiation(state_name()) -> boolean().
+renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation;
+renegotiation(_) -> false.
+
+
+-define(CONNECTED(StateName),
+ (element(1,StateName) == connected orelse
+ element(1,StateName) == ext_info ) ).
-spec handle_event(gen_statem:event_type(),
event_content(),
state_name(),
#data{}
- ) -> handle_event_result().
+ ) -> gen_statem:event_handler_result(state_name()) .
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-%%% ######## Error in the initialisation ####
-
-handle_event(_, _Event, {init_error,Error}, _) ->
- case Error of
- {badmatch,{error,enotconn}} ->
- %% Handles the abnormal sequence:
- %% SYN->
- %% <-SYNACK
- %% ACK->
- %% RST->
- {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}};
-
- OtherError ->
- {stop, {shutdown,{init,OtherError}}}
- end;
-
+callback_mode() ->
+ handle_event_function.
%%% ######## {hello, client|server} ####
%% The very first event that is sent when the we are set as controlling process of Socket
handle_event(_, socket_control, {hello,_}, D) ->
VsnMsg = ssh_transport:hello_version_msg(string_version(D#data.ssh_params)),
- ok = send_bytes(VsnMsg, D),
+ send_bytes(VsnMsg, D),
case inet:getopts(Socket=D#data.socket, [recbuf]) of
{ok, [{recbuf,Size}]} ->
%% Set the socket to the hello text line handling mode:
@@ -538,12 +555,13 @@ handle_event(_, {info_line,_Line}, {hello,Role}, D) ->
case Role of
client ->
%% The server may send info lines to the client before the version_exchange
+ %% RFC4253/4.2
inet:setopts(D#data.socket, [{active, once}]),
keep_state_and_data;
server ->
%% But the client may NOT send them to the server. Openssh answers with cleartext,
%% and so do we
- ok = send_bytes("Protocol mismatch.", D),
+ send_bytes("Protocol mismatch.", D),
{stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}}
end;
@@ -558,7 +576,7 @@ handle_event(_, {version_exchange,Version}, {hello,Role}, D) ->
{active, once},
{recbuf, D#data.inet_initial_recbuf_size}]),
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1),
- ok = send_bytes(SshPacket, D),
+ send_bytes(SshPacket, D),
{next_state, {kexinit,Role,init}, D#data{ssh_params = Ssh,
key_exchange_init_msg = KeyInitMsg}};
not_supported ->
@@ -576,7 +594,7 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload),
Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of
{ok, NextKexMsg, Ssh2} when Role==client ->
- ok = send_bytes(NextKexMsg, D),
+ send_bytes(NextKexMsg, D),
Ssh2;
{ok, Ssh2} when Role==server ->
Ssh2
@@ -589,43 +607,53 @@ handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
%%%---- diffie-hellman
handle_event(_, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
{ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params),
- ok = send_bytes(KexdhReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexdhReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
- ok = send_bytes(NewKeys, D),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
%%%---- diffie-hellman group exchange
handle_event(_, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) ->
- {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
- ok = send_bytes(GexGroup, D),
+ {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ send_bytes(GexGroup, D),
+ Ssh = ssh_transport:parallell_gen_key(Ssh1),
{next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) ->
- {ok, GexGroup, Ssh} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
- ok = send_bytes(GexGroup, D),
+ {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ send_bytes(GexGroup, D),
+ Ssh = ssh_transport:parallell_gen_key(Ssh1),
{next_state, {key_exchange_dh_gex_init,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) ->
{ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params),
- ok = send_bytes(KexGexInit, D),
+ send_bytes(KexGexInit, D),
{next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}};
%%%---- elliptic curve diffie-hellman
handle_event(_, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
{ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params),
- ok = send_bytes(KexEcdhReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexEcdhReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
- {ok, NewKeys, Ssh} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
- ok = send_bytes(NewKeys, D),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
@@ -633,9 +661,11 @@ handle_event(_, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D)
handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) ->
{ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params),
- ok = send_bytes(KexGexReply, D),
- {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
- ok = send_bytes(NewKeys, D),
+ send_bytes(KexGexReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ send_bytes(ExtInfo, D),
{next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
@@ -643,37 +673,68 @@ handle_event(_, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,serv
handle_event(_, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) ->
{ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params),
- ok = send_bytes(NewKeys, D),
- {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh1}};
+ send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ send_bytes(ExtInfo, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
%%% ######## {new_keys, client|server} ####
%% First key exchange round:
-handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,init}, D) ->
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D) ->
{ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
- Ssh = case Role of
- client ->
- {MsgReq, Ssh2} = ssh_auth:service_request_msg(Ssh1),
- ok = send_bytes(MsgReq, D),
- Ssh2;
- server ->
- Ssh1
- end,
- {next_state, {service_request,Role}, D#data{ssh_params=Ssh}};
+ %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
+ send_bytes(MsgReq, D),
+ {next_state, {ext_info,client,init}, D#data{ssh_params=Ssh}};
+
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,server,init}, D) ->
+ {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}};
%% Subsequent key exchange rounds (renegotiation):
-handle_event(_, #ssh_msg_newkeys{}, {new_keys,Role,renegotiate}, D) ->
+handle_event(_, #ssh_msg_newkeys{} = Msg, {new_keys,Role,renegotiate}, D) ->
+ {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ %% {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ %% send_bytes(ExtInfo, D),
+ {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}};
+
+
+%%% ######## {ext_info, client|server, init|renegotiate} ####
+
+handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) ->
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {next_state, {service_request,Role}, D};
+
+handle_event(_, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) ->
+ D = handle_ssh_msg_ext_info(Msg, D0),
{next_state, {connected,Role}, D};
-%%% ######## {service_request, client|server}
+handle_event(_, #ssh_msg_newkeys{}=Msg, {ext_info,_Role,renegotiate}, D) ->
+ {ok, Ssh} = ssh_transport:handle_new_keys(Msg, D#data.ssh_params),
+ {keep_state, D#data{ssh_params = Ssh}};
+
+
+handle_event(internal, Msg, {ext_info,Role,init}, D) when is_tuple(Msg) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {service_request,Role}, D, [postpone]};
+
+handle_event(internal, Msg, {ext_info,Role,_ReNegFlag}, D) when is_tuple(Msg) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {connected,Role}, D, [postpone]};
+
+%%% ######## {service_request, client|server} ####
handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {service_request,server}, D) ->
case ServiceName of
"ssh-userauth" ->
Ssh0 = #ssh{session_id=SessionId} = D#data.ssh_params,
{ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
- ok = send_bytes(Reply, D),
+ send_bytes(Reply, D),
{next_state, {userauth,server}, D#data{ssh_params = Ssh}};
_ ->
@@ -685,7 +746,7 @@ handle_event(_, Msg = #ssh_msg_service_request{name=ServiceName}, StateName = {s
handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
#data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = State) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
- ok = send_bytes(Msg, State),
+ send_bytes(Msg, State),
{next_state, {userauth,client}, State#data{auth_user = Ssh#ssh.user, ssh_params = Ssh}};
@@ -702,7 +763,7 @@ handle_event(_,
%% Probably the very first userauth_request but we deny unauthorized login
{not_authorized, _, {Reply,Ssh}} =
ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0),
- ok = send_bytes(Reply, D),
+ send_bytes(Reply, D),
{keep_state, D#data{ssh_params = Ssh}};
{"ssh-connection", "ssh-connection", Method} ->
@@ -712,7 +773,7 @@ handle_event(_,
%% Yepp! we support this method
case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
{authorized, User, {Reply, Ssh}} ->
- ok = send_bytes(Reply, D),
+ send_bytes(Reply, D),
D#data.starter ! ssh_connected,
connected_fun(User, Method, D),
{next_state, {connected,server},
@@ -720,11 +781,11 @@ handle_event(_,
ssh_params = Ssh#ssh{authenticated = true}}};
{not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
retry_fun(User, Reason, D),
- ok = send_bytes(Reply, D),
+ send_bytes(Reply, D),
{next_state, {userauth_keyboard_interactive,server}, D#data{ssh_params = Ssh}};
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Reason, D),
- ok = send_bytes(Reply, D),
+ send_bytes(Reply, D),
{keep_state, D#data{ssh_params = Ssh}}
end;
false ->
@@ -745,6 +806,11 @@ handle_event(_,
end;
%%---- userauth success to client
+handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
+
handle_event(_, #ssh_msg_userauth_success{}, {userauth,client}, D=#data{ssh_params = Ssh}) ->
D#data.starter ! ssh_connected,
{next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
@@ -794,9 +860,13 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) -
handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
#data{ssh_params = Ssh0} = D) ->
- {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, Ssh0#ssh.io_cb, Ssh0),
- send_bytes(Reply, D),
- {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}};
+ not_ok ->
+ {next_state, {userauth,client}, D, [postpone]}
+ end;
handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) ->
case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of
@@ -809,34 +879,60 @@ handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_inte
{not_authorized, {User, Reason}, {Reply, Ssh}} ->
retry_fun(User, Reason, D),
send_bytes(Reply, D),
- {next_state, {userauth,server}, D#data{ssh_params = Ssh}}
+ {next_state, {userauth,server}, D#data{ssh_params = Ssh}};
+
+ {authorized_but_one_more, _User, {Reply, Ssh}} ->
+ send_bytes(Reply, D),
+ {next_state, {userauth_keyboard_interactive_extra,server}, D#data{ssh_params = Ssh}}
end;
-handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
+handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D) ->
+ {authorized, User, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response({extra,Msg}, D#data.ssh_params),
+ send_bytes(Reply, D),
+ D#data.starter ! ssh_connected,
+ connected_fun(User, "keyboard-interactive", D),
+ {next_state, {connected,server}, D#data{auth_user = User,
+ ssh_params = Ssh#ssh{authenticated = true}}};
+
+handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
#data{ssh_params = Ssh0} = D0) ->
Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference,
Method =/= "keyboard-interactive"],
D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}},
- {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
+ {next_state, {userauth,client}, D, [postpone]};
+
+handle_event(_, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client},
+ #data{ssh_params = Ssh0} = D0) ->
+ Opts = Ssh0#ssh.opts,
+ D = case ?GET_OPT(password, Opts) of
+ undefined ->
+ D0;
+ _ ->
+ D0#data{ssh_params =
+ Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency
+ end,
+ {next_state, {userauth,client}, D, [postpone]};
-handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) ->
- {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
+handle_event(_, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
-handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
- {next_state, {userauth,client}, D, [{next_event, internal, Msg}]};
+handle_event(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth,client}, D, [postpone]};
-handle_event(_, Msg=#ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
- {next_state, {userauth_keyboard_interactive,client}, D, [{next_event, internal, Msg}]};
+handle_event(_, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth_keyboard_interactive,client}, D, [postpone]};
%%% ######## {connected, client|server} ####
-handle_event(_, {#ssh_msg_kexinit{},_} = Event, {connected,Role}, D0) ->
+handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) ->
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(D0#data.ssh_params),
D = D0#data{ssh_params = Ssh,
key_exchange_init_msg = KeyInitMsg},
send_bytes(SshPacket, D),
- {next_state, {kexinit,Role,renegotiate}, D, [{next_event, internal, Event}]};
+ {next_state, {kexinit,Role,renegotiate}, D, [postpone]};
handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
{disconnect, _, {{replies,Replies}, _}} =
@@ -885,6 +981,9 @@ handle_event(internal, Msg=#ssh_msg_channel_extended_data{}, StateName, D) -
handle_event(internal, Msg=#ssh_msg_channel_eof{}, StateName, D) ->
handle_connection_msg(Msg, StateName, D);
+handle_event(internal, Msg=#ssh_msg_channel_close{}, {connected,server} = StateName, D) ->
+ handle_connection_msg(Msg, StateName, cache_request_idle_timer_check(D));
+
handle_event(internal, Msg=#ssh_msg_channel_close{}, StateName, D) ->
handle_connection_msg(Msg, StateName, D);
@@ -892,6 +991,7 @@ handle_event(internal, Msg=#ssh_msg_channel_request{}, StateName, D) -
handle_connection_msg(Msg, StateName, D);
handle_event(internal, Msg=#ssh_msg_channel_success{}, StateName, D) ->
+ update_inet_buffers(D#data.socket),
handle_connection_msg(Msg, StateName, D);
handle_event(internal, Msg=#ssh_msg_channel_failure{}, StateName, D) ->
@@ -915,7 +1015,7 @@ handle_event(cast, renegotiate, _, _) ->
handle_event(cast, data_size, {connected,Role}, D) ->
{ok, [{send_oct,Sent0}]} = inet:getstat(D#data.socket, [send_oct]),
Sent = Sent0 - D#data.last_size_rekey,
- MaxSent = proplists:get_value(rekey_limit, D#data.opts, 1024000000),
+ MaxSent = ?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
timer:apply_after(?REKEY_DATA_TIMOUT, gen_statem, cast, [self(), data_size]),
case Sent >= MaxSent of
true ->
@@ -936,12 +1036,10 @@ handle_event(cast, data_size, _, _) ->
-handle_event(cast, _, StateName, _) when StateName /= {connected,server},
- StateName /= {connected,client} ->
+handle_event(cast, _, StateName, _) when not ?CONNECTED(StateName) ->
{keep_state_and_data, [postpone]};
-
-handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
+handle_event(cast, {adjust_window,ChannelId,Bytes}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_window_pending = Pending,
@@ -967,23 +1065,24 @@ handle_event(cast, {adjust_window,ChannelId,Bytes}, {connected,_}, D) ->
keep_state_and_data
end;
-handle_event(cast, {reply_request,success,ChannelId}, {connected,_}, D) ->
+handle_event(cast, {reply_request,success,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = RemoteId} ->
Msg = ssh_connection:channel_success_msg(RemoteId),
+ update_inet_buffers(D#data.socket),
{keep_state, send_msg(Msg,D)};
undefined ->
keep_state_and_data
end;
-handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, {connected,_}, D) ->
+handle_event(cast, {request,ChannelPid, ChannelId, Type, Data}, StateName, D) when ?CONNECTED(StateName) ->
{keep_state, handle_request(ChannelPid, ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {request,ChannelId,Type,Data}, {connected,_}, D) ->
+handle_event(cast, {request,ChannelId,Type,Data}, StateName, D) when ?CONNECTED(StateName) ->
{keep_state, handle_request(ChannelId, Type, Data, false, none, D)};
-handle_event(cast, {unknown,Data}, {connected,_}, D) ->
+handle_event(cast, {unknown,Data}, StateName, D) when ?CONNECTED(StateName) ->
Msg = #ssh_msg_unimplemented{sequence = Data},
{keep_state, send_msg(Msg,D)};
@@ -1006,13 +1105,13 @@ handle_event({call,From}, get_print_info, StateName, D) ->
{keep_state_and_data, [{reply,From,Reply}]};
handle_event({call,From}, {connection_info, Options}, _, D) ->
- Info = ssh_info(Options, D, []),
+ Info = fold_keys(Options, fun conn_info/2, D),
{keep_state_and_data, [{reply,From,Info}]};
handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{} = Channel ->
- Info = ssh_channel_info(Options, Channel, []),
+ Info = fold_keys(Options, fun chann_info/2, Channel),
{keep_state_and_data, [{reply,From,Info}]};
undefined ->
{keep_state_and_data, [{reply,From,[]}]}
@@ -1044,30 +1143,34 @@ handle_event({call,From}, stop, StateName, D0) ->
{Repls,D} = send_replies(Replies, D0),
{stop_and_reply, normal, [{reply,From,ok}|Repls], D#data{connection_state=Connection}};
-handle_event({call,_}, _, StateName, _) when StateName /= {connected,server},
- StateName /= {connected,client} ->
+
+handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) ->
{keep_state_and_data, [postpone]};
-handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
D = handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_request_idle_timer_check(D)};
-handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
D = handle_request(ChannelId, Type, Data, true, From, D0),
%% Note reply to channel will happen later when reply is recived from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_request_idle_timer_check(D)};
-handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, {connected,_}, D0) ->
+handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)
+ when ?CONNECTED(StateName) ->
{{replies, Replies}, Connection} =
ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From),
{Repls,D} = send_replies(Replies, D0#data{connection_state = Connection}),
start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why?
{keep_state, D, Repls};
-handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
+handle_event({call,From}, {eof, ChannelId}, StateName, D0)
+ when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id, sent_close = false} ->
D = send_msg(ssh_connection:channel_eof_msg(Id), D0),
@@ -1078,8 +1181,8 @@ handle_event({call,From}, {eof, ChannelId}, {connected,_}, D0) ->
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
- {connected,_},
- D0) ->
+ StateName,
+ D0) when ?CONNECTED(StateName) ->
erlang:monitor(process, ChannelPid),
{ChannelId, D1} = new_channel_id(D0),
D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
@@ -1099,7 +1202,8 @@ handle_event({call,From},
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, cache_cancel_idle_timer(D)};
-handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
+handle_event({call,From}, {send_window, ChannelId}, StateName, D)
+ when ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
send_packet_size = Packsize} ->
@@ -1109,7 +1213,8 @@ handle_event({call,From}, {send_window, ChannelId}, {connected,_}, D) ->
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
+handle_event({call,From}, {recv_window, ChannelId}, StateName, D)
+ when ?CONNECTED(StateName) ->
Reply = case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
recv_packet_size = Packsize} ->
@@ -1119,7 +1224,8 @@ handle_event({call,From}, {recv_window, ChannelId}, {connected,_}, D) ->
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {close, ChannelId}, {connected,_}, D0) ->
+handle_event({call,From}, {close, ChannelId}, StateName, D0)
+ when ?CONNECTED(StateName) ->
case ssh_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id} = Channel ->
D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
@@ -1158,17 +1264,17 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock,
ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D))
of
Msg = #ssh_msg_kexinit{} ->
- {keep_state, D, [{next_event, internal, {Msg,DecryptedBytes}},
- {next_event, internal, prepare_next_packet}
+ {keep_state, D, [{next_event, internal, prepare_next_packet},
+ {next_event, internal, {Msg,DecryptedBytes}}
]};
Msg ->
- {keep_state, D, [{next_event, internal, Msg},
- {next_event, internal, prepare_next_packet}
+ {keep_state, D, [{next_event, internal, prepare_next_packet},
+ {next_event, internal, Msg}
]}
catch
_C:_E ->
disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Encountered unexpected input"},
+ description = "Bad packet"},
StateName, D)
end;
@@ -1183,13 +1289,12 @@ handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock,
{bad_mac, Ssh1} ->
disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad mac"},
+ description = "Bad packet"},
StateName, D0#data{ssh_params=Ssh1});
- {error, {exceeds_max_size,PacketLen}} ->
+ {error, {exceeds_max_size,_PacketLen}} ->
disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "Bad packet length "
- ++ integer_to_list(PacketLen)},
+ description = "Bad packet"},
StateName, D0)
catch
_C:_E ->
@@ -1206,16 +1311,20 @@ handle_event(internal, prepare_next_packet, _, D) ->
Sz when Sz >= Enough ->
self() ! {D#data.transport_protocol, D#data.socket, <<>>};
_ ->
- inet:setopts(D#data.socket, [{active, once}])
+ ok
end,
+ inet:setopts(D#data.socket, [{active, once}]),
keep_state_and_data;
handle_event(info, {CloseTag,Socket}, StateName,
D = #data{socket = Socket,
transport_close_tag = CloseTag}) ->
- disconnect(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
- description = "Connection closed"},
- StateName, D);
+ %% Simulate a disconnect from the peer
+ handle_event(info,
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Connection closed"},
+ StateName,
+ D);
handle_event(info, {timeout, {_, From} = Request}, _,
#data{connection_state = #connection{requests = Requests} = C0} = D) ->
@@ -1251,11 +1360,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
"Unexpected message '~p' received in state '~p'\n"
"Role: ~p\n"
"Peer: ~p\n"
- "Local Address: ~p\n", [UnexpectedMessage,
- StateName,
- Ssh#ssh.role,
- Ssh#ssh.peer,
- proplists:get_value(address, Ssh#ssh.opts)])),
+ "Local Address: ~p\n",
+ [UnexpectedMessage,
+ StateName,
+ Ssh#ssh.role,
+ Ssh#ssh.peer,
+ ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)])),
error_logger:info_report(Msg),
keep_state_and_data;
@@ -1269,11 +1379,12 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
"Message: ~p\n"
"Role: ~p\n"
"Peer: ~p\n"
- "Local Address: ~p\n", [Other,
- UnexpectedMessage,
- Ssh#ssh.role,
- element(2,Ssh#ssh.peer),
- proplists:get_value(address, Ssh#ssh.opts)]
+ "Local Address: ~p\n",
+ [Other,
+ UnexpectedMessage,
+ Ssh#ssh.role,
+ element(2,Ssh#ssh.peer),
+ ?GET_INTERNAL_OPT(address, Ssh#ssh.opts)]
)),
error_logger:error_report(Msg),
keep_state_and_data
@@ -1282,11 +1393,16 @@ handle_event(info, UnexpectedMessage, StateName, D = #data{ssh_params = Ssh}) ->
handle_event(internal, {disconnect,Msg,_Reason}, StateName, D) ->
disconnect(Msg, StateName, D);
+handle_event(_Type, _Msg, {ext_info,Role,_ReNegFlag}, D) ->
+ %% If something else arrives, goto next state and handle the event in that one
+ {next_state, {connected,Role}, D, [postpone]};
+
handle_event(Type, Ev, StateName, D) ->
Descr =
case catch atom_to_list(element(1,Ev)) of
"ssh_msg_" ++_ when Type==internal ->
- "Message in wrong state";
+%% "Message in wrong state";
+ lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName]));
_ ->
"Internal error"
end,
@@ -1315,12 +1431,10 @@ terminate(shutdown, StateName, State0) ->
State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
description = "Application shutdown"},
State0),
-timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead
finalize_termination(StateName, State);
%% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)->
%% State = send_msg(Msg, State0),
-%% timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead
%% finalize_termination(StateName, Msg, State);
terminate({shutdown,_R}, StateName, State) ->
@@ -1382,12 +1496,12 @@ fmt_stat_rec(FieldNames, Rec, Exclude) ->
state_name(),
#data{},
term()
- ) -> {gen_statem:callback_mode(), state_name(), #data{}}.
+ ) -> {ok, state_name(), #data{}}.
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
code_change(_OldVsn, StateName, State, _Extra) ->
- {handle_event_function, StateName, State}.
+ {ok, StateName, State}.
%%====================================================================
@@ -1397,11 +1511,11 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%%--------------------------------------------------------------------
%% Starting
-start_the_connection_child(UserPid, Role, Socket, Options) ->
- Sups = proplists:get_value(supervisors, Options),
+start_the_connection_child(UserPid, Role, Socket, Options0) ->
+ Sups = ?GET_INTERNAL_OPT(supervisors, Options0),
ConnectionSup = proplists:get_value(connection_sup, Sups),
- Opts = [{supervisors, Sups}, {user_pid, UserPid} | proplists:get_value(ssh_opts, Options, [])],
- {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Opts]),
+ Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0),
+ {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, [Role, Socket, Options]),
ok = socket_control(Socket, Pid, Options),
Pid.
@@ -1428,46 +1542,45 @@ peer_role(client) -> server;
peer_role(server) -> client.
%%--------------------------------------------------------------------
-%% StateName to Role
-role({_,Role}) -> Role;
-role({_,Role,_}) -> Role.
-
-%%--------------------------------------------------------------------
-%% Check the StateName to see if we are in the renegotiation phase
-renegotiation({_,_,ReNeg}) -> ReNeg == renegotiation;
-renegotiation(_) -> false.
-
-%%--------------------------------------------------------------------
supported_host_keys(client, _, Options) ->
try
- case proplists:get_value(public_key,
- proplists:get_value(preferred_algorithms,Options,[])
- ) of
- undefined ->
- ssh_transport:default_algorithms(public_key);
- L ->
- L -- (L--ssh_transport:default_algorithms(public_key))
- end
+ find_sup_hkeys(Options)
of
[] ->
- {stop, {shutdown, "No public key algs"}};
+ error({shutdown, "No public key algs"});
Algs ->
[atom_to_list(A) || A<-Algs]
catch
exit:Reason ->
- {stop, {shutdown, Reason}}
+ error({shutdown, Reason})
end;
supported_host_keys(server, KeyCb, Options) ->
- [atom_to_list(A) || A <- proplists:get_value(public_key,
- proplists:get_value(preferred_algorithms,Options,[]),
- ssh_transport:default_algorithms(public_key)
- ),
+ [atom_to_list(A) || A <- find_sup_hkeys(Options),
available_host_key(KeyCb, A, Options)
].
+
+find_sup_hkeys(Options) ->
+ case proplists:get_value(public_key,
+ ?GET_OPT(preferred_algorithms,Options)
+ )
+ of
+ undefined ->
+ ssh_transport:default_algorithms(public_key);
+ L ->
+ NonSupported = L--ssh_transport:supported_algorithms(public_key),
+ L -- NonSupported
+ end.
+
+
+
%% Alg :: atom()
-available_host_key(KeyCb, Alg, Opts) ->
- element(1, catch KeyCb:host_key(Alg, Opts)) == ok.
+available_host_key({KeyCb,KeyCbOpts}, Alg, Opts) ->
+ UserOpts = ?GET_OPT(user_options, Opts),
+ case KeyCb:host_key(Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ {ok,_} -> true;
+ _ -> false
+ end.
send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->
@@ -1475,8 +1588,11 @@ send_msg(Msg, State=#data{ssh_params=Ssh0}) when is_tuple(Msg) ->
send_bytes(Bytes, State),
State#data{ssh_params=Ssh}.
+send_bytes("", _D) ->
+ ok;
send_bytes(Bytes, #data{socket = Socket, transport_cb = Transport}) ->
- Transport:send(Socket, Bytes).
+ _ = Transport:send(Socket, Bytes),
+ ok.
handle_version({2, 0} = NumVsn, StrVsn, Ssh0) ->
Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0),
@@ -1580,6 +1696,57 @@ cache(#data{connection_state=C}) -> C#connection.channel_cache.
%%%----------------------------------------------------------------
+handle_ssh_msg_ext_info(#ssh_msg_ext_info{}, D=#data{ssh_params = #ssh{recv_ext_info=false}} ) ->
+ % The peer sent this although we didn't allow it!
+ D;
+
+handle_ssh_msg_ext_info(#ssh_msg_ext_info{data=Data}, D0) ->
+ lists:foldl(fun ext_info/2, D0, Data).
+
+
+ext_info({"server-sig-algs",SigAlgsStr},
+ D0 = #data{ssh_params=#ssh{role=client,
+ userauth_pubkeys=ClientSigAlgs}=Ssh0}) ->
+ %% ClientSigAlgs are the pub_key algortithms that:
+ %% 1) is usable, that is, the user has such a public key and
+ %% 2) is either the default list or set by the caller
+ %% with the client option 'pref_public_key_algs'
+ %%
+ %% The list is already checked for duplicates.
+
+ SigAlgs = [A || Astr <- string:tokens(SigAlgsStr, ","),
+ A <- try [list_to_existing_atom(Astr)]
+ %% list_to_existing_atom will fail for unknown algorithms
+ catch _:_ -> []
+ end],
+
+ CommonAlgs = [A || A <- SigAlgs,
+ lists:member(A, ClientSigAlgs)],
+
+ %% Re-arrange the client supported public-key algorithms so that the server
+ %% preferred ones are tried first.
+ %% Trying algorithms not mentioned by the server is ok, since the server can't know
+ %% if the client supports 'server-sig-algs' or not.
+
+ D0#data{
+ ssh_params =
+ Ssh0#ssh{
+ userauth_pubkeys =
+ CommonAlgs ++ (ClientSigAlgs -- CommonAlgs)
+ }};
+
+ext_info(_, D0) ->
+ %% Not implemented
+ D0.
+
+%%%----------------------------------------------------------------
+is_usable_user_pubkey(A, Ssh) ->
+ case ssh_auth:get_public_key(A, Ssh) of
+ {ok,_} -> true;
+ _ -> false
+ end.
+
+%%%----------------------------------------------------------------
handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From, D) ->
case ssh_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = Id} = Channel ->
@@ -1635,7 +1802,6 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} =
disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) ->
State = send_msg(Msg, State0),
disconnect_fun(Description, State),
-timer:sleep(400),
{stop, {shutdown,Description}, State}.
%%%----------------------------------------------------------------
@@ -1644,43 +1810,43 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
-ssh_info([], _State, Acc) ->
- Acc;
-ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn,
- c_version = StringVsn}} = State, Acc) ->
- ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]);
-
-ssh_info([server_version | Rest], #data{ssh_params =#ssh{s_vsn = IntVsn,
- s_version = StringVsn}} = State, Acc) ->
- ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]);
-ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) ->
- ssh_info(Rest, State, [{peer, Peer} | Acc]);
-ssh_info([sockname | Rest], #data{socket = Socket} = State, Acc) ->
- {ok, SockName} = inet:sockname(Socket),
- ssh_info(Rest, State, [{sockname, SockName}|Acc]);
-ssh_info([user | Rest], #data{auth_user = User} = State, Acc) ->
- ssh_info(Rest, State, [{user, User}|Acc]);
-ssh_info([ _ | Rest], State, Acc) ->
- ssh_info(Rest, State, Acc).
-
-
-ssh_channel_info([], _, Acc) ->
- Acc;
+%%%----------------------------------------------------------------
+conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version};
+conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version};
+conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer;
+conn_info(user, D) -> D#data.auth_user;
+conn_info(sockname, D) -> {ok, SockName} = inet:sockname(D#data.socket),
+ SockName;
+%% dbg options ( = not documented):
+conn_info(socket, D) -> D#data.socket;
+conn_info(chan_ids, D) ->
+ ssh_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) ->
+ [Id | Acc]
+ end, [], cache(D)).
-ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize,
- recv_packet_size = Packsize
- } = Channel, Acc) ->
- ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize},
- {packet_size, Packsize}}} | Acc]);
-ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize,
- send_packet_size = Packsize
- } = Channel, Acc) ->
- ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize},
- {packet_size, Packsize}}} | Acc]);
-ssh_channel_info([ _ | Rest], Channel, Acc) ->
- ssh_channel_info(Rest, Channel, Acc).
+%%%----------------------------------------------------------------
+chann_info(recv_window, C) ->
+ {{win_size, C#channel.recv_window_size},
+ {packet_size, C#channel.recv_packet_size}};
+chann_info(send_window, C) ->
+ {{win_size, C#channel.send_window_size},
+ {packet_size, C#channel.send_packet_size}};
+%% dbg options ( = not documented):
+chann_info(pid, C) ->
+ C#channel.user.
+%%%----------------------------------------------------------------
+%% Assisting meta function for the *_info functions
+fold_keys(Keys, Fun, Extra) ->
+ lists:foldr(fun(Key, Acc) ->
+ try Fun(Key, Extra) of
+ Value -> [{Key,Value}|Acc]
+ catch
+ _:_ -> Acc
+ end
+ end, [], Keys).
+%%%----------------------------------------------------------------
log_error(Reason) ->
Report = io_lib:format("Erlang ssh connection handler failed with reason:~n"
" ~p~n"
@@ -1689,7 +1855,6 @@ log_error(Reason) ->
[Reason, erlang:get_stacktrace()]),
error_logger:error_report(Report).
-
%%%----------------------------------------------------------------
not_connected_filter({connection_reply, _Data}) -> true;
not_connected_filter(_) -> false.
@@ -1701,6 +1866,11 @@ send_replies(Repls, State) ->
Repls).
get_repl({connection_reply,Msg}, {CallRepls,S}) ->
+ if is_record(Msg, ssh_msg_channel_success) ->
+ update_inet_buffers(S#data.socket);
+ true ->
+ ok
+ end,
{CallRepls, send_msg(Msg,S)};
get_repl({channel_data,undefined,_Data}, Acc) ->
Acc;
@@ -1720,47 +1890,24 @@ get_repl(X, Acc) ->
exit({get_repl,X,Acc}).
%%%----------------------------------------------------------------
-disconnect_fun({disconnect,Msg}, D) ->
- disconnect_fun(Msg, D);
-disconnect_fun(Reason, #data{opts=Opts}) ->
- case proplists:get_value(disconnectfun, Opts) of
- undefined ->
- ok;
- Fun ->
- catch Fun(Reason)
- end.
-
-unexpected_fun(UnexpectedMessage, #data{opts = Opts,
- ssh_params = #ssh{peer = {_,Peer} }
- } ) ->
- case proplists:get_value(unexpectedfun, Opts) of
- undefined ->
- report;
- Fun ->
- catch Fun(UnexpectedMessage, Peer)
- end.
+-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ).
+
+disconnect_fun({disconnect,Msg}, D) -> ?CALL_FUN(disconnectfun,D)(Msg);
+disconnect_fun(Reason, D) -> ?CALL_FUN(disconnectfun,D)(Reason).
+unexpected_fun(UnexpectedMessage, #data{ssh_params = #ssh{peer = {_,Peer} }} = D) ->
+ ?CALL_FUN(unexpectedfun,D)(UnexpectedMessage, Peer).
debug_fun(#ssh_msg_debug{always_display = Display,
message = DbgMsg,
language = Lang},
- #data{opts = Opts}) ->
- case proplists:get_value(ssh_msg_debug_fun, Opts) of
- undefined ->
- ok;
- Fun ->
- catch Fun(self(), Display, DbgMsg, Lang)
- end.
+ D) ->
+ ?CALL_FUN(ssh_msg_debug_fun,D)(self(), Display, DbgMsg, Lang).
-connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}},
- opts = Opts}) ->
- case proplists:get_value(connectfun, Opts) of
- undefined ->
- ok;
- Fun ->
- catch Fun(User, Peer, Method)
- end.
+connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) ->
+ ?CALL_FUN(connectfun,D)(User, Peer, Method).
+
retry_fun(_, undefined, _) ->
ok;
@@ -1774,7 +1921,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
_ ->
{infofun, Reason}
end,
- Fun = proplists:get_value(Tag, Opts, fun(_,_)-> ok end),
+ Fun = ?GET_OPT(Tag, Opts),
try erlang:fun_info(Fun, arity)
of
{arity, 2} -> %% Backwards compatible
@@ -1793,7 +1940,7 @@ retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
%%% channels open for a while.
cache_init_idle_timer(D) ->
- case proplists:get_value(idle_time, D#data.opts, infinity) of
+ case ?GET_OPT(idle_time, (D#data.ssh_params)#ssh.opts) of
infinity ->
D#data{idle_timer_value = infinity,
idle_timer_ref = infinity % A flag used later...
@@ -1856,9 +2003,8 @@ start_channel_request_timer(Channel, From, Time) ->
%%% Connection start and initalization helpers
socket_control(Socket, Pid, Options) ->
- {_, TransportCallback, _} = % For example {_,gen_tcp,_}
- proplists:get_value(transport, Options, ?DefaultTransport),
- case TransportCallback:controlling_process(Socket, Pid) of
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ case Callback:controlling_process(Socket, Pid) of
ok ->
gen_statem:cast(Pid, socket_control);
{error, Reason} ->
@@ -1889,3 +2035,15 @@ handshake(Pid, Ref, Timeout) ->
{error, timeout}
end.
+update_inet_buffers(Socket) ->
+ try
+ {ok, BufSzs0} = inet:getopts(Socket, [sndbuf,recbuf]),
+ MinVal = 655360,
+ [{Tag,MinVal} || {Tag,Val} <- BufSzs0,
+ Val < MinVal]
+ of
+ [] -> ok;
+ NewOpts -> inet:setopts(Socket, NewOpts)
+ catch
+ _:_ -> ok
+ end.
diff --git a/lib/ssh/src/ssh_connection_sup.erl b/lib/ssh/src/ssh_connection_sup.erl
index 0f54053f52..60ee8b7c73 100644
--- a/lib/ssh/src/ssh_connection_sup.erl
+++ b/lib/ssh/src/ssh_connection_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -45,19 +45,17 @@ start_child(Sup, Args) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
init(_) ->
- RestartStrategy = simple_one_for_one,
- MaxR = 0,
- MaxT = 3600,
-
- Name = undefined, % As simple_one_for_one is used.
- StartFunc = {ssh_connection_handler, start_link, []},
- Restart = temporary, % E.g. should not be restarted
- Shutdown = 4000,
- Modules = [ssh_connection_handler],
- Type = worker,
-
- ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules},
- {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}.
+ SupFlags = #{strategy => simple_one_for_one,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
+ start => {ssh_connection_handler, start_link, []},
+ restart => temporary,
+ shutdown => 4000,
+ type => worker,
+ modules => [ssh_connection_handler]
+ }
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl
index 480795cfc7..3f742ad9b6 100644
--- a/lib/ssh/src/ssh_dbg.erl
+++ b/lib/ssh/src/ssh_dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -22,79 +22,216 @@
-module(ssh_dbg).
--export([messages/0,
- messages/1,
+-export([messages/0, messages/1, messages/2, messages/3,
+ auth/0, auth/1, auth/2, auth/3,
stop/0
]).
+-export([shrink_bin/1,
+ wr_record/3]).
+
-include("ssh.hrl").
-include("ssh_transport.hrl").
-include("ssh_connect.hrl").
-include("ssh_auth.hrl").
--record(data, {
- writer,
- acc = []}).
%%%================================================================
-messages() -> messages(fun(String,_D) -> io:format(String) end).
-%% messages() -> messages(fun(String,Acc) -> [String|Acc] end)
+messages() -> start(msg).
+messages(F) -> start(msg,F).
+messages(F,X) -> start(msg,F,X).
+messages(F,M,I) -> start(msg,F,M,I).
-messages(Write) when is_function(Write,2) ->
- catch dbg:start(),
- setup_tracer(Write),
- dbg:p(new,c),
- dbg_ssh_messages().
+auth() -> start(auth).
+auth(F) -> start(auth,F).
+auth(F,X) -> start(auth,F,X).
+auth(F,M,I) -> start(auth,F,M,I).
-dbg_ssh_messages() ->
- dbg:tp(ssh_message,encode,1, x),
- dbg:tp(ssh_message,decode,1, x),
- dbg:tpl(ssh_transport,select_algorithm,3, x).
+stop() -> dbg:stop().
%%%----------------------------------------------------------------
-stop() ->
- dbg:stop().
+start(Type) -> start(Type, fun io:format/2).
+
+start(Type, F) when is_function(F,2) -> start(Type, fmt_fun(F));
+start(Type, F) when is_function(F,3) -> start(Type, F, id_fun()).
+
+start(Type, WriteFun, MangleArgFun) when is_function(WriteFun, 3),
+ is_function(MangleArgFun, 1) ->
+ start(Type, WriteFun, MangleArgFun, []);
+start(Type, WriteFun, InitValue) ->
+ start(Type, WriteFun, id_fun(), InitValue).
+
+start(Type, WriteFun, MangleArgFun, InitValue) when is_function(WriteFun, 3),
+ is_function(MangleArgFun, 1) ->
+ cond_start(Type, WriteFun, MangleArgFun, InitValue),
+ dbg_ssh(Type).
+
+%%%----------------------------------------------------------------
+fmt_fun(F) -> fun(Fmt,Args,Data) -> F(Fmt,Args), Data end.
+
+id_fun() -> fun(X) -> X end.
+%%%----------------------------------------------------------------
+dbg_ssh(msg) ->
+ dbg_ssh(auth),
+ dbg:tp(ssh_message,encode,1, x),
+ dbg:tp(ssh_message,decode,1, x),
+ dbg:tpl(ssh_transport,select_algorithm,4, x),
+ dbg:tp(ssh_transport,hello_version_msg,1, x),
+ dbg:tp(ssh_transport,handle_hello_version,1, x),
+ dbg:tpl(ssh_connection_handler,ext_info,2, x);
+
+dbg_ssh(auth) ->
+ dbg:tp(ssh_transport,hello_version_msg,1, x),
+ dbg:tp(ssh_transport,handle_hello_version,1, x),
+ dbg:tp(ssh_message,encode,1, x),
+ dbg:tpl(ssh_transport,select_algorithm,4, x),
+ dbg:tpl(ssh_connection_handler,ext_info,2, x),
+ lists:foreach(fun(F) -> dbg:tp(ssh_auth, F, x) end,
+ [publickey_msg, password_msg, keyboard_interactive_msg]).
+
%%%================================================================
-msg_formater({trace,Pid,call,{ssh_message,encode,[Msg]}}, D) ->
- fmt("~nSEND ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D);
-msg_formater({trace,_Pid,return_from,{ssh_message,encode,1},_Res}, D) ->
+cond_start(Type, WriteFun, MangleArgFun, Init) ->
+ try
+ dbg:start(),
+ setup_tracer(Type, WriteFun, MangleArgFun, Init),
+ dbg:p(new,[c,timestamp])
+ catch
+ _:_ -> ok
+ end.
+
+
+msg_formater(msg, {trace_ts,Pid,call,{ssh_message,encode,[Msg]},TS}, D) ->
+ fmt("~n~s SEND ~p ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D);
+msg_formater(msg, {trace_ts,_Pid,return_from,{ssh_message,encode,1},_Res,_TS}, D) ->
D;
-msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) ->
+msg_formater(msg, {trace_ts,_Pid,call,{ssh_message,decode,_},_TS}, D) ->
D;
-msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) ->
- fmt("~nRECV ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D);
+msg_formater(msg, {trace_ts,Pid,return_from,{ssh_message,decode,1},Msg,TS}, D) ->
+ fmt("~n~s ~p RECV ~s~n", [ts(TS),Pid,wr_record(shrink_bin(Msg))], D);
+
+msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_failure{authentications=As},TS}, D) ->
+ fmt("~n~s ~p Client login FAILURE. Try ~s~n", [ts(TS),Pid,As], D);
+
+msg_formater(auth, {trace_ts,Pid,return_from,{ssh_message,decode,1},#ssh_msg_userauth_success{},TS}, D) ->
+ fmt("~n~s ~p Client login SUCCESS~n", [ts(TS),Pid], D);
+
-msg_formater({trace,_Pid,call,{ssh_transport,select_algorithm,_}}, D) ->
+msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,select_algorithm,_},_TS}, D) ->
+ D;
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,select_algorithm,_},{ok,Alg},TS}, D) ->
+ fmt("~n~s ~p ALGORITHMS~n~s~n", [ts(TS),Pid, wr_record(Alg)], D);
+
+msg_formater(_, {trace_ts,_Pid,call,{ssh_transport,hello_version_msg,_},_TS}, D) ->
+ D;
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_transport,hello_version_msg,1},Hello,TS}, D) ->
+ fmt("~n~s ~p TCP SEND HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D);
+
+msg_formater(_, {trace_ts,Pid,call,{ssh_transport,handle_hello_version,[Hello]},TS}, D) ->
+ fmt("~n~s ~p RECV HELLO~n ~p~n", [ts(TS),Pid,lists:flatten(Hello)], D);
+msg_formater(_, {trace_ts,_Pid,return_from,{ssh_transport,handle_hello_version,1},_,_TS}, D) ->
D;
-msg_formater({trace,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) ->
- fmt("~nALGORITHMS ~p~n~s~n", [Pid, wr_record(Alg)], D);
-msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) ->
- fmt("~nERL MSG ~p SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D);
+msg_formater(_, {trace_ts,Pid,call,{ssh_connection_handler,ext_info,[{"server-sig-algs",SigAlgs},State]},TS}, D) ->
+ try lists:keyfind(ssh, 1, tuple_to_list(State)) of
+ false ->
+ D;
+ #ssh{userauth_pubkeys = PKs} ->
+ fmt("~n~s ~p Client got suggestion to use user public key sig-algs~n ~p~n and can use~n ~p~n",
+ [ts(TS),Pid,string:tokens(SigAlgs,","),PKs], D)
+ catch
+ _:_ ->
+ D
+ end;
+
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_connection_handler,ext_info,2},State,TS}, D) ->
+ try lists:keyfind(ssh, 1, tuple_to_list(State)) of
+ false ->
+ D;
+ #ssh{userauth_pubkeys = PKs} ->
+ fmt("~n~s ~p Client will try user public key sig-algs~n ~p~n", [ts(TS),Pid,PKs], D)
+ catch
+ _:_ ->
+ D
+ end;
+
+msg_formater(_, {trace_ts,Pid,call,{ssh_auth,publickey_msg,[[SigAlg,#ssh{user=User}]]},TS}, D) ->
+ fmt("~n~s ~p Client will try to login user ~p with public key algorithm ~p~n", [ts(TS),Pid,User,SigAlg], D);
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,publickey_msg,1},{not_ok,#ssh{user=User}},TS}, D) ->
+ fmt("~s ~p User ~p can't login with that kind of public key~n", [ts(TS),Pid,User], D);
+
+msg_formater(_, {trace_ts,Pid,call,{ssh_auth,password_msg,[[#ssh{user=User}]]},TS}, D) ->
+ fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D);
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,password_msg,1},{not_ok,#ssh{user=User}},TS}, D) ->
+ fmt("~s ~p User ~p can't login with password~n", [ts(TS),Pid,User], D);
+
+msg_formater(_, {trace_ts,Pid,call,{ssh_auth,keyboard_interactive_msg,[[#ssh{user=User}]]},TS}, D) ->
+ fmt("~n~s ~p Client will try to login user ~p with password~n", [ts(TS),Pid,User], D);
+msg_formater(_, {trace_ts,Pid,return_from,{ssh_auth,keyboard_interactive_msg,1},{not_ok,#ssh{user=User}},TS}, D) ->
+ fmt("~s ~p User ~p can't login with keyboard_interactive password~n", [ts(TS),Pid,User], D);
-msg_formater({trace,Pid,'receive',ErlangMsg}, D) ->
- fmt("~nERL MSG ~p RECIEVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D);
+msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Pid,TS}, D) ->
+ fmt("~n~s ~p TCP SEND on ~p~n ~p~n", [ts(TS),Pid,Sock, shrink_bin(Bytes)], D);
-msg_formater(M, D) ->
- fmt("~nDBG ~n~p~n", [shrink_bin(M)], D).
+msg_formater(msg, {trace_ts,Pid,send,{tcp,Sock,Bytes},Dest,TS}, D) ->
+ fmt("~n~s ~p TCP SEND from ~p TO ~p~n ~p~n", [ts(TS),Pid,Sock,Dest, shrink_bin(Bytes)], D);
-%% msg_formater(_, D) ->
-%% D.
+msg_formater(msg, {trace_ts,Pid,send,ErlangMsg,Dest,TS}, D) ->
+ fmt("~n~s ~p ERL MSG SEND TO ~p~n ~p~n", [ts(TS),Pid,Dest, shrink_bin(ErlangMsg)], D);
-fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) ->
- D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}.
+msg_formater(msg, {trace_ts,Pid,'receive',{tcp,Sock,Bytes},TS}, D) ->
+ fmt("~n~s ~p TCP RECEIVE on ~p~n ~p~n", [ts(TS),Pid,Sock,shrink_bin(Bytes)], D);
+
+msg_formater(msg, {trace_ts,Pid,'receive',ErlangMsg,TS}, D) ->
+ fmt("~n~s ~p ERL MSG RECEIVE~n ~p~n", [ts(TS),Pid,shrink_bin(ErlangMsg)], D);
+
+
+%% msg_formater(_, {trace_ts,_Pid,return_from,MFA,_Ret,_TS}=M, D) ->
+%% case lists:member(MFA, [{ssh_auth,keyboard_interactive_msg,1},
+%% {ssh_auth,password_msg,1},
+%% {ssh_auth,publickey_msg,1}]) of
+%% true ->
+%% D;
+%% false ->
+%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D)
+%% end;
+
+%% msg_formater(_, M, D) ->
+%% fmt("~nDBG ~n~p~n", [shrink_bin(M)], D).
+
+msg_formater(_, _, D) ->
+ D.
%%%----------------------------------------------------------------
-setup_tracer(Write) ->
- Handler = fun msg_formater/2,
- InitialData = #data{writer = Write},
+-record(data, {writer,
+ acc}).
+
+fmt(Fmt, Args, D=#data{writer=Write, acc=Acc}) ->
+ D#data{acc = Write(Fmt,Args,Acc)}.
+
+ts({_,_,Usec}=Now) ->
+ {_Date,{HH,MM,SS}} = calendar:now_to_local_time(Now),
+ io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.6.0w",[HH,MM,SS,Usec]);
+ts(_) ->
+ "-".
+
+setup_tracer(Type, WriteFun, MangleArgFun, Init) ->
+ Handler = fun(Arg, D) ->
+ msg_formater(Type, MangleArgFun(Arg), D)
+ end,
+ InitialData = #data{writer = WriteFun,
+ acc = Init},
{ok,_} = dbg:tracer(process, {Handler, InitialData}),
ok.
%%%----------------------------------------------------------------
-shrink_bin(B) when is_binary(B), size(B)>100 -> {'*** SHRINKED BIN',size(B),element(1,split_binary(B,20)),'***'};
+shrink_bin(B) when is_binary(B), size(B)>256 -> {'*** SHRINKED BIN',
+ size(B),
+ element(1,split_binary(B,64)),
+ '...',
+ element(2,split_binary(B,size(B)-64))
+ };
shrink_bin(L) when is_list(L) -> lists:map(fun shrink_bin/1, L);
shrink_bin(T) when is_tuple(T) -> list_to_tuple(shrink_bin(tuple_to_list(T)));
shrink_bin(X) -> X.
@@ -117,6 +254,7 @@ shrink_bin(X) -> X.
?wr_record(ssh_msg_kexdh_init);
?wr_record(ssh_msg_kexdh_reply);
?wr_record(ssh_msg_newkeys);
+?wr_record(ssh_msg_ext_info);
?wr_record(ssh_msg_kex_dh_gex_request);
?wr_record(ssh_msg_kex_dh_gex_request_old);
?wr_record(ssh_msg_kex_dh_gex_group);
diff --git a/lib/ssh/src/ssh_dbg.hrl b/lib/ssh/src/ssh_dbg.hrl
new file mode 100644
index 0000000000..e94664737b
--- /dev/null
+++ b/lib/ssh/src/ssh_dbg.hrl
@@ -0,0 +1,27 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-ifndef(SSH_DBG_HRL).
+-define(SSH_DBG_HRL, 1).
+
+-define(formatrec(RecName,R),
+ ssh_dbg:wr_record(R, record_info(fields,RecName), [])).
+
+-endif. % SSH_DBG_HRL defined
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 216f65f33a..33792da38f 100644
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -75,17 +75,9 @@ host_key(Algorithm, Opts) ->
Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
case decode(File, Password) of
{ok,Key} ->
- case {Key,Algorithm} of
- {#'RSAPrivateKey'{}, 'ssh-rsa'} -> {ok,Key};
- {#'DSAPrivateKey'{}, 'ssh-dss'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp256r1'}}, 'ecdsa-sha2-nistp256'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp384r1'}}, 'ecdsa-sha2-nistp384'} -> {ok,Key};
- {#'ECPrivateKey'{parameters = {namedCurve, ?'secp521r1'}}, 'ecdsa-sha2-nistp521'} -> {ok,Key};
- _ ->
- {error,bad_keytype_in_file}
- end;
- Other ->
- Other
+ check_key_type(Key, Algorithm);
+ {error,DecodeError} ->
+ {error,DecodeError}
end.
is_auth_key(Key, User,Opts) ->
@@ -109,12 +101,25 @@ is_host_key(Key, PeerName, Algorithm, Opts) ->
user_key(Algorithm, Opts) ->
File = file_name(user, identity_key_filename(Algorithm), Opts),
Password = proplists:get_value(identity_pass_phrase(Algorithm), Opts, ignore),
- decode(File, Password).
+ case decode(File, Password) of
+ {ok, Key} ->
+ check_key_type(Key, Algorithm);
+ Error ->
+ Error
+ end.
%% Internal functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+check_key_type(Key, Algorithm) ->
+ case ssh_transport:valid_key_sha_alg(Key,Algorithm) of
+ true -> {ok,Key};
+ false -> {error,bad_keytype_in_file}
+ end.
file_base_name('ssh-rsa' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-256' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-384' ) -> "ssh_host_rsa_key";
+file_base_name('rsa-sha2-512' ) -> "ssh_host_rsa_key";
file_base_name('ssh-dss' ) -> "ssh_host_dsa_key";
file_base_name('ecdsa-sha2-nistp256') -> "ssh_host_ecdsa_key";
file_base_name('ecdsa-sha2-nistp384') -> "ssh_host_ecdsa_key";
@@ -192,8 +197,8 @@ lookup_user_key(Key, User, Opts) ->
ssh_dir({remoteuser, User}, Opts) ->
case proplists:get_value(user_dir_fun, Opts) of
undefined ->
- case proplists:get_value(user_dir, Opts) of
- undefined ->
+ case proplists:get_value(user_dir, Opts, false) of
+ false ->
default_user_dir();
Dir ->
Dir
@@ -221,6 +226,8 @@ file_name(Type, Name, Opts) ->
%% in: "host" out: "host,1.2.3.4.
+add_ip(IP) when is_tuple(IP) ->
+ ssh_connection:encode_ip(IP);
add_ip(Host) ->
case inet:getaddr(Host, inet) of
{ok, Addr} ->
@@ -251,12 +258,18 @@ do_lookup_host_key(KeyToMatch, Host, Alg, Opts) ->
identity_key_filename('ssh-dss' ) -> "id_dsa";
identity_key_filename('ssh-rsa' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-256' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-384' ) -> "id_rsa";
+identity_key_filename('rsa-sha2-512' ) -> "id_rsa";
identity_key_filename('ecdsa-sha2-nistp256') -> "id_ecdsa";
identity_key_filename('ecdsa-sha2-nistp384') -> "id_ecdsa";
identity_key_filename('ecdsa-sha2-nistp521') -> "id_ecdsa".
identity_pass_phrase("ssh-dss" ) -> dsa_pass_phrase;
identity_pass_phrase("ssh-rsa" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-256" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-384" ) -> rsa_pass_phrase;
+identity_pass_phrase("rsa-sha2-512" ) -> rsa_pass_phrase;
identity_pass_phrase("ecdsa-sha2-"++_) -> ecdsa_pass_phrase;
identity_pass_phrase(P) when is_atom(P) ->
identity_pass_phrase(atom_to_list(P)).
diff --git a/lib/ssh/src/ssh_info.erl b/lib/ssh/src/ssh_info.erl
index 0c24c09887..d464def6fa 100644
--- a/lib/ssh/src/ssh_info.erl
+++ b/lib/ssh/src/ssh_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 026d0f6151..8ba759ad60 100644
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,60 +27,57 @@
-export([yes_no/2, read_password/2, read_line/2, format/2]).
-include("ssh.hrl").
-read_line(Prompt, Ssh) ->
+read_line(Prompt, Opts) ->
format("~s", [listify(Prompt)]),
- proplists:get_value(user_pid, Ssh) ! {self(), question},
+ ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question},
receive
- Answer ->
+ Answer when is_list(Answer) ->
Answer
end.
-yes_no(Prompt, Ssh) ->
- io:format("~s [y/n]?", [Prompt]),
- proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question},
+yes_no(Prompt, Opts) ->
+ format("~s [y/n]?", [Prompt]),
+ ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question},
receive
- Answer ->
+ %% I can't see that the atoms y and n are ever received, but it must
+ %% be investigated before removing
+ y -> yes;
+ n -> no;
+
+ Answer when is_list(Answer) ->
case trim(Answer) of
"y" -> yes;
"n" -> no;
"Y" -> yes;
"N" -> no;
- y -> yes;
- n -> no;
_ ->
- io:format("please answer y or n\n"),
- yes_no(Prompt, Ssh)
+ format("please answer y or n\n",[]),
+ yes_no(Prompt, Opts)
end
end.
-
-read_password(Prompt, Ssh) ->
+read_password(Prompt, Opts) ->
format("~s", [listify(Prompt)]),
- case is_list(Ssh) of
- false ->
- proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password};
- _ ->
- proplists:get_value(user_pid, Ssh) ! {self(), user_password}
- end,
+ ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password},
receive
- Answer ->
- case Answer of
- "" ->
- read_password(Prompt, Ssh);
- Pass -> Pass
- end
+ Answer when is_list(Answer) ->
+ case trim(Answer) of
+ "" ->
+ read_password(Prompt, Opts);
+ Pwd ->
+ Pwd
+ end
end.
-listify(A) when is_atom(A) ->
- atom_to_list(A);
-listify(L) when is_list(L) ->
- L;
-listify(B) when is_binary(B) ->
- binary_to_list(B).
format(Fmt, Args) ->
io:format(Fmt, Args).
+%%%================================================================
+listify(A) when is_atom(A) -> atom_to_list(A);
+listify(L) when is_list(L) -> L;
+listify(B) when is_binary(B) -> binary_to_list(B).
+
trim(Line) when is_list(Line) ->
lists:reverse(trim1(lists:reverse(trim1(Line))));
@@ -93,6 +90,3 @@ trim1([$\r|Cs]) -> trim(Cs);
trim1([$\n|Cs]) -> trim(Cs);
trim1([$\t|Cs]) -> trim(Cs);
trim1(Cs) -> Cs.
-
-
-
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 562f040477..b1fc05ae33 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -215,6 +215,16 @@ encode(#ssh_msg_service_accept{
}) ->
<<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring_utf8(Service)>>;
+encode(#ssh_msg_ext_info{
+ nr_extensions = N,
+ data = Data
+ }) ->
+ lists:foldl(fun({ExtName,ExtVal}, Acc) ->
+ <<Acc/binary, ?Estring(ExtName), ?Estring(ExtVal)>>
+ end,
+ <<?Ebyte(?SSH_MSG_EXT_INFO), ?Euint32(N)>>,
+ Data);
+
encode(#ssh_msg_newkeys{}) ->
<<?Ebyte(?SSH_MSG_NEWKEYS)>>;
@@ -435,6 +445,18 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) ->
num_responses = Num,
data = Data};
+decode(<<?BYTE(?SSH_MSG_EXT_INFO), ?UINT32(N), BinData/binary>>) ->
+ Data = bin_foldr(
+ fun(Bin,Acc) when length(Acc) == N ->
+ {Bin,Acc};
+ (<<?DEC_BIN(V0,__0), ?DEC_BIN(V1,__1), Rest/binary>>, Acc) ->
+ {Rest,[{binary_to_list(V0),binary_to_list(V1)}|Acc]}
+ end, [], BinData),
+ #ssh_msg_ext_info{
+ nr_extensions = N,
+ data = Data
+ };
+
%%% Keyexchange messages
decode(<<?BYTE(?SSH_MSG_KEXINIT), Cookie:128, Data/binary>>) ->
decode_kex_init(Data, [Cookie, ssh_msg_kexinit], 10);
@@ -537,17 +559,28 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bool), ?DEC_BIN(Msg,__0), ?DEC_BIN(Lang,__
%%% Helper functions
%%%
+bin_foldr(Fun, Acc, Bin) ->
+ lists:reverse(bin_foldl(Fun, Acc, Bin)).
+
+bin_foldl(_, Acc, <<>>) -> Acc;
+bin_foldl(Fun, Acc0, Bin0) ->
+ {Bin,Acc} = Fun(Bin0,Acc0),
+ bin_foldl(Fun, Acc, Bin).
+
+%%%----------------------------------------------------------------
decode_keyboard_interactive_prompts(<<>>, Acc) ->
lists:reverse(Acc);
decode_keyboard_interactive_prompts(<<?DEC_BIN(Prompt,__0), ?BYTE(Bool), Bin/binary>>,
Acc) ->
decode_keyboard_interactive_prompts(Bin, [{Prompt, erl_boolean(Bool)} | Acc]).
+%%%----------------------------------------------------------------
erl_boolean(0) ->
false;
erl_boolean(1) ->
true.
+%%%----------------------------------------------------------------
decode_kex_init(<<?BYTE(Bool), ?UINT32(X)>>, Acc, 0) ->
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
decode_kex_init(<<?BYTE(Bool)>>, Acc, 0) ->
@@ -565,15 +598,16 @@ decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) ->
%%% Signature decode/encode
%%%
-decode_signature(<<?DEC_BIN(_Alg,__0), ?UINT32(_), Signature/binary>>) ->
- Signature.
+decode_signature(<<?DEC_BIN(Alg,__0), ?UINT32(_), Signature/binary>>) ->
+ {binary_to_list(Alg), Signature}.
-encode_signature(#'RSAPublicKey'{}, Signature) ->
- <<?Ebinary(<<"ssh-rsa">>), ?Ebinary(Signature)>>;
-encode_signature({_, #'Dss-Parms'{}}, Signature) ->
+encode_signature({#'RSAPublicKey'{},Sign}, Signature) ->
+ SignName = list_to_binary(atom_to_list(Sign)),
+ <<?Ebinary(SignName), ?Ebinary(Signature)>>;
+encode_signature({{_, #'Dss-Parms'{}},_}, Signature) ->
<<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
-encode_signature({#'ECPoint'{}, {namedCurve,OID}}, Signature) ->
+encode_signature({{#'ECPoint'{}, {namedCurve,OID}},_}, Signature) ->
CurveName = public_key:oid2ssh_curvename(OID),
<<?Ebinary(<<"ecdsa-sha2-",CurveName/binary>>), ?Ebinary(Signature)>>.
diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl
new file mode 100644
index 0000000000..7eeed70739
--- /dev/null
+++ b/lib/ssh/src/ssh_options.erl
@@ -0,0 +1,904 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+-module(ssh_options).
+
+-include("ssh.hrl").
+-include_lib("kernel/include/file.hrl").
+
+-export([default/1,
+ get_value/5, get_value/6,
+ put_value/5,
+ delete_key/5,
+ handle_options/2
+ ]).
+
+-export_type([options/0
+ ]).
+
+%%%================================================================
+%%% Types
+
+-type option_in() :: proplists:property() | proplists:proplist() .
+
+-type option_class() :: internal_options | socket_options | user_options .
+
+-type option_declaration() :: #{class := user_options,
+ chk := fun((any) -> boolean() | {true,any()}),
+ default => any()
+ }.
+
+-type option_declarations() :: #{ {option_key(),def} := option_declaration() }.
+
+-type error() :: {error,{eoptions,any()}} .
+
+%%%================================================================
+%%%
+%%% Get an option
+%%%
+
+-spec get_value(option_class(), option_key(), options(),
+ atom(), non_neg_integer()) -> any() | no_return().
+
+get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
+ case Class of
+ internal_options -> maps:get(Key, maps:get(internal_options,Opts));
+ socket_options -> proplists:get_value(Key, maps:get(socket_options,Opts));
+ user_options -> maps:get(Key, Opts)
+ end;
+get_value(Class, Key, Opts, _CallerMod, _CallerLine) ->
+ error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}).
+
+
+-spec get_value(option_class(), option_key(), options(), fun(() -> any()),
+ atom(), non_neg_integer()) -> any() | no_return().
+
+get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) ->
+ proplists:get_value(Key, maps:get(socket_options,Opts), DefFun);
+get_value(Class, Key, Opts, DefFun, CallerMod, CallerLine) when is_map(Opts) ->
+ try get_value(Class, Key, Opts, CallerMod, CallerLine)
+ of
+ undefined -> DefFun();
+ Value -> Value
+ catch
+ error:{badkey,Key} -> DefFun()
+ end;
+get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) ->
+ error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}).
+
+
+%%%================================================================
+%%%
+%%% Put an option
+%%%
+
+-spec put_value(option_class(), option_in(), options(),
+ atom(), non_neg_integer()) -> options().
+
+put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
+ put_user_value(KeyVal, Opts);
+
+put_value(internal_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
+ InternalOpts = maps:get(internal_options,Opts),
+ Opts#{internal_options := put_internal_value(KeyVal, InternalOpts)};
+
+put_value(socket_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
+ SocketOpts = maps:get(socket_options,Opts),
+ Opts#{socket_options := put_socket_value(KeyVal, SocketOpts)}.
+
+
+%%%----------------
+put_user_value(L, Opts) when is_list(L) ->
+ lists:foldl(fun put_user_value/2, Opts, L);
+put_user_value({Key,Value}, Opts) ->
+ Opts#{Key := Value}.
+
+%%%----------------
+put_internal_value(L, IntOpts) when is_list(L) ->
+ lists:foldl(fun put_internal_value/2, IntOpts, L);
+put_internal_value({Key,Value}, IntOpts) ->
+ IntOpts#{Key => Value}.
+
+%%%----------------
+put_socket_value(L, SockOpts) when is_list(L) ->
+ L ++ SockOpts;
+put_socket_value({Key,Value}, SockOpts) ->
+ [{Key,Value} | SockOpts];
+put_socket_value(A, SockOpts) when is_atom(A) ->
+ [A | SockOpts].
+
+%%%================================================================
+%%%
+%%% Delete an option
+%%%
+
+-spec delete_key(option_class(), option_key(), options(),
+ atom(), non_neg_integer()) -> options().
+
+delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
+ InternalOpts = maps:get(internal_options,Opts),
+ Opts#{internal_options := maps:remove(Key, InternalOpts)}.
+
+
+%%%================================================================
+%%%
+%%% Initialize the options
+%%%
+
+-spec handle_options(role(), proplists:proplist()) -> options() | error() .
+
+-spec handle_options(role(), proplists:proplist(), options()) -> options() | error() .
+
+handle_options(Role, PropList0) ->
+ handle_options(Role, PropList0, #{socket_options => [],
+ internal_options => #{},
+ user_options => []
+ }).
+
+handle_options(Role, PropList0, Opts0) when is_map(Opts0),
+ is_list(PropList0) ->
+ PropList1 = proplists:unfold(PropList0),
+ try
+ OptionDefinitions = default(Role),
+ InitialMap =
+ maps:fold(
+ fun({K,def}, #{default:=V}, M) -> M#{K=>V};
+ (_,_,M) -> M
+ end,
+ Opts0#{user_options =>
+ maps:get(user_options,Opts0) ++ PropList1
+ },
+ OptionDefinitions),
+ %% Enter the user's values into the map; unknown keys are
+ %% treated as socket options
+ lists:foldl(fun(KV, Vals) ->
+ save(KV, OptionDefinitions, Vals)
+ end, InitialMap, PropList1)
+ catch
+ error:{eoptions, KV, undefined} ->
+ {error, {eoptions,KV}};
+
+ error:{eoptions, KV, Txt} when is_list(Txt) ->
+ {error, {eoptions,{KV,lists:flatten(Txt)}}};
+
+ error:{eoptions, KV, Extra} ->
+ {error, {eoptions,{KV,Extra}}}
+ end.
+
+
+check_fun(Key, Defs) ->
+ #{chk := Fun} = maps:get({Key,def}, Defs),
+ Fun.
+
+%%%================================================================
+%%%
+%%% Check and save one option
+%%%
+
+
+%%% First some prohibited inet options:
+save({K,V}, _, _) when K == reuseaddr ;
+ K == active
+ ->
+ forbidden_option(K, V);
+
+%%% then compatibility conversions:
+save({allow_user_interaction,V}, Opts, Vals) ->
+ save({user_interaction,V}, Opts, Vals);
+
+%% Special case for socket options 'inet' and 'inet6'
+save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 ->
+ save({inet,Inet}, Defs, OptMap);
+
+%% Two clauses to prepare for a proplists:unfold
+save({Inet,true}, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap);
+save({Inet,false}, _Defs, OptMap) when Inet==inet ; Inet==inet6 -> OptMap;
+
+%% and finaly the 'real stuff':
+save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
+ try (check_fun(Key,Defs))(Value)
+ of
+ true ->
+ OptMap#{Key := Value};
+ {true, ModifiedValue} ->
+ OptMap#{Key := ModifiedValue};
+ false ->
+ error({eoptions, {Key,Value}, "Bad value"})
+ catch
+ %% An unknown Key (= not in the definition map) is
+ %% regarded as an inet option:
+ error:{badkey,{inet,def}} ->
+ %% atomic (= non-tuple) options 'inet' and 'inet6':
+ OptMap#{socket_options := [Value | maps:get(socket_options,OptMap)]};
+ error:{badkey,{Key,def}} ->
+ OptMap#{socket_options := [{Key,Value} | maps:get(socket_options,OptMap)]};
+
+ %% But a Key that is known but the value does not validate
+ %% by the check fun will give an error exception:
+ error:{check,{BadValue,Extra}} ->
+ error({eoptions, {Key,BadValue}, Extra})
+ end.
+
+%%%================================================================
+%%%
+%%% Default options
+%%%
+
+-spec default(role() | common) -> option_declarations() .
+
+default(server) ->
+ (default(common))
+ #{
+ {subsystems, def} =>
+ #{default => [ssh_sftpd:subsystem_spec([])],
+ chk => fun(L) ->
+ is_list(L) andalso
+ lists:all(fun({Name,{CB,Args}}) ->
+ check_string(Name) andalso
+ is_atom(CB) andalso
+ is_list(Args);
+ (_) ->
+ false
+ end, L)
+ end,
+ class => user_options
+ },
+
+ {shell, def} =>
+ #{default => {shell, start, []},
+ chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A);
+ (V) -> check_function1(V) orelse check_function2(V)
+ end,
+ class => user_options
+ },
+
+ {exec, def} => % FIXME: need some archeology....
+ #{default => undefined,
+ chk => fun({M,F,_}) -> is_atom(M) andalso is_atom(F);
+ (V) -> is_function(V)
+ end,
+ class => user_options
+ },
+
+ {ssh_cli, def} =>
+ #{default => undefined,
+ chk => fun({Cb, As}) -> is_atom(Cb) andalso is_list(As);
+ (V) -> V == no_cli
+ end,
+ class => user_options
+ },
+
+ {system_dir, def} =>
+ #{default => "/etc/ssh",
+ chk => fun(V) -> check_string(V) andalso check_dir(V) end,
+ class => user_options
+ },
+
+ {auth_method_kb_interactive_data, def} =>
+ #{default => undefined, % Default value can be constructed when User is known
+ chk => fun({S1,S2,S3,B}) ->
+ check_string(S1) andalso
+ check_string(S2) andalso
+ check_string(S3) andalso
+ is_boolean(B);
+ (F) ->
+ check_function3(F)
+ end,
+ class => user_options
+ },
+
+ {user_passwords, def} =>
+ #{default => [],
+ chk => fun(V) ->
+ is_list(V) andalso
+ lists:all(fun({S1,S2}) ->
+ check_string(S1) andalso
+ check_string(S2)
+ end, V)
+ end,
+ class => user_options
+ },
+
+ {password, def} =>
+ #{default => undefined,
+ chk => fun check_string/1,
+ class => user_options
+ },
+
+ {dh_gex_groups, def} =>
+ #{default => undefined,
+ chk => fun check_dh_gex_groups/1,
+ class => user_options
+ },
+
+ {dh_gex_limits, def} =>
+ #{default => {0, infinity},
+ chk => fun({I1,I2}) ->
+ check_pos_integer(I1) andalso
+ check_pos_integer(I2) andalso
+ I1 < I2;
+ (_) ->
+ false
+ end,
+ class => user_options
+ },
+
+ {pwdfun, def} =>
+ #{default => undefined,
+ chk => fun(V) -> check_function4(V) orelse check_function2(V) end,
+ class => user_options
+ },
+
+ {negotiation_timeout, def} =>
+ #{default => 2*60*1000,
+ chk => fun check_timeout/1,
+ class => user_options
+ },
+
+ {max_sessions, def} =>
+ #{default => infinity,
+ chk => fun check_pos_integer/1,
+ class => user_options
+ },
+
+ {max_channels, def} =>
+ #{default => infinity,
+ chk => fun check_pos_integer/1,
+ class => user_options
+ },
+
+ {parallel_login, def} =>
+ #{default => false,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
+ {minimal_remote_max_packet_size, def} =>
+ #{default => 0,
+ chk => fun check_pos_integer/1,
+ class => user_options
+ },
+
+ {failfun, def} =>
+ #{default => fun(_,_,_) -> void end,
+ chk => fun(V) -> check_function3(V) orelse
+ check_function2(V) % Backwards compatibility
+ end,
+ class => user_options
+ },
+
+ {connectfun, def} =>
+ #{default => fun(_,_,_) -> void end,
+ chk => fun check_function3/1,
+ class => user_options
+ },
+
+%%%%% Undocumented
+ {infofun, def} =>
+ #{default => fun(_,_,_) -> void end,
+ chk => fun(V) -> check_function3(V) orelse
+ check_function2(V) % Backwards compatibility
+ end,
+ class => user_options
+ }
+ };
+
+default(client) ->
+ (default(common))
+ #{
+ {dsa_pass_phrase, def} =>
+ #{default => undefined,
+ chk => fun check_string/1,
+ class => user_options
+ },
+
+ {rsa_pass_phrase, def} =>
+ #{default => undefined,
+ chk => fun check_string/1,
+ class => user_options
+ },
+
+ {silently_accept_hosts, def} =>
+ #{default => false,
+ chk => fun check_silently_accept_hosts/1,
+ class => user_options
+ },
+
+ {user_interaction, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
+ {pref_public_key_algs, def} =>
+ #{default => ssh_transport:default_algorithms(public_key),
+ chk => fun check_pref_public_key_algs/1,
+ class => user_options
+ },
+
+ {dh_gex_limits, def} =>
+ #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays?
+ chk => fun({Min,I,Max}) ->
+ lists:all(fun check_pos_integer/1,
+ [Min,I,Max]);
+ (_) -> false
+ end,
+ class => user_options
+ },
+
+ {connect_timeout, def} =>
+ #{default => infinity,
+ chk => fun check_timeout/1,
+ class => user_options
+ },
+
+ {user, def} =>
+ #{default =>
+ begin
+ Env = case os:type() of
+ {win32, _} -> "USERNAME";
+ {unix, _} -> "LOGNAME"
+ end,
+ case os:getenv(Env) of
+ false ->
+ case os:getenv("USER") of
+ false -> undefined;
+ User -> User
+ end;
+ User ->
+ User
+ end
+ end,
+ chk => fun check_string/1,
+ class => user_options
+ },
+
+ {password, def} =>
+ #{default => undefined,
+ chk => fun check_string/1,
+ class => user_options
+ },
+
+ {quiet_mode, def} =>
+ #{default => false,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
+%%%%% Undocumented
+ {keyboard_interact_fun, def} =>
+ #{default => undefined,
+ chk => fun check_function3/1,
+ class => user_options
+ }
+ };
+
+default(common) ->
+ #{
+ {user_dir, def} =>
+ #{default => false, % FIXME: TBD ~/.ssh at time of call when user is known
+ chk => fun(V) -> check_string(V) andalso check_dir(V) end,
+ class => user_options
+ },
+
+ {preferred_algorithms, def} =>
+ #{default => ssh:default_algorithms(),
+ chk => fun check_preferred_algorithms/1,
+ class => user_options
+ },
+
+ {id_string, def} =>
+ #{default => undefined, % FIXME: see ssh_transport:ssh_vsn/0
+ chk => fun(random) ->
+ {true, {random,2,5}}; % 2 - 5 random characters
+ ({random,I1,I2}) ->
+ %% Undocumented
+ check_pos_integer(I1) andalso
+ check_pos_integer(I2) andalso
+ I1=<I2;
+ (V) ->
+ check_string(V)
+ end,
+ class => user_options
+ },
+
+ {key_cb, def} =>
+ #{default => {ssh_file, []},
+ chk => fun({Mod,Opts}) -> is_atom(Mod) andalso is_list(Opts);
+ (Mod) when is_atom(Mod) -> {true, {Mod,[]}};
+ (_) -> false
+ end,
+ class => user_options
+ },
+
+ {profile, def} =>
+ #{default => ?DEFAULT_PROFILE,
+ chk => fun erlang:is_atom/1,
+ class => user_options
+ },
+
+ {idle_time, def} =>
+ #{default => infinity,
+ chk => fun check_timeout/1,
+ class => user_options
+ },
+
+ %% This is a "SocketOption"...
+ %% {fd, def} =>
+ %% #{default => undefined,
+ %% chk => fun erlang:is_integer/1,
+ %% class => user_options
+ %% },
+
+ {disconnectfun, def} =>
+ #{default => fun(_) -> void end,
+ chk => fun check_function1/1,
+ class => user_options
+ },
+
+ {unexpectedfun, def} =>
+ #{default => fun(_,_) -> report end,
+ chk => fun check_function2/1,
+ class => user_options
+ },
+
+ {ssh_msg_debug_fun, def} =>
+ #{default => fun(_,_,_,_) -> void end,
+ chk => fun check_function4/1,
+ class => user_options
+ },
+
+ {rekey_limit, def} => % FIXME: Why not common?
+ #{default => 1024000000,
+ chk => fun check_non_neg_integer/1,
+ class => user_options
+ },
+
+ {auth_methods, def} =>
+ #{default => ?SUPPORTED_AUTH_METHODS,
+ chk => fun(As) ->
+ try
+ Sup = string:tokens(?SUPPORTED_AUTH_METHODS, ","),
+ New = string:tokens(As, ","),
+ [] == [X || X <- New,
+ not lists:member(X,Sup)]
+ catch
+ _:_ -> false
+ end
+ end,
+ class => user_options
+ },
+
+%%%%% Undocumented
+ {transport, def} =>
+ #{default => ?DEFAULT_TRANSPORT,
+ chk => fun({A,B,C}) ->
+ is_atom(A) andalso is_atom(B) andalso is_atom(C)
+ end,
+ class => user_options
+ },
+
+ {vsn, def} =>
+ #{default => {2,0},
+ chk => fun({Maj,Min}) -> check_non_neg_integer(Maj) andalso check_non_neg_integer(Min);
+ (_) -> false
+ end,
+ class => user_options
+ },
+
+ {tstflg, def} =>
+ #{default => [],
+ chk => fun erlang:is_list/1,
+ class => user_options
+ },
+
+ {user_dir_fun, def} =>
+ #{default => undefined,
+ chk => fun check_function1/1,
+ class => user_options
+ },
+
+ {max_random_length_padding, def} =>
+ #{default => ?MAX_RND_PADDING_LEN,
+ chk => fun check_non_neg_integer/1,
+ class => user_options
+ },
+
+ {send_ext_info, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ },
+
+ {recv_ext_info, def} =>
+ #{default => true,
+ chk => fun erlang:is_boolean/1,
+ class => user_options
+ }
+ }.
+
+
+%%%================================================================
+%%%================================================================
+%%%================================================================
+
+%%%
+%%% check_*/1 -> true | false | error({check,Spec})
+%%% See error_in_check/2,3
+%%%
+
+%%% error_in_check(BadValue) -> error_in_check(BadValue, undefined).
+
+error_in_check(BadValue, Extra) -> error({check,{BadValue,Extra}}).
+
+
+%%%----------------------------------------------------------------
+check_timeout(infinity) -> true;
+check_timeout(I) -> check_pos_integer(I).
+
+%%%----------------------------------------------------------------
+check_pos_integer(I) -> is_integer(I) andalso I>0.
+
+%%%----------------------------------------------------------------
+check_non_neg_integer(I) -> is_integer(I) andalso I>=0.
+
+%%%----------------------------------------------------------------
+check_function1(F) -> is_function(F,1).
+check_function2(F) -> is_function(F,2).
+check_function3(F) -> is_function(F,3).
+check_function4(F) -> is_function(F,4).
+
+%%%----------------------------------------------------------------
+check_pref_public_key_algs(V) ->
+ %% Get the dynamically supported keys, that is, thoose
+ %% that are stored
+ PKs = ssh_transport:supported_algorithms(public_key),
+ CHK = fun(A, Ack) ->
+ case lists:member(A, PKs) of
+ true ->
+ case lists:member(A,Ack) of
+ false -> [A|Ack];
+ true -> Ack % Remove duplicates
+ end;
+ false -> error_in_check(A, "Not supported public key")
+ end
+ end,
+ case lists:foldr(
+ fun(ssh_dsa, Ack) -> CHK('ssh-dss', Ack); % compatibility
+ (ssh_rsa, Ack) -> CHK('ssh-rsa', Ack); % compatibility
+ (X, Ack) -> CHK(X, Ack)
+ end, [], V)
+ of
+ V -> true;
+ [] -> false;
+ V1 -> {true,V1}
+ end.
+
+
+%%%----------------------------------------------------------------
+%% Check that it is a directory and is readable
+check_dir(Dir) ->
+ case file:read_file_info(Dir) of
+ {ok, #file_info{type = directory,
+ access = Access}} ->
+ case Access of
+ read -> true;
+ read_write -> true;
+ _ -> error_in_check(Dir, eacces)
+ end;
+
+ {ok, #file_info{}}->
+ error_in_check(Dir, enotdir);
+
+ {error, Error} ->
+ error_in_check(Dir, Error)
+ end.
+
+%%%----------------------------------------------------------------
+check_string(S) -> is_list(S). % FIXME: stub
+
+%%%----------------------------------------------------------------
+check_dh_gex_groups({file,File}) when is_list(File) ->
+ case file:consult(File) of
+ {ok, GroupDefs} ->
+ check_dh_gex_groups(GroupDefs);
+ {error, Error} ->
+ error_in_check({file,File},Error)
+ end;
+
+check_dh_gex_groups({ssh_moduli_file,File}) when is_list(File) ->
+ case file:open(File,[read]) of
+ {ok,D} ->
+ try
+ read_moduli_file(D, 1, [])
+ of
+ {ok,Moduli} ->
+ check_dh_gex_groups(Moduli);
+ {error,Error} ->
+ error_in_check({ssh_moduli_file,File}, Error)
+ catch
+ _:_ ->
+ error_in_check({ssh_moduli_file,File}, "Bad format in file "++File)
+ after
+ file:close(D)
+ end;
+
+ {error, Error} ->
+ error_in_check({ssh_moduli_file,File}, Error)
+ end;
+
+check_dh_gex_groups(L0) when is_list(L0), is_tuple(hd(L0)) ->
+ {true,
+ collect_per_size(
+ lists:foldl(
+ fun({N,G,P}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,{G,P}}, Acc) when is_integer(N),N>0,
+ is_integer(G),G>0,
+ is_integer(P),P>0 ->
+ [{N,{G,P}} | Acc];
+ ({N,GPs}, Acc) when is_list(GPs) ->
+ lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0,
+ is_integer(Pi),Pi>0 ->
+ [{N,{Gi,Pi}} | Acci]
+ end, Acc, GPs)
+ end, [], L0))};
+
+check_dh_gex_groups(_) ->
+ false.
+
+
+
+collect_per_size(L) ->
+ lists:foldr(
+ fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc];
+ ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc]
+ end, [], lists:sort(L)).
+
+read_moduli_file(D, I, Acc) ->
+ case io:get_line(D,"") of
+ {error,Error} ->
+ {error,Error};
+ eof ->
+ {ok, Acc};
+ "#" ++ _ -> read_moduli_file(D, I+1, Acc);
+ <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc);
+ Data ->
+ Line = if is_binary(Data) -> binary_to_list(Data);
+ is_list(Data) -> Data
+ end,
+ try
+ [_Time,_Class,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"),
+ M = {list_to_integer(Size),
+ {list_to_integer(G), list_to_integer(P,16)}
+ },
+ read_moduli_file(D, I+1, [M|Acc])
+ catch
+ _:_ ->
+ read_moduli_file(D, I+1, Acc)
+ end
+ end.
+
+%%%----------------------------------------------------------------
+-define(SHAs, [md5, sha, sha224, sha256, sha384, sha512]).
+
+check_silently_accept_hosts(B) when is_boolean(B) -> true;
+check_silently_accept_hosts(F) when is_function(F,2) -> true;
+check_silently_accept_hosts({false,S}) when is_atom(S) -> valid_hash(S);
+check_silently_accept_hosts({S,F}) when is_function(F,2) -> valid_hash(S);
+check_silently_accept_hosts(_) -> false.
+
+
+valid_hash(S) -> valid_hash(S, proplists:get_value(hashs,crypto:supports())).
+
+valid_hash(S, Ss) when is_atom(S) -> lists:member(S, ?SHAs) andalso lists:member(S, Ss);
+valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L);
+valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec").
+
+%%%----------------------------------------------------------------
+check_preferred_algorithms(Algs) ->
+ [error_in_check(K,"Bad preferred_algorithms key")
+ || {K,_} <- Algs,
+ not lists:keymember(K,1,ssh:default_algorithms())],
+
+ try alg_duplicates(Algs, [], [])
+ of
+ [] ->
+ {true,
+ [case proplists:get_value(Key, Algs) of
+ undefined ->
+ {Key,DefAlgs};
+ Vals ->
+ handle_pref_alg(Key,Vals,SupAlgs)
+ end
+ || {{Key,DefAlgs}, {Key,SupAlgs}} <- lists:zip(ssh:default_algorithms(),
+ ssh_transport:supported_algorithms())
+ ]
+ };
+
+ Dups ->
+ error_in_check(Dups, "Duplicates")
+ catch
+ _:_ ->
+ false
+ 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, _) ->
+ error_in_check({Key,Vs}, "Badly formed list").
+
+chk_alg_vs(OptKey, Values, SupportedValues) ->
+ case (Values -- SupportedValues) of
+ [] -> Values;
+ [none] -> [none]; % for testing only
+ Bad -> error_in_check({OptKey,Bad}, "Unsupported value(s) found")
+ end.
+
+%%%----------------------------------------------------------------
+forbidden_option(K,V) ->
+ Txt = io_lib:format("The option '~s' is used internally. The "
+ "user is not allowed to specify this option.",
+ [K]),
+ error({eoptions, {K,V}, Txt}).
+
+%%%----------------------------------------------------------------
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index afc2fb88ff..c1558a19b1 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -37,7 +37,7 @@
-export([open/3, open_tar/3, opendir/2, close/2, readdir/2, pread/4, read/3,
open/4, open_tar/4, opendir/3, close/3, readdir/3, pread/5, read/4,
apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3,
- pwrite/5, write/4,
+ pwrite/5, write/4,
position/3, real_path/2, read_file_info/2, get_file_info/2,
position/4, real_path/3, read_file_info/3, get_file_info/3,
write_file_info/3, read_link_info/2, read_link/2, make_symlink/3,
@@ -52,7 +52,7 @@
%% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp!
-export([info_to_attr/1, attr_to_info/1]).
--record(state,
+-record(state,
{
xf,
rep_buf = <<>>,
@@ -64,7 +64,7 @@
-record(fileinf,
{
- handle,
+ handle,
offset,
size,
mode
@@ -81,7 +81,7 @@
enc_text_buf = <<>>, % Encrypted text
plain_text_buf = <<>> % Decrypted text
}).
-
+
-define(FILEOP_TIMEOUT, infinity).
-define(NEXT_REQID(S),
@@ -98,20 +98,16 @@ start_channel(Cm) when is_pid(Cm) ->
start_channel(Socket) when is_port(Socket) ->
start_channel(Socket, []);
start_channel(Host) when is_list(Host) ->
- start_channel(Host, []).
-
-start_channel(Socket, Options) when is_port(Socket) ->
- Timeout =
- %% A mixture of ssh:connect and ssh_sftp:start_channel:
- case proplists:get_value(connect_timeout, Options, undefined) of
- undefined ->
- proplists:get_value(timeout, Options, infinity);
- TO ->
- TO
- end,
- case ssh:connect(Socket, Options, Timeout) of
- {ok,Cm} ->
- case start_channel(Cm, Options) of
+ start_channel(Host, []).
+
+start_channel(Socket, UserOptions) when is_port(Socket) ->
+ {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions),
+ Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel:
+ proplists:get_value(connect_timeout, SshOpts,
+ proplists:get_value(timeout, SftpOpts, infinity)),
+ case ssh:connect(Socket, SshOpts, Timeout) of
+ {ok,Cm} ->
+ case start_channel(Cm, UserOptions) of
{ok, Pid} ->
{ok, Pid, Cm};
Error ->
@@ -120,17 +116,17 @@ start_channel(Socket, Options) when is_port(Socket) ->
Error ->
Error
end;
-start_channel(Cm, Opts) when is_pid(Cm) ->
- Timeout = proplists:get_value(timeout, Opts, infinity),
- {_, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []),
+start_channel(Cm, UserOptions) when is_pid(Cm) ->
+ Timeout = proplists:get_value(timeout, UserOptions, infinity),
+ {_SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions),
case ssh_xfer:attach(Cm, [], ChanOpts) of
- {ok, ChannelId, Cm} ->
- case ssh_channel:start(Cm, ChannelId,
+ {ok, ChannelId, Cm} ->
+ case ssh_channel:start(Cm, ChannelId,
?MODULE, [Cm, ChannelId, SftpOpts]) of
{ok, Pid} ->
case wait_for_version_negotiation(Pid, Timeout) of
ok ->
- {ok, Pid};
+ {ok, Pid};
TimeOut ->
TimeOut
end;
@@ -143,15 +139,17 @@ start_channel(Cm, Opts) when is_pid(Cm) ->
Error
end;
-start_channel(Host, Opts) ->
- start_channel(Host, 22, Opts).
-start_channel(Host, Port, Opts) ->
- {SshOpts, ChanOpts, SftpOpts} = handle_options(Opts, [], [], []),
- Timeout = proplists:get_value(timeout, SftpOpts, infinity),
+start_channel(Host, UserOptions) ->
+ start_channel(Host, 22, UserOptions).
+
+start_channel(Host, Port, UserOptions) ->
+ {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions),
+ Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel:
+ proplists:get_value(connect_timeout, SshOpts,
+ proplists:get_value(timeout, SftpOpts, infinity)),
case ssh_xfer:connect(Host, Port, SshOpts, ChanOpts, Timeout) of
{ok, ChannelId, Cm} ->
- case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,
- ChannelId, SftpOpts]) of
+ case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,ChannelId,SftpOpts]) of
{ok, Pid} ->
case wait_for_version_negotiation(Pid, Timeout) of
ok ->
@@ -165,7 +163,7 @@ start_channel(Host, Port, Opts) ->
{error, ignore}
end;
Error ->
- Error
+ Error
end.
stop_channel(Pid) ->
@@ -174,12 +172,12 @@ stop_channel(Pid) ->
OldValue = process_flag(trap_exit, true),
link(Pid),
exit(Pid, ssh_sftp_stop_channel),
- receive
+ receive
{'EXIT', Pid, normal} ->
ok
after 5000 ->
exit(Pid, kill),
- receive
+ receive
{'EXIT', Pid, killed} ->
ok
end
@@ -209,9 +207,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) ->
erl_tar:init(Pid, write,
fun(write, {_,Data}) ->
write_to_remote_tar(Pid, Handle, to_bin(Data), FileOpTimeout);
- (position, {_,Pos}) ->
+ (position, {_,Pos}) ->
position(Pid, Handle, Pos, FileOpTimeout);
- (close, _) ->
+ (close, _) ->
close(Pid, Handle, FileOpTimeout)
end);
{true,false,[{crypto,{CryptoInitFun,CryptoEncryptFun,CryptoEndFun}}]} ->
@@ -245,9 +243,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) ->
erl_tar:init(Pid, read,
fun(read2, {_,Len}) ->
read_repeat(Pid, Handle, Len, FileOpTimeout);
- (position, {_,Pos}) ->
+ (position, {_,Pos}) ->
position(Pid, Handle, Pos, FileOpTimeout);
- (close, _) ->
+ (close, _) ->
close(Pid, Handle, FileOpTimeout)
end);
{false,true,[{crypto,{CryptoInitFun,CryptoDecryptFun}}]} ->
@@ -258,9 +256,9 @@ open_tar(Pid, File, Mode, FileOpTimeout) ->
erl_tar:init(Pid, read,
fun(read2, {_,Len}) ->
read_buf(Pid, SftpHandle, BufHandle, Len, FileOpTimeout);
- (position, {_,Pos}) ->
+ (position, {_,Pos}) ->
position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout);
- (close, _) ->
+ (close, _) ->
call(Pid, {erase_bufinf,BufHandle}, FileOpTimeout),
close(Pid, SftpHandle, FileOpTimeout)
end);
@@ -292,16 +290,16 @@ pread(Pid, Handle, Offset, Len, FileOpTimeout) ->
read(Pid, Handle, Len) ->
read(Pid, Handle, Len, ?FILEOP_TIMEOUT).
read(Pid, Handle, Len, FileOpTimeout) ->
- call(Pid, {read,false,Handle, Len}, FileOpTimeout).
+ call(Pid, {read,false,Handle, Len}, FileOpTimeout).
-%% TODO this ought to be a cast! Is so in all practial meaning
+%% TODO this ought to be a cast! Is so in all practical meaning
%% even if it is obscure!
apread(Pid, Handle, Offset, Len) ->
call(Pid, {pread,true,Handle, Offset, Len}, infinity).
%% TODO this ought to be a cast!
aread(Pid, Handle, Len) ->
- call(Pid, {read,true,Handle, Len}, infinity).
+ call(Pid, {read,true,Handle, Len}, infinity).
pwrite(Pid, Handle, Offset, Data) ->
pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT).
@@ -313,12 +311,12 @@ write(Pid, Handle, Data) ->
write(Pid, Handle, Data, FileOpTimeout) ->
call(Pid, {write,false,Handle,Data}, FileOpTimeout).
-%% TODO this ought to be a cast! Is so in all practial meaning
+%% TODO this ought to be a cast! Is so in all practical meaning
%% even if it is obscure!
apwrite(Pid, Handle, Offset, Data) ->
call(Pid, {pwrite,true,Handle,Offset,Data}, infinity).
-%% TODO this ought to be a cast! Is so in all practial meaning
+%% TODO this ought to be a cast! Is so in all practical meaning
%% even if it is obscure!
awrite(Pid, Handle, Data) ->
call(Pid, {write,true,Handle,Data}, infinity).
@@ -367,7 +365,7 @@ make_symlink(Pid, Name, Target) ->
make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT).
make_symlink(Pid, Name, Target, FileOpTimeout) ->
call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout).
-
+
rename(Pid, FromFile, ToFile) ->
rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT).
rename(Pid, FromFile, ToFile, FileOpTimeout) ->
@@ -411,8 +409,8 @@ list_dir(Pid, Name, FileOpTimeout) ->
close(Pid, Handle, FileOpTimeout),
case Res of
{ok, List} ->
- NList = lists:foldl(fun({Nm, _Info},Acc) ->
- [Nm|Acc] end,
+ NList = lists:foldl(fun({Nm, _Info},Acc) ->
+ [Nm|Acc] end,
[], List),
{ok,NList};
Error -> Error
@@ -482,7 +480,7 @@ write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz, FileOpTimeout) ->
<<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin,
case write(Pid, Handle, Data, FileOpTimeout) of
ok ->
- write_file_loop(Pid, Handle,
+ write_file_loop(Pid, Handle,
Pos+PacketSz, Bin, Remain-PacketSz,
PacketSz, FileOpTimeout);
Error ->
@@ -510,7 +508,7 @@ init([Cm, ChannelId, Options]) ->
Xf = #ssh_xfer{cm = Cm,
channel = ChannelId},
{ok, #state{xf = Xf,
- req_id = 0,
+ req_id = 0,
rep_buf = <<>>,
inf = new_inf(),
opts = Options}};
@@ -519,7 +517,7 @@ init([Cm, ChannelId, Options]) ->
Error ->
{stop, {shutdown, Error}}
end.
-
+
%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
@@ -541,7 +539,7 @@ handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From,
handle_call({_, wait_for_version_negotiation}, _, State) ->
{reply, ok, State};
-
+
handle_call({{timeout, infinity}, Msg}, From, State) ->
do_handle_call(Msg, From, State);
handle_call({{timeout, Timeout}, Msg}, From, #state{req_id = Id} = State) ->
@@ -555,13 +553,13 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
do_handle_call({get_bufinf,BufHandle}, _From, S=#state{inf=I0}) ->
- {reply, dict:find(BufHandle,I0), S};
+ {reply, maps:find(BufHandle,I0), S};
do_handle_call({put_bufinf,BufHandle,B}, _From, S=#state{inf=I0}) ->
- {reply, ok, S#state{inf=dict:store(BufHandle,B,I0)}};
+ {reply, ok, S#state{inf=maps:put(BufHandle,B,I0)}};
do_handle_call({erase_bufinf,BufHandle}, _From, S=#state{inf=I0}) ->
- {reply, ok, S#state{inf=dict:erase(BufHandle,I0)}};
+ {reply, ok, S#state{inf=maps:remove(BufHandle,I0)}};
do_handle_call({open, Async,FileName,Mode}, From, #state{xf = XF} = State) ->
{Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode),
@@ -636,7 +634,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) ->
binary -> {{ok,Data}, State2};
text -> {{ok,binary_to_list(Data)}, State2}
end;
- (Rep, State2) ->
+ (Rep, State2) ->
{Rep, State2}
end);
Error ->
@@ -777,7 +775,7 @@ do_handle_call(recv_window, _From, State) ->
do_handle_call(stop, _From, State) ->
{stop, shutdown, ok, State};
-do_handle_call(Call, _From, State) ->
+do_handle_call(Call, _From, State) ->
{reply, {error, bad_call, Call, State}, State}.
%%--------------------------------------------------------------------
@@ -785,13 +783,13 @@ do_handle_call(Call, _From, State) ->
%%
%% Description: Handles channel messages
%%--------------------------------------------------------------------
-handle_ssh_msg({ssh_cm, _ConnectionManager,
- {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} =
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} =
State0) ->
State = handle_reply(State0, <<Data0/binary,Data/binary>>),
{ok, State};
-handle_ssh_msg({ssh_cm, _ConnectionManager,
+handle_ssh_msg({ssh_cm, _ConnectionManager,
{data, _ChannelId, 1, Data}}, State) ->
error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
{ok, State};
@@ -803,7 +801,7 @@ handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
%% Ignore signals according to RFC 4254 section 6.9.
{ok, State};
-handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}},
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}},
State0) ->
State = reply_all(State0, {error, Error}),
{stop, ChannelId, State};
@@ -823,7 +821,7 @@ handle_msg({ssh_channel_up, _, _}, #state{opts = Options, xf = Xf} = State) ->
{ok, State};
%% Version negotiation timed out
-handle_msg({timeout, undefined, From},
+handle_msg({timeout, undefined, From},
#state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
ssh_channel:reply(From, {error, timeout}),
{stop, ChannelId, State};
@@ -839,12 +837,12 @@ handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) ->
end;
%% Connection manager goes down
-handle_msg({'DOWN', _Ref, _Type, _Process, _},
+handle_msg({'DOWN', _Ref, _Type, _Process, _},
#state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
{stop, ChannelId, State};
-
+
%% Stopped by user
-handle_msg({'EXIT', _, ssh_sftp_stop_channel},
+handle_msg({'EXIT', _, ssh_sftp_stop_channel},
#state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
{stop, ChannelId, State};
@@ -865,6 +863,9 @@ terminate(_Reason, State) ->
%%====================================================================
%% Internal functions
%%====================================================================
+handle_options(UserOptions) ->
+ handle_options(UserOptions, [], [], []).
+
handle_options([], Sftp, Chan, Ssh) ->
{Ssh, Chan, Sftp};
handle_options([{timeout, _} = Opt | Rest], Sftp, Chan, Ssh) ->
@@ -883,10 +884,10 @@ call(Pid, Msg, TimeOut) ->
handle_reply(State, <<?UINT32(Len),Reply:Len/binary,Rest/binary>>) ->
do_handle_reply(State, Reply, Rest);
-handle_reply(State, Data) ->
+handle_reply(State, Data) ->
State#state{rep_buf = Data}.
-do_handle_reply(#state{xf = Xf} = State,
+do_handle_reply(#state{xf = Xf} = State,
<<?SSH_FXP_VERSION, ?UINT32(Version), BinExt/binary>>, Rest) ->
Ext = ssh_xfer:decode_ext(BinExt),
case Xf#ssh_xfer.vsn of
@@ -899,7 +900,7 @@ do_handle_reply(#state{xf = Xf} = State,
ok
end,
ssh_channel:reply(From, ok)
- end,
+ end,
State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest};
do_handle_reply(State0, Data, Rest) ->
@@ -919,9 +920,9 @@ handle_req_reply(State0, {_, ReqID, _} = XfReply) ->
List = lists:keydelete(ReqID, 1, State0#state.req_list),
State1 = State0#state { req_list = List },
case catch Fun(xreply(XfReply),State1) of
- {'EXIT', _} ->
+ {'EXIT', _} ->
State1;
- State ->
+ State ->
State
end
end.
@@ -998,15 +999,15 @@ reply_all(State, Reply) ->
make_reply(ReqID, true, From, State) ->
{reply, {async, ReqID},
update_request_info(ReqID, State,
- fun(Reply,State1) ->
+ fun(Reply,State1) ->
async_reply(ReqID,Reply,From,State1)
end)};
make_reply(ReqID, false, From, State) ->
{noreply,
update_request_info(ReqID, State,
- fun(Reply,State1) ->
- sync_reply(Reply, From, State1)
+ fun(Reply,State1) ->
+ sync_reply(Reply, From, State1)
end)}.
make_reply_post(ReqID, true, From, State, PostFun) ->
@@ -1062,25 +1063,16 @@ attr_to_info(A) when is_record(A, ssh_xfer_attr) ->
gid = A#ssh_xfer_attr.group}.
-%% Added workaround for sftp timestam problem. (Timestamps should be
-%% in UTC but they where not) . The workaround uses a deprecated
-%% function i calandar. This will work as expected most of the time
-%% but has problems for the same reason as
-%% calendar:local_time_to_universal_time/1. We consider it better that
-%% the timestamps work as expected most of the time instead of none of
-%% the time. Hopfully the file-api will be updated so that we can
-%% solve this problem in a better way in the future.
-
unix_to_datetime(undefined) ->
undefined;
unix_to_datetime(UTCSecs) ->
- UTCDateTime =
+ UTCDateTime =
calendar:gregorian_seconds_to_datetime(UTCSecs + 62167219200),
erlang:universaltime_to_localtime(UTCDateTime).
datetime_to_unix(undefined) ->
undefined;
-datetime_to_unix(LocalDateTime) ->
+datetime_to_unix(LocalDateTime) ->
UTCDateTime = erlang:localtime_to_universaltime(LocalDateTime),
calendar:datetime_to_gregorian_seconds(UTCDateTime) - 62167219200.
@@ -1128,11 +1120,11 @@ open_mode3(Modes) ->
end,
{[], Fl, A}.
-%% accessors for inf dict
-new_inf() -> dict:new().
+%% accessors for inf map
+new_inf() -> #{}.
add_new_handle(Handle, FileMode, Inf) ->
- dict:store(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf).
+ maps:put(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf).
update_size(Handle, NewSize, State) ->
OldSize = get_size(Handle, State),
@@ -1152,27 +1144,24 @@ update_offset(Handle, NewOffset, State0) ->
%% access size and offset for handle
put_size(Handle, Size, State) ->
Inf0 = State#state.inf,
- case dict:find(Handle, Inf0) of
+ case maps:find(Handle, Inf0) of
{ok, FI} ->
- State#state{inf=dict:store(Handle, FI#fileinf{size=Size}, Inf0)};
+ State#state{inf=maps:put(Handle, FI#fileinf{size=Size}, Inf0)};
_ ->
- State#state{inf=dict:store(Handle, #fileinf{size=Size,offset=0},
- Inf0)}
+ State#state{inf=maps:put(Handle, #fileinf{size=Size,offset=0}, Inf0)}
end.
put_offset(Handle, Offset, State) ->
Inf0 = State#state.inf,
- case dict:find(Handle, Inf0) of
+ case maps:find(Handle, Inf0) of
{ok, FI} ->
- State#state{inf=dict:store(Handle, FI#fileinf{offset=Offset},
- Inf0)};
+ State#state{inf=maps:put(Handle, FI#fileinf{offset=Offset}, Inf0)};
_ ->
- State#state{inf=dict:store(Handle, #fileinf{size=Offset,
- offset=Offset}, Inf0)}
+ State#state{inf=maps:put(Handle, #fileinf{size=Offset, offset=Offset}, Inf0)}
end.
get_size(Handle, State) ->
- case dict:find(Handle, State#state.inf) of
+ case maps:find(Handle, State#state.inf) of
{ok, FI} ->
FI#fileinf.size;
_ ->
@@ -1180,11 +1169,11 @@ get_size(Handle, State) ->
end.
%% get_offset(Handle, State) ->
-%% {ok, FI} = dict:find(Handle, State#state.inf),
+%% {ok, FI} = maps:find(Handle, State#state.inf),
%% FI#fileinf.offset.
get_mode(Handle, State) ->
- case dict:find(Handle, State#state.inf) of
+ case maps:find(Handle, State#state.inf) of
{ok, FI} ->
FI#fileinf.mode;
_ ->
@@ -1192,14 +1181,14 @@ get_mode(Handle, State) ->
end.
erase_handle(Handle, State) ->
- FI = dict:erase(Handle, State#state.inf),
+ FI = maps:remove(Handle, State#state.inf),
State#state{inf = FI}.
%%
%% Caluclate a integer offset
%%
lseek_position(Handle, Pos, State) ->
- case dict:find(Handle, State#state.inf) of
+ case maps:find(Handle, State#state.inf) of
{ok, #fileinf{offset=O, size=S}} ->
lseek_pos(Pos, O, S);
_ ->
@@ -1229,7 +1218,7 @@ lseek_pos({cur, Offset}, CurOffset, _CurSize)
true ->
{ok, NewOffset}
end;
-lseek_pos({eof, Offset}, _CurOffset, CurSize)
+lseek_pos({eof, Offset}, _CurOffset, CurSize)
when is_integer(Offset) andalso -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset andalso
Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
NewOffset = CurSize + Offset,
@@ -1239,7 +1228,7 @@ lseek_pos({eof, Offset}, _CurOffset, CurSize)
{ok, NewOffset}
end;
lseek_pos(_, _, _) ->
- {error, einval}.
+ {error, einval}.
%%%================================================================
%%%
@@ -1277,13 +1266,13 @@ position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout) ->
case Pos of
{cur,0} when Mode==write ->
{ok,Size+size(Buf0)};
-
+
{cur,0} when Mode==read ->
{ok,Size};
-
+
_ when Mode==read, is_integer(Pos) ->
Skip = Pos-Size,
- if
+ if
Skip < 0 ->
{error, cannot_rewind};
Skip == 0 ->
@@ -1318,7 +1307,7 @@ read_buf(Pid, SftpHandle, BufHandle, WantedLen, FileOpTimeout) ->
eof
end.
-do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout,
+do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout,
B=#bufinf{plain_text_buf=PlainBuf0,
size = Size})
when size(PlainBuf0) >= WantedLen ->
@@ -1327,7 +1316,7 @@ do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout,
{ok,ResultBin,B#bufinf{plain_text_buf=PlainBuf,
size = Size + WantedLen}};
-do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
+do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
B0=#bufinf{plain_text_buf = PlainBuf0,
enc_text_buf = EncBuf0,
chunksize = undefined
@@ -1335,12 +1324,12 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
when size(EncBuf0) > 0 ->
%% We have (at least) one decodable byte waiting for decodeing.
{ok,DecodedBin,B} = apply_crypto(EncBuf0, B0),
- do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
+ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
B#bufinf{plain_text_buf = <<PlainBuf0/binary, DecodedBin/binary>>,
enc_text_buf = <<>>
});
-
-do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
+
+do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
B0=#bufinf{plain_text_buf = PlainBuf0,
enc_text_buf = EncBuf0,
chunksize = ChunkSize0
@@ -1349,11 +1338,11 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
%% We have (at least) one chunk of decodable bytes waiting for decodeing.
<<ToDecode:ChunkSize0/binary, EncBuf/binary>> = EncBuf0,
{ok,DecodedBin,B} = apply_crypto(ToDecode, B0),
- do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
+ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
B#bufinf{plain_text_buf = <<PlainBuf0/binary, DecodedBin/binary>>,
enc_text_buf = EncBuf
});
-
+
do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0}) ->
%% We must read more bytes and append to the buffer of encoded bytes.
case read(Pid, SftpHandle, Packet, FileOpTimeout) of
@@ -1370,7 +1359,7 @@ do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc
write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) ->
{ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
{ok,B0=#bufinf{plain_text_buf=PTB}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout),
- case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
+ case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
B0#bufinf{plain_text_buf = <<PTB/binary,PlainBin/binary>>}) of
{ok, B} ->
call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
@@ -1379,7 +1368,7 @@ write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) ->
{error,Error}
end.
-do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
+do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
B=#bufinf{enc_text_buf = EncBuf0,
size = Size})
when size(EncBuf0) >= Packet ->
@@ -1421,9 +1410,9 @@ do_the_write_buf(_Pid, _SftpHandle, _Packet, _FileOpTimeout, B) ->
apply_crypto(In, B=#bufinf{crypto_state = CState0,
crypto_fun = F}) ->
case F(In,CState0) of
- {ok,EncodedBin,CState} ->
+ {ok,EncodedBin,CState} ->
{ok, EncodedBin, B#bufinf{crypto_state=CState}};
- {ok,EncodedBin,CState,ChunkSize} ->
+ {ok,EncodedBin,CState,ChunkSize} ->
{ok, EncodedBin, B#bufinf{crypto_state=CState,
chunksize=ChunkSize}}
end.
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index dca018f20f..427edf01ab 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -34,8 +34,7 @@
%%--------------------------------------------------------------------
%% External exports
--export([subsystem_spec/1,
- listen/1, listen/2, listen/3, stop/1]).
+-export([subsystem_spec/1]).
-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]).
@@ -76,29 +75,6 @@
subsystem_spec(Options) ->
{"sftp", {?MODULE, Options}}.
-%%% DEPRECATED START %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%--------------------------------------------------------------------
-%% Function: listen() -> Pid | {error,Error}
-%% Description: Starts the server
-%%--------------------------------------------------------------------
-listen(Port) ->
- listen(any, Port, []).
-listen(Port, Options) ->
- listen(any, Port, Options).
-listen(Addr, Port, Options) ->
- SubSystems = [subsystem_spec(Options)],
- ssh:daemon(Addr, Port, [{subsystems, SubSystems} |Options]).
-
-%%--------------------------------------------------------------------
-%% Function: stop(Pid) -> ok
-%% Description: Stops the listener
-%%--------------------------------------------------------------------
-stop(Pid) ->
- ssh:stop_listener(Pid).
-
-
-%%% DEPRECATED END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%====================================================================
%% subsystem callbacks
@@ -664,29 +640,25 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
do_open(ReqId, State, Path, Flags).
do_open(ReqId, State0, Path, Flags) ->
- #state{file_handler = FileMod, file_state = FS0, root = Root, xf = #ssh_xfer{vsn = Vsn}} = State0,
- XF = State0#state.xf,
- F = [binary | Flags],
- {IsDir, _FS1} = FileMod:is_dir(Path, FS0),
+ #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0,
+ AbsPath = relate_file_name(Path, State0),
+ {IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0),
case IsDir of
true when Vsn > 5 ->
ssh_xfer:xf_send_status(State0#state.xf, ReqId,
- ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory");
+ ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"),
+ State0;
true ->
ssh_xfer:xf_send_status(State0#state.xf, ReqId,
- ?SSH_FX_FAILURE, "File is a directory");
+ ?SSH_FX_FAILURE, "File is a directory"),
+ State0;
false ->
- AbsPath = case Root of
- "" ->
- Path;
- _ ->
- relate_file_name(Path, State0)
- end,
- {Res, FS1} = FileMod:open(AbsPath, F, FS0),
+ OpenFlags = [binary | Flags],
+ {Res, FS1} = FileMod:open(AbsPath, OpenFlags, FS0),
State1 = State0#state{file_state = FS1},
case Res of
{ok, IoDevice} ->
- add_handle(State1, XF, ReqId, file, {Path,IoDevice});
+ add_handle(State1, State0#state.xf, ReqId, file, {Path,IoDevice});
{error, Error} ->
ssh_xfer:xf_send_status(State1#state.xf, ReqId,
ssh_xfer:encode_erlang_status(Error)),
@@ -742,6 +714,10 @@ resolve_symlinks_2([], State, _LinkCnt, AccPath) ->
{{ok, AccPath}, State}.
+%% The File argument is always in a user visible file system, i.e.
+%% is under Root and is relative to CWD or Root, if starts with "/".
+%% The result of the function is always an absolute path in a
+%% "backend" file system.
relate_file_name(File, State) ->
relate_file_name(File, State, _Canonicalize=true).
@@ -749,19 +725,20 @@ relate_file_name(File, State, Canonicalize) when is_binary(File) ->
relate_file_name(unicode:characters_to_list(File), State, Canonicalize);
relate_file_name(File, #state{cwd = CWD, root = ""}, Canonicalize) ->
relate_filename_to_path(File, CWD, Canonicalize);
-relate_file_name(File, #state{root = Root}, Canonicalize) ->
- case is_within_root(Root, File) of
- true ->
- File;
- false ->
- RelFile = make_relative_filename(File),
- NewFile = relate_filename_to_path(RelFile, Root, Canonicalize),
- case is_within_root(Root, NewFile) of
- true ->
- NewFile;
- false ->
- Root
- end
+relate_file_name(File, #state{cwd = CWD, root = Root}, Canonicalize) ->
+ CWD1 = case is_within_root(Root, CWD) of
+ true -> CWD;
+ false -> Root
+ end,
+ AbsFile = case make_relative_filename(File) of
+ File ->
+ relate_filename_to_path(File, CWD1, Canonicalize);
+ RelFile ->
+ relate_filename_to_path(RelFile, Root, Canonicalize)
+ end,
+ case is_within_root(Root, AbsFile) of
+ true -> AbsFile;
+ false -> Root
end.
is_within_root(Root, File) ->
diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl
index 78f452df67..81f181f1fc 100644
--- a/lib/ssh/src/ssh_sftpd_file_api.erl
+++ b/lib/ssh/src/ssh_sftpd_file_api.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@
-callback list_dir(file:name(), State::term()) ->
{{ok, Filenames::term()}, State::term()} | {{error, Reason::term()}, State::term()}.
-callback make_dir(Dir::term(), State::term()) ->
- {{ok, State::term()},State::term()} | {{error, Reason::term()}, State::term()}.
+ {ok, State::term()} | {{error, Reason::term()}, State::term()}.
-callback make_symlink(Path2::term(), Path::term(), State::term()) ->
{ok, State::term()} | {{error, Reason::term()}, State::term()}.
-callback open(Path::term(), Flags::term(), State::term()) ->
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index 637f5f398f..8db051095c 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,7 +26,9 @@
-behaviour(supervisor).
--export([start_link/1,
+-include("ssh.hrl").
+
+-export([start_link/5,
connection_supervisor/1,
channel_supervisor/1
]).
@@ -37,8 +39,8 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Opts) ->
- supervisor:start_link(?MODULE, [Opts]).
+start_link(Role, Address, Port, Profile, Options) ->
+ supervisor:start_link(?MODULE, [Role, Address, Port, Profile, Options]).
connection_supervisor(SupPid) ->
Children = supervisor:which_children(SupPid),
@@ -51,49 +53,40 @@ channel_supervisor(SupPid) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([Opts]) ->
- RestartStrategy = one_for_all,
- MaxR = 0,
- MaxT = 3600,
- Children = child_specs(Opts),
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init([Role, Address, Port, Profile, Options]) ->
+ SupFlags = #{strategy => one_for_all,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs = child_specs(Role, Address, Port, Profile, Options),
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_specs(Opts) ->
- case proplists:get_value(role, Opts) of
- client ->
- [];
- server ->
- [ssh_channel_child_spec(Opts), ssh_connectinon_child_spec(Opts)]
- end.
+child_specs(client, _Address, _Port, _Profile, _Options) ->
+ [];
+child_specs(server, Address, Port, Profile, Options) ->
+ [ssh_channel_child_spec(server, Address, Port, Profile, Options),
+ ssh_connection_child_spec(server, Address, Port, Profile, Options)].
-ssh_connectinon_child_spec(Opts) ->
- Address = proplists:get_value(address, Opts),
- Port = proplists:get_value(port, Opts),
- Role = proplists:get_value(role, Opts),
- Name = id(Role, ssh_connection_sup, Address, Port),
- StartFunc = {ssh_connection_sup, start_link, [Opts]},
- Restart = temporary,
- Shutdown = 5000,
- Modules = [ssh_connection_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-ssh_channel_child_spec(Opts) ->
- Address = proplists:get_value(address, Opts),
- Port = proplists:get_value(port, Opts),
- Role = proplists:get_value(role, Opts),
- Name = id(Role, ssh_channel_sup, Address, Port),
- StartFunc = {ssh_channel_sup, start_link, [Opts]},
- Restart = temporary,
- Shutdown = infinity,
- Modules = [ssh_channel_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+ssh_connection_child_spec(Role, Address, Port, _Profile, Options) ->
+ #{id => id(Role, ssh_connection_sup, Address, Port),
+ start => {ssh_connection_sup, start_link, [Options]},
+ restart => temporary,
+ shutdown => 5000,
+ type => supervisor,
+ modules => [ssh_connection_sup]
+ }.
+
+ssh_channel_child_spec(Role, Address, Port, _Profile, Options) ->
+ #{id => id(Role, ssh_channel_sup, Address, Port),
+ start => {ssh_channel_sup, start_link, [Options]},
+ restart => temporary,
+ shutdown => infinity,
+ type => supervisor,
+ modules => [ssh_channel_sup]
+ }.
id(Role, Sup, Address, Port) ->
{Role, Sup, Address, Port}.
diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl
index 8b57387589..eaec7a54e4 100644
--- a/lib/ssh/src/ssh_sup.erl
+++ b/lib/ssh/src/ssh_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -31,63 +31,20 @@
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([]) ->
- SupFlags = {one_for_one, 10, 3600},
- Children = children(),
- {ok, {SupFlags, Children}}.
-
-%%%=========================================================================
-%%% Internal functions
-%%%=========================================================================
-get_services() ->
- case (catch application:get_env(ssh, services)) of
- {ok, Services} ->
- Services;
- _ ->
- []
- end.
-
-children() ->
- Services = get_services(),
- Clients = [Service || Service <- Services, is_client(Service)],
- Servers = [Service || Service <- Services, is_server(Service)],
-
- [server_child_spec(Servers), client_child_spec(Clients)].
-
-server_child_spec(Servers) ->
- Name = sshd_sup,
- StartFunc = {sshd_sup, start_link, [Servers]},
- Restart = permanent,
- Shutdown = infinity,
- Modules = [sshd_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-client_child_spec(Clients) ->
- Name = sshc_sup,
- StartFunc = {sshc_sup, start_link, [Clients]},
- Restart = permanent,
- Shutdown = infinity,
- Modules = [sshc_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-is_server({sftpd, _}) ->
- true;
-is_server({shelld, _}) ->
- true;
-is_server(_) ->
- false.
-
-is_client({sftpc, _}) ->
- true;
-is_client({shellc, _}) ->
- true;
-is_client(_) ->
- false.
-
-
-
+init(_) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [#{id => Module,
+ start => {Module, start_link, []},
+ restart => permanent,
+ shutdown => 4000, %brutal_kill,
+ type => supervisor,
+ modules => [Module]
+ }
+ || Module <- [sshd_sup,
+ sshc_sup]
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index e97ac7b01a..e70abf59c2 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@
%%
%%----------------------------------------------------------------------
%% Purpose: The ssh server instance supervisor, an instans of this supervisor
-%% exists for every ip-address and port combination, hangs under
+%% exists for every ip-address and port combination, hangs under
%% sshd_sup.
%%----------------------------------------------------------------------
@@ -31,64 +31,103 @@
-include("ssh.hrl").
--export([start_link/1, stop_listener/1,
+-export([start_link/4, stop_listener/1,
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/3,
- restart_acceptor/3, stop_subsystem/2]).
+ subsystem_supervisor/1, channel_supervisor/1,
+ connection_supervisor/1,
+ acceptor_supervisor/1, start_subsystem/6,
+ stop_subsystem/2]).
%% Supervisor callback
-export([init/1]).
%%%=========================================================================
-%%% Internal API
+%%% API
%%%=========================================================================
-start_link(ServerOpts) ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+start_link(Address, Port, Profile, Options) ->
Name = make_name(Address, Port, Profile),
- supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]).
+ supervisor:start_link({local, Name}, ?MODULE, [Address, Port, Profile, Options]).
-stop_listener(SysSup) ->
- stop_acceptor(SysSup).
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([Address, Port, Profile, Options]) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs =
+ case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of
+ undefined ->
+ [#{id => id(ssh_acceptor_sup, Address, Port, Profile),
+ start => {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]},
+ restart => transient,
+ shutdown => infinity,
+ type => supervisor,
+ modules => [ssh_acceptor_sup]
+ }];
+ _ ->
+ []
+ end,
+ {ok, {SupFlags,ChildSpecs}}.
+
+%%%=========================================================================
+%%% Service API
+%%%=========================================================================
+stop_listener(SystemSup) ->
+ {Name, AcceptorSup, _, _} = lookup(ssh_acceptor_sup, SystemSup),
+ case supervisor:terminate_child(AcceptorSup, Name) of
+ ok ->
+ supervisor:delete_child(AcceptorSup, Name);
+ Error ->
+ Error
+ end.
stop_listener(Address, Port, Profile) ->
- Name = make_name(Address, Port, Profile),
- stop_acceptor(whereis(Name)).
-
+ stop_listener(
+ system_supervisor(Address, Port, Profile)).
+
+
stop_system(SysSup) ->
- Name = sshd_sup:system_name(SysSup),
- spawn(fun() -> sshd_sup:stop_child(Name) end),
+ spawn(fun() -> sshd_sup:stop_child(SysSup) end),
ok.
-stop_system(Address, Port, Profile) ->
+stop_system(Address, Port, Profile) ->
spawn(fun() -> sshd_sup:stop_child(Address, Port, Profile) end),
ok.
+
system_supervisor(Address, Port, Profile) ->
Name = make_name(Address, Port, Profile),
whereis(Name).
subsystem_supervisor(SystemSup) ->
- ssh_subsystem_sup(supervisor:which_children(SystemSup)).
+ {_, Child, _, _} = lookup(ssh_subsystem_sup, SystemSup),
+ Child.
channel_supervisor(SystemSup) ->
- SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)),
- ssh_subsystem_sup:channel_supervisor(SubSysSup).
+ ssh_subsystem_sup:channel_supervisor(
+ subsystem_supervisor(SystemSup)).
connection_supervisor(SystemSup) ->
- SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)),
- ssh_subsystem_sup:connection_supervisor(SubSysSup).
+ ssh_subsystem_sup:connection_supervisor(
+ subsystem_supervisor(SystemSup)).
acceptor_supervisor(SystemSup) ->
- ssh_acceptor_sup(supervisor:which_children(SystemSup)).
+ {_, Child, _, _} = lookup(ssh_acceptor_sup, SystemSup),
+ Child.
-start_subsystem(SystemSup, Options) ->
- Spec = ssh_subsystem_child_spec(Options),
- supervisor:start_child(SystemSup, Spec).
+
+start_subsystem(SystemSup, Role, Address, Port, Profile, Options) ->
+ SubsystemSpec =
+ #{id => make_ref(),
+ start => {ssh_subsystem_sup, start_link, [Role, Address, Port, Profile, Options]},
+ restart => temporary,
+ shutdown => infinity,
+ type => supervisor,
+ modules => [ssh_subsystem_sup]},
+ supervisor:start_child(SystemSup, SubsystemSpec).
stop_subsystem(SystemSup, SubSys) ->
case catch lists:keyfind(SubSys, 2, supervisor:which_children(SystemSup)) of
@@ -106,100 +145,21 @@ stop_subsystem(SystemSup, SubSys) ->
ok
end.
-
-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);
- Error ->
- Error
- end.
-
-restart_acceptor(Address, Port, Profile) ->
- SysSupName = make_name(Address, Port, Profile),
- AcceptorName = id(ssh_acceptor_sup, Address, Port, Profile),
- supervisor:restart_child(SysSupName, AcceptorName).
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([ServerOpts]) ->
- RestartStrategy = one_for_one,
- MaxR = 0,
- MaxT = 3600,
- Children = case proplists:get_value(asocket,ServerOpts) of
- undefined -> child_specs(ServerOpts);
- _ -> []
- end,
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
-
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_specs(ServerOpts) ->
- [ssh_acceptor_child_spec(ServerOpts)].
-
-ssh_acceptor_child_spec(ServerOpts) ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- 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,
- Modules = [ssh_acceptor_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-ssh_subsystem_child_spec(ServerOpts) ->
- Name = make_ref(),
- StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]},
- Restart = temporary,
- Shutdown = infinity,
- Modules = [ssh_subsystem_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
-
-
id(Sup, Address, Port, Profile) ->
- case is_list(Address) of
- true ->
- {Sup, any, Port, Profile};
- false ->
- {Sup, Address, Port, Profile}
- end.
+ {Sup, Address, Port, Profile}.
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.
+ list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [fmt_host(Address), Port, Profile]))).
-ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) ->
- Child;
-ssh_subsystem_sup([_ | Rest]) ->
- ssh_subsystem_sup(Rest).
+fmt_host(IP) when is_tuple(IP) -> inet:ntoa(IP);
+fmt_host(A) when is_atom(A) -> A;
+fmt_host(S) when is_list(S) -> S.
-ssh_acceptor_sup([{_, Child, _, [ssh_acceptor_sup]} | _]) ->
- Child;
-ssh_acceptor_sup([_ | Rest]) ->
- ssh_acceptor_sup(Rest).
-stop_acceptor(Sup) ->
- [{Name, AcceptorSup}] =
- [{SupName, ASup} || {SupName, ASup, _, [ssh_acceptor_sup]} <-
- supervisor:which_children(Sup)],
- case supervisor:terminate_child(AcceptorSup, Name) of
- ok ->
- supervisor:delete_child(AcceptorSup, Name);
- Error ->
- Error
- end.
+lookup(SupModule, SystemSup) ->
+ lists:keyfind([SupModule], 4,
+ supervisor:which_children(SystemSup)).
+
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 7cb3b75ac0..412f5de9de 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,15 +38,18 @@
handle_hello_version/1,
key_exchange_init_msg/1,
key_init/3, new_keys_message/1,
+ ext_info_message/1,
handle_kexinit_msg/3, handle_kexdh_init/2,
handle_kex_dh_gex_group/2, handle_kex_dh_gex_init/2, handle_kex_dh_gex_reply/2,
handle_new_keys/2, handle_kex_dh_gex_request/2,
handle_kexdh_reply/2,
handle_kex_ecdh_init/2,
handle_kex_ecdh_reply/2,
+ parallell_gen_key/1,
extract_public_key/1,
ssh_packet/2, pack/2,
- sign/3, verify/4]).
+ valid_key_sha_alg/2,
+ sha/1, sign/3, verify/4]).
%%% For test suites
-export([pack/3]).
@@ -78,6 +81,10 @@ default_algorithms() -> [{K,default_algorithms(K)} || K <- algo_classes()].
algo_classes() -> [kex, public_key, cipher, mac, compression].
+default_algorithms(kex) ->
+ supported_algorithms(kex, [
+ 'diffie-hellman-group1-sha1' % Gone in OpenSSH 7.3.p1
+ ]);
default_algorithms(cipher) ->
supported_algorithms(cipher, same(['AEAD_AES_128_GCM',
@@ -85,6 +92,7 @@ default_algorithms(cipher) ->
default_algorithms(mac) ->
supported_algorithms(mac, same(['AEAD_AES_128_GCM',
'AEAD_AES_256_GCM']));
+
default_algorithms(Alg) ->
supported_algorithms(Alg, []).
@@ -94,34 +102,41 @@ supported_algorithms() -> [{K,supported_algorithms(K)} || K <- algo_classes()].
supported_algorithms(kex) ->
select_crypto_supported(
[
- {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]},
{'ecdh-sha2-nistp384', [{public_keys,ecdh}, {ec_curve,secp384r1}, {hashs,sha384}]},
- {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]},
+ {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]},
+ {'ecdh-sha2-nistp256', [{public_keys,ecdh}, {ec_curve,secp256r1}, {hashs,sha256}]},
{'diffie-hellman-group-exchange-sha256', [{public_keys,dh}, {hashs,sha256}]},
+ {'diffie-hellman-group16-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1
+ {'diffie-hellman-group18-sha512', [{public_keys,dh}, {hashs,sha512}]}, % In OpenSSH 7.3.p1
+ {'diffie-hellman-group14-sha256', [{public_keys,dh}, {hashs,sha256}]}, % In OpenSSH 7.3.p1
+ {'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]},
{'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]},
- {'ecdh-sha2-nistp521', [{public_keys,ecdh}, {ec_curve,secp521r1}, {hashs,sha512}]},
{'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]}
]);
supported_algorithms(public_key) ->
select_crypto_supported(
- [{'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]},
+ [
{'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {ec_curve,secp384r1}]},
{'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {ec_curve,secp521r1}]},
+ {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {ec_curve,secp256r1}]},
{'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
- {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]}
+ {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
+ {'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]},
+ {'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1
]);
supported_algorithms(cipher) ->
same(
select_crypto_supported(
- [{'aes256-ctr', [{ciphers,{aes_ctr,256}}]},
- {'aes192-ctr', [{ciphers,{aes_ctr,192}}]},
- {'aes128-ctr', [{ciphers,{aes_ctr,128}}]},
- {'aes128-cbc', [{ciphers,aes_cbc128}]},
+ [
+ {'[email protected]', [{ciphers,{aes_gcm,256}}]},
+ {'aes256-ctr', [{ciphers,{aes_ctr,256}}]},
+ {'aes192-ctr', [{ciphers,{aes_ctr,192}}]},
{'[email protected]', [{ciphers,{aes_gcm,128}}]},
- {'[email protected]', [{ciphers,{aes_gcm,256}}]},
- {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'aes128-ctr', [{ciphers,{aes_ctr,128}}]},
{'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]},
+ {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]},
+ {'aes128-cbc', [{ciphers,aes_cbc128}]},
{'3des-cbc', [{ciphers,des3_cbc}]}
]
));
@@ -143,14 +158,14 @@ supported_algorithms(compression) ->
%%%----------------------------------------------------------------------------
versions(client, Options)->
- Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION),
+ Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_CLIENT_VERSION),
{Vsn, format_version(Vsn, software_version(Options))};
versions(server, Options) ->
- Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION),
+ Vsn = ?GET_INTERNAL_OPT(vsn, Options, ?DEFAULT_SERVER_VERSION),
{Vsn, format_version(Vsn, software_version(Options))}.
software_version(Options) ->
- case proplists:get_value(id_string, Options) of
+ case ?GET_OPT(id_string, Options) of
undefined ->
"Erlang"++ssh_vsn();
{random,Nlo,Nup} ->
@@ -161,7 +176,7 @@ software_version(Options) ->
ssh_vsn() ->
try {ok,L} = application:get_all_key(ssh),
- proplists:get_value(vsn,L,"")
+ proplists:get_value(vsn, L, "")
of
"" -> "";
VSN when is_list(VSN) -> "/" ++ VSN;
@@ -171,7 +186,7 @@ ssh_vsn() ->
end.
random_id(Nlo, Nup) ->
- [crypto:rand_uniform($a,$z+1) || _<- lists:duplicate(crypto:rand_uniform(Nlo,Nup+1),x) ].
+ [$a + rand:uniform($z-$a+1) - 1 || _<- lists:duplicate(Nlo + rand:uniform(Nup-Nlo+1) - 1, x)].
hello_version_msg(Data) ->
[Data,"\r\n"].
@@ -190,9 +205,6 @@ is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
recv_mac_key = Key, recv_sequence = SeqNum}) ->
Mac == mac(Algorithm, Key, SeqNum, Data).
-yes_no(Ssh, Prompt) ->
- (Ssh#ssh.io_cb):yes_no(Prompt, Ssh).
-
format_version({Major,Minor}, SoftwareVersion) ->
"SSH-" ++ integer_to_list(Major) ++ "." ++
integer_to_list(Minor) ++ "-" ++ SoftwareVersion.
@@ -222,14 +234,8 @@ key_exchange_init_msg(Ssh0) ->
kex_init(#ssh{role = Role, opts = Opts, available_host_keys = HostKeyAlgs}) ->
Random = ssh_bits:random(16),
- PrefAlgs =
- case proplists:get_value(preferred_algorithms,Opts) of
- undefined ->
- default_algorithms();
- Algs0 ->
- Algs0
- end,
- kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs).
+ PrefAlgs = ?GET_OPT(preferred_algorithms, Opts),
+ kexinit_message(Role, Random, PrefAlgs, HostKeyAlgs, Opts).
key_init(client, Ssh, Value) ->
Ssh#ssh{c_keyinit = Value};
@@ -237,10 +243,11 @@ key_init(server, Ssh, Value) ->
Ssh#ssh{s_keyinit = Value}.
-kexinit_message(_Role, Random, Algs, HostKeyAlgs) ->
+kexinit_message(Role, Random, Algs, HostKeyAlgs, Opts) ->
#ssh_msg_kexinit{
cookie = Random,
- kex_algorithms = to_strings( get_algs(kex,Algs) ),
+ kex_algorithms = to_strings( get_algs(kex,Algs) )
+ ++ kex_ext_info(Role,Opts),
server_host_key_algorithms = HostKeyAlgs,
encryption_algorithms_client_to_server = c2s(cipher,Algs),
encryption_algorithms_server_to_client = s2c(cipher,Algs),
@@ -262,71 +269,92 @@ 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),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ Ssh = install_alg(snd, Ssh1),
{ok, SshPacket, Ssh}.
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = client} = Ssh0) ->
- {ok, Algoritms} = select_algorithm(client, Own, CounterPart),
- case verify_algorithm(Algoritms) of
- true ->
- key_exchange_first_msg(Algoritms#alg.kex,
- Ssh0#ssh{algorithms = Algoritms});
- _ ->
- %% TODO: Correct code?
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed"
- })
+ #ssh{role = client} = Ssh) ->
+ try
+ {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts),
+ true = verify_algorithm(Algorithms),
+ Algorithms
+ of
+ Algos ->
+ key_exchange_first_msg(Algos#alg.kex,
+ Ssh#ssh{algorithms = Algos})
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Selection of key exchange algorithm failed"})
end;
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = server} = Ssh) ->
- {ok, Algoritms} = select_algorithm(server, CounterPart, Own),
- case verify_algorithm(Algoritms) of
- true ->
- {ok, Ssh#ssh{algorithms = Algoritms}};
- _ ->
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- description = "Selection of key exchange algorithm failed"
- })
+ #ssh{role = server} = Ssh) ->
+ try
+ {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts),
+ true = verify_algorithm(Algorithms),
+ Algorithms
+ of
+ Algos ->
+ {ok, Ssh#ssh{algorithms = Algos}}
+ catch
+ _:_ ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Selection of key exchange algorithm failed"})
end.
-%% TODO: diffie-hellman-group14-sha1 should also be supported.
-%% Maybe check more things ...
-
-verify_algorithm(#alg{kex = undefined}) -> false;
-verify_algorithm(#alg{hkey = undefined}) -> false;
-verify_algorithm(#alg{send_mac = undefined}) -> false;
-verify_algorithm(#alg{recv_mac = undefined}) -> false;
-verify_algorithm(#alg{encrypt = undefined}) -> false;
-verify_algorithm(#alg{decrypt = undefined}) -> false;
-verify_algorithm(#alg{compress = undefined}) -> false;
-verify_algorithm(#alg{decompress = undefined}) -> false;
-verify_algorithm(#alg{kex = Kex}) -> lists:member(Kex, supported_algorithms(kex)).
+verify_algorithm(#alg{kex = undefined}) -> {false, "kex"};
+verify_algorithm(#alg{hkey = undefined}) -> {false, "hkey"};
+verify_algorithm(#alg{send_mac = undefined}) -> {false, "send_mac"};
+verify_algorithm(#alg{recv_mac = undefined}) -> {false, "recv_mac"};
+verify_algorithm(#alg{encrypt = undefined}) -> {false, "encrypt"};
+verify_algorithm(#alg{decrypt = undefined}) -> {false, "decrypt"};
+verify_algorithm(#alg{compress = undefined}) -> {false, "compress"};
+verify_algorithm(#alg{decompress = undefined}) -> {false, "decompress"};
+verify_algorithm(#alg{kex = Kex}) ->
+ %% This also catches the error if 'ext-info-s' or 'ext-info-c' is selected.
+ %% (draft-ietf-curdle-ssh-ext-info-04 2.2)
+ case lists:member(Kex, supported_algorithms(kex)) of
+ true -> true;
+ false -> {false, "kex"}
+ end.
%%%----------------------------------------------------------------
%%%
%%% Key exchange initialization
%%%
key_exchange_first_msg(Kex, Ssh0) when Kex == 'diffie-hellman-group1-sha1' ;
- Kex == 'diffie-hellman-group14-sha1' ->
+ Kex == 'diffie-hellman-group14-sha1' ;
+ Kex == 'diffie-hellman-group14-sha256' ;
+ Kex == 'diffie-hellman-group16-sha512' ;
+ Kex == 'diffie-hellman-group18-sha512'
+ ->
{G, P} = dh_group(Kex),
- {Public, Private} = generate_key(dh, [P,G]),
+ Sz = dh_bits(Ssh0#ssh.algorithms),
+ {Public, Private} = generate_key(dh, [P,G,2*Sz]),
{SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0),
{ok, SshPacket,
Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}};
key_exchange_first_msg(Kex, Ssh0=#ssh{opts=Opts}) when Kex == 'diffie-hellman-group-exchange-sha1' ;
Kex == 'diffie-hellman-group-exchange-sha256' ->
- {Min,NBits,Max} =
- proplists:get_value(dh_gex_limits, Opts, {?DEFAULT_DH_GROUP_MIN,
- ?DEFAULT_DH_GROUP_NBITS,
- ?DEFAULT_DH_GROUP_MAX}),
+ {Min,NBits0,Max} = ?GET_OPT(dh_gex_limits, Opts),
+ DhBits = dh_bits(Ssh0#ssh.algorithms),
+ NBits1 =
+ %% NIST Special Publication 800-57 Part 1 Revision 4: Recommendation for Key Management
+ if
+ DhBits =< 112 -> 2048;
+ DhBits =< 128 -> 3072;
+ DhBits =< 192 -> 7680;
+ true -> 8192
+ end,
+ NBits = min(max(max(NBits0,NBits1),Min), Max),
+
{SshPacket, Ssh1} =
ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min,
n = NBits,
@@ -348,26 +376,31 @@ key_exchange_first_msg(Kex, Ssh0) when Kex == 'ecdh-sha2-nistp256' ;
%%%
%%% diffie-hellman-group1-sha1
%%% diffie-hellman-group14-sha1
+%%% diffie-hellman-group14-sha256
+%%% diffie-hellman-group16-sha512
+%%% diffie-hellman-group18-sha512
%%%
handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
- Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex,
+ hkey=SignAlg} = Algs}) ->
%% server
{G, P} = dh_group(Kex),
if
1=<E, E=<(P-1) ->
- {Public, Private} = generate_key(dh, [P,G]),
+ Sz = dh_bits(Algs),
+ {Public, Private} = generate_key(dh, [P,G,2*Sz]),
K = compute_key(dh, E, Private, [P,G]),
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, MyPubHostKey, E, Public, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {E,Public,K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kexdh_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kexdh_reply{public_host_key = {MyPubHostKey,SignAlg},
f = Public,
h_sig = H_SIG
}, Ssh0),
{ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
- shared_secret = K,
+ shared_secret = ssh_bits:mpint(K),
exchanged_hash = H,
session_id = sid(Ssh1, H)}};
@@ -383,19 +416,20 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e = E},
handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey,
f = F,
h_sig = H_SIG},
- #ssh{keyex_key = {{Private, Public}, {G, P}}} = Ssh0) ->
+ #ssh{keyex_key = {{Private, Public}, {G, P}},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} = Ssh0) ->
%% client
if
1=<F, F=<(P-1)->
K = compute_key(dh, F, Private, [P,G]),
- H = kex_h(Ssh0, PeerPubHostKey, Public, F, K),
-
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Public,F,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = K,
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -425,14 +459,13 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = Min0,
%% server
{Min, Max} = adjust_gex_min_max(Min0, Max0, Opts),
case public_key:dh_gex_group(Min, NBits, Max,
- proplists:get_value(dh_gex_groups,Opts)) of
- {ok, {_Sz, {G,P}}} ->
- {Public, Private} = generate_key(dh, [P,G]),
+ ?GET_OPT(dh_gex_groups,Opts)) of
+ {ok, {_, {G,P}}} ->
{SshPacket, Ssh} =
ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
{ok, SshPacket,
- Ssh#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}
+ Ssh#ssh{keyex_key = {x, {G, P}},
+ keyex_info = {Min0, Max0, NBits}
}};
{error,_} ->
ssh_connection_handler:disconnect(
@@ -449,7 +482,7 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits},
%% This message was in the draft-00 of rfc4419
%% (https://tools.ietf.org/html/draft-ietf-secsh-dh-group-exchange-00)
%% In later drafts and the rfc is "is used for backward compatibility".
- %% Unfortunatly the rfc does not specify how to treat the parameter n
+ %% Unfortunately the rfc does not specify how to treat the parameter n
%% if there is no group of that modulus length :(
%% The draft-00 however specifies that n is the "... number of bits
%% the subgroup should have at least".
@@ -460,14 +493,13 @@ handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request_old{n = NBits},
Max0 = 8192,
{Min, Max} = adjust_gex_min_max(Min0, Max0, Opts),
case public_key:dh_gex_group(Min, NBits, Max,
- proplists:get_value(dh_gex_groups,Opts)) of
- {ok, {_Sz, {G,P}}} ->
- {Public, Private} = generate_key(dh, [P,G]),
+ ?GET_OPT(dh_gex_groups,Opts)) of
+ {ok, {_, {G,P}}} ->
{SshPacket, Ssh} =
ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
{ok, SshPacket,
- Ssh#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {-1, -1, NBits} % flag for kex_h hash calc
+ Ssh#ssh{keyex_key = {x, {G, P}},
+ keyex_info = {-1, -1, NBits} % flag for kex_hash calc
}};
{error,_} ->
ssh_connection_handler:disconnect(
@@ -486,28 +518,25 @@ handle_kex_dh_gex_request(_, _) ->
adjust_gex_min_max(Min0, Max0, Opts) ->
- case proplists:get_value(dh_gex_limits, Opts) of
- undefined ->
- {Min0, Max0};
- {Min1, Max1} ->
- Min2 = max(Min0, Min1),
- Max2 = min(Max0, Max1),
- if
- Min2 =< Max2 ->
- {Min2, Max2};
- Max2 < Min2 ->
- ssh_connection_handler:disconnect(
- #ssh_msg_disconnect{
- code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
- description = "No possible diffie-hellman-group-exchange group possible"
- })
- end
+ {Min1, Max1} = ?GET_OPT(dh_gex_limits, Opts),
+ Min2 = max(Min0, Min1),
+ Max2 = min(Max0, Max1),
+ if
+ Min2 =< Max2 ->
+ {Min2, Max2};
+ Max2 < Min2 ->
+ ssh_connection_handler:disconnect(
+ #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "No possible diffie-hellman-group-exchange group possible"
+ })
end.
handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
%% client
- {Public, Private} = generate_key(dh, [P,G]),
+ Sz = dh_bits(Ssh0#ssh.algorithms),
+ {Public, Private} = generate_key(dh, [P,G,2*Sz]),
{SshPacket, Ssh1} =
ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0), % Pub = G^Priv mod P (def)
@@ -516,23 +545,24 @@ handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
handle_kex_dh_gex_init(#ssh_msg_kex_dh_gex_init{e = E},
#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}} =
- Ssh0) ->
+ keyex_info = {Min, Max, NBits},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} = Ssh0) ->
%% server
if
1=<E, E=<(P-1) ->
K = compute_key(dh, E, Private, [P,G]),
if
1<K, K<(P-1) ->
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, MyPubHostKey, Min, NBits, Max, P, G, E, Public, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,E,Public,K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh} =
- ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kex_dh_gex_reply{public_host_key = {MyPubHostKey,SignAlg},
f = Public,
h_sig = H_SIG}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = K,
+ {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
exchanged_hash = H,
session_id = sid(Ssh, H)
}};
@@ -555,7 +585,9 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
f = F,
h_sig = H_SIG},
#ssh{keyex_key = {{Private, Public}, {G, P}},
- keyex_info = {Min, Max, NBits}} =
+ keyex_info = {Min, Max, NBits},
+ algorithms = #alg{kex=Kex,
+ hkey=SignAlg}} =
Ssh0) ->
%% client
if
@@ -563,14 +595,13 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
K = compute_key(dh, F, Private, [P,G]),
if
1<K, K<(P-1) ->
- H = kex_h(Ssh0, PeerPubHostKey, Min, NBits, Max, P, G, Public, F, K),
-
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Kex), {Min,NBits,Max,P,G,Public,F,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = K,
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
_Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -600,7 +631,8 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostK
%%% diffie-hellman-ecdh-sha2-*
%%%
handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
- Ssh0 = #ssh{algorithms = #alg{kex=Kex}}) ->
+ Ssh0 = #ssh{algorithms = #alg{kex=Kex,
+ hkey=SignAlg}}) ->
%% at server
Curve = ecdh_curve(Kex),
{MyPublic, MyPrivate} = generate_key(ecdh, Curve),
@@ -608,17 +640,17 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
compute_key(ecdh, PeerPublic, MyPrivate, Curve)
of
K ->
- MyPrivHostKey = get_host_key(Ssh0),
+ MyPrivHostKey = get_host_key(Ssh0, SignAlg),
MyPubHostKey = extract_public_key(MyPrivHostKey),
- H = kex_h(Ssh0, Curve, MyPubHostKey, PeerPublic, MyPublic, K),
- H_SIG = sign_host_key(Ssh0, MyPrivHostKey, H),
+ H = kex_hash(Ssh0, MyPubHostKey, SignAlg, sha(Curve), {PeerPublic, MyPublic, K}),
+ H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
{SshPacket, Ssh1} =
- ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = MyPubHostKey,
+ ssh_packet(#ssh_msg_kex_ecdh_reply{public_host_key = {MyPubHostKey,SignAlg},
q_s = MyPublic,
h_sig = H_SIG},
Ssh0),
{ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve},
- shared_secret = K,
+ shared_secret = ssh_bits:mpint(K),
exchanged_hash = H,
session_id = sid(Ssh1, H)}}
catch
@@ -633,20 +665,21 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_init{q_c = PeerPublic},
handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
q_s = PeerPublic,
h_sig = H_SIG},
- #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve}} = Ssh0
+ #ssh{keyex_key = {{MyPublic,MyPrivate}, Curve},
+ algorithms = #alg{hkey=SignAlg}} = Ssh0
) ->
%% at client
try
compute_key(ecdh, PeerPublic, MyPrivate, Curve)
of
K ->
- H = kex_h(Ssh0, Curve, PeerPubHostKey, MyPublic, PeerPublic, K),
+ H = kex_hash(Ssh0, PeerPubHostKey, SignAlg, sha(Curve), {MyPublic,PeerPublic,K}),
case verify_host_key(Ssh0, PeerPubHostKey, H, H_SIG) of
ok ->
{SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = K,
- exchanged_hash = H,
- session_id = sid(Ssh, H)}};
+ {ok, SshPacket, install_alg(snd, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)})};
Error ->
ssh_connection_handler:disconnect(
#ssh_msg_disconnect{
@@ -666,7 +699,7 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
%%%----------------------------------------------------------------
handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
- try install_alg(Ssh0) of
+ try install_alg(rcv, Ssh0) of
#ssh{} = Ssh ->
{ok, Ssh}
catch
@@ -677,34 +710,63 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
})
end.
+
+%%%----------------------------------------------------------------
+kex_ext_info(Role, Opts) ->
+ case ?GET_OPT(recv_ext_info,Opts) of
+ true when Role==client -> ["ext-info-c"];
+ true when Role==server -> ["ext-info-s"];
+ false -> []
+ end.
+
+ext_info_message(#ssh{role=client,
+ send_ext_info=true,
+ opts=Opts} = Ssh0) ->
+ %% Since no extension sent by the client is implemented, we add a fake one
+ %% to be able to test the framework.
+ %% Remove this when there is one and update ssh_protocol_SUITE whare it is used.
+ case proplists:get_value(ext_info_client, ?GET_OPT(tstflg,Opts)) of
+ true ->
+ Msg = #ssh_msg_ext_info{nr_extensions = 1,
+ data = [{"[email protected]", "Testing,PleaseIgnore"}]
+ },
+ {SshPacket, Ssh} = ssh_packet(Msg, Ssh0),
+ {ok, SshPacket, Ssh};
+ _ ->
+ {ok, "", Ssh0}
+ end;
+
+ext_info_message(#ssh{role=server,
+ send_ext_info=true,
+ opts = Opts} = Ssh0) ->
+ AlgsList = lists:map(fun erlang:atom_to_list/1,
+ proplists:get_value(public_key,
+ ?GET_OPT(preferred_algorithms, Opts))),
+ Msg = #ssh_msg_ext_info{nr_extensions = 1,
+ data = [{"server-sig-algs", string:join(AlgsList,",")}]
+ },
+ {SshPacket, Ssh} = ssh_packet(Msg, Ssh0),
+ {ok, SshPacket, Ssh};
+
+ext_info_message(Ssh0) ->
+ {ok, "", Ssh0}. % "" means: 'do not send'
+
+%%%----------------------------------------------------------------
%% select session id
-sid(#ssh{session_id = undefined}, H) ->
- H;
-sid(#ssh{session_id = Id}, _) ->
- Id.
+sid(#ssh{session_id = undefined}, H) -> H;
+sid(#ssh{session_id = Id}, _) -> Id.
%%
%% The host key should be read from storage
%%
-get_host_key(SSH) ->
- #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH,
-
- case Mod:host_key(ALG#alg.hkey, Opts) of
- {ok, #'RSAPrivateKey'{} = Key} -> Key;
- {ok, #'DSAPrivateKey'{} = Key} -> Key;
- {ok, #'ECPrivateKey'{} = Key} -> Key;
- Result ->
- exit({error, {Result, unsupported_key_type}})
+get_host_key(SSH, SignAlg) ->
+ #ssh{key_cb = {KeyCb,KeyCbOpts}, opts = Opts} = SSH,
+ UserOpts = ?GET_OPT(user_options, Opts),
+ case KeyCb:host_key(SignAlg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
+ {ok, PrivHostKey} -> PrivHostKey;
+ Result -> exit({error, {Result, unsupported_key_type}})
end.
-sign_host_key(_Ssh, PrivateKey, H) ->
- sign(H, sign_host_key_sha(PrivateKey), PrivateKey).
-
-sign_host_key_sha(#'ECPrivateKey'{parameters = {namedCurve,OID}}) -> sha(OID);
-sign_host_key_sha(#'RSAPrivateKey'{}) -> sha;
-sign_host_key_sha(#'DSAPrivateKey'{}) -> sha.
-
-
extract_public_key(#'RSAPrivateKey'{modulus = N, publicExponent = E}) ->
#'RSAPublicKey'{modulus = N, publicExponent = E};
extract_public_key(#'DSAPrivateKey'{y = Y, p = P, q = Q, g = G}) ->
@@ -714,45 +776,71 @@ extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
{#'ECPoint'{point=Q}, {namedCurve,OID}}.
-verify_host_key(SSH, PublicKey, Digest, Signature) ->
- case verify(Digest, host_key_sha(PublicKey), Signature, PublicKey) of
- false ->
- {error, bad_signature};
- true ->
- known_host_key(SSH, PublicKey, public_algo(PublicKey))
+verify_host_key(#ssh{algorithms=Alg}=SSH, PublicKey, Digest, {AlgStr,Signature}) ->
+ case atom_to_list(Alg#alg.hkey) of
+ AlgStr ->
+ case verify(Digest, sha(Alg#alg.hkey), Signature, PublicKey) of
+ false ->
+ {error, bad_signature};
+ true ->
+ known_host_key(SSH, PublicKey, public_algo(PublicKey))
+ end;
+ _ ->
+ {error, bad_signature_name}
end.
-host_key_sha(#'RSAPublicKey'{}) -> sha;
-host_key_sha({_, #'Dss-Parms'{}}) -> sha;
-host_key_sha({#'ECPoint'{},{namedCurve,OID}}) -> sha(OID).
+accepted_host(Ssh, PeerName, Public, Opts) ->
+ case ?GET_OPT(silently_accept_hosts, Opts) of
+
+ %% Original option values; User question and no host key fingerprints known.
+ %% Keep the original question unchanged:
+ false -> yes == yes_no(Ssh, "New host " ++ PeerName ++ " accept");
+ true -> true;
+
+ %% Variant: User question but with host key fingerprint in the question:
+ {false,Alg} ->
+ HostKeyAlg = (Ssh#ssh.algorithms)#alg.hkey,
+ Prompt = io_lib:format("The authenticity of the host can't be established.~n"
+ "~s host key fingerprint is ~s.~n"
+ "New host ~p accept",
+ [fmt_hostkey(HostKeyAlg),
+ public_key:ssh_hostkey_fingerprint(Alg,Public),
+ PeerName]),
+ yes == yes_no(Ssh, Prompt);
+
+ %% Call-back alternatives: A user provided fun is called for the decision:
+ F when is_function(F,2) ->
+ true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public)));
+
+ {DigestAlg,F} when is_function(F,2) ->
+ true == (catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)))
+
+ end.
-public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa';
-public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
-public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
- Curve = public_key:oid2ssh_curvename(OID),
- list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+yes_no(Ssh, Prompt) ->
+ (Ssh#ssh.io_cb):yes_no(Prompt, Ssh#ssh.opts).
-accepted_host(Ssh, PeerName, Opts) ->
- case proplists:get_value(silently_accept_hosts, Opts, false) of
- true ->
- yes;
- false ->
- yes_no(Ssh, "New host " ++ PeerName ++ " accept")
- end.
-known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
+fmt_hostkey('ssh-rsa') -> "RSA";
+fmt_hostkey('ssh-dss') -> "DSA";
+fmt_hostkey(A) when is_atom(A) -> fmt_hostkey(atom_to_list(A));
+fmt_hostkey("ecdsa"++_) -> "ECDSA";
+fmt_hostkey(X) -> X.
+
+
+known_host_key(#ssh{opts = Opts, key_cb = {KeyCb,KeyCbOpts}, peer = {PeerName,_}} = Ssh,
Public, Alg) ->
- PeerName = peer_name(Peer),
- case Mod:is_host_key(Public, PeerName, Alg, Opts) of
+ UserOpts = ?GET_OPT(user_options, Opts),
+ case KeyCb:is_host_key(Public, PeerName, Alg, [{key_cb_private,KeyCbOpts}|UserOpts]) of
true ->
ok;
false ->
- case accepted_host(Ssh, PeerName, Opts) of
- yes ->
- Mod:add_host_key(PeerName, Public, Opts);
- no ->
+ case accepted_host(Ssh, PeerName, Public, Opts) of
+ true ->
+ KeyCb:add_host_key(PeerName, Public, [{key_cb_private,KeyCbOpts}|UserOpts]);
+ false ->
{error, rejected}
end
end.
@@ -764,7 +852,7 @@ known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
%%
%% The first algorithm in each list MUST be the preferred (guessed)
%% algorithm. Each string MUST contain at least one algorithm name.
-select_algorithm(Role, Client, Server) ->
+select_algorithm(Role, Client, Server, Opts) ->
{Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
{SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
@@ -789,17 +877,34 @@ select_algorithm(Role, Client, Server) ->
Kex = select(Client#ssh_msg_kexinit.kex_algorithms,
Server#ssh_msg_kexinit.kex_algorithms),
- Alg = #alg{kex = Kex,
- hkey = HK,
- encrypt = Encrypt,
- decrypt = Decrypt,
- send_mac = SendMac,
- recv_mac = RecvMac,
- compress = Compression,
- decompress = Decompression,
- c_lng = C_Lng,
- s_lng = S_Lng},
- {ok, Alg}.
+ SendExtInfo =
+ %% To send we must have that option enabled and ...
+ ?GET_OPT(send_ext_info,Opts) andalso
+ %% ... the peer must have told us to send:
+ case Role of
+ server -> lists:member("ext-info-c", Client#ssh_msg_kexinit.kex_algorithms);
+ client -> lists:member("ext-info-s", Server#ssh_msg_kexinit.kex_algorithms)
+ end,
+
+ RecvExtInfo =
+ %% The peer should not send unless told so by us (which is
+ %% guided by an option).
+ %% (However a malicious peer could send anyway, so we must be prepared)
+ ?GET_OPT(recv_ext_info,Opts),
+
+ {ok, #alg{kex = Kex,
+ hkey = HK,
+ encrypt = Encrypt,
+ decrypt = Decrypt,
+ send_mac = SendMac,
+ recv_mac = RecvMac,
+ compress = Compression,
+ decompress = Decompression,
+ c_lng = C_Lng,
+ s_lng = S_Lng,
+ send_ext_info = SendExtInfo,
+ recv_ext_info = RecvExtInfo
+ }}.
%%% It is an agreed problem with RFC 5674 that if the selection is
@@ -880,45 +985,66 @@ select_compression_decompression(server, Client, Server) ->
Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
{Compression, Decompression}.
-install_alg(SSH) ->
- SSH1 = alg_final(SSH),
- SSH2 = alg_setup(SSH1),
- alg_init(SSH2).
+%% DIr = rcv | snd
+install_alg(Dir, SSH) ->
+ SSH1 = alg_final(Dir, SSH),
+ SSH2 = alg_setup(Dir, SSH1),
+ alg_init(Dir, SSH2).
-alg_setup(SSH) ->
+alg_setup(snd, SSH) ->
ALG = SSH#ssh.algorithms,
SSH#ssh{kex = ALG#alg.kex,
hkey = ALG#alg.hkey,
encrypt = ALG#alg.encrypt,
- decrypt = ALG#alg.decrypt,
send_mac = ALG#alg.send_mac,
send_mac_size = mac_digest_size(ALG#alg.send_mac),
+ compress = ALG#alg.compress,
+ c_lng = ALG#alg.c_lng,
+ s_lng = ALG#alg.s_lng,
+ send_ext_info = ALG#alg.send_ext_info,
+ recv_ext_info = ALG#alg.recv_ext_info
+ };
+
+alg_setup(rcv, SSH) ->
+ ALG = SSH#ssh.algorithms,
+ SSH#ssh{kex = ALG#alg.kex,
+ hkey = ALG#alg.hkey,
+ decrypt = ALG#alg.decrypt,
recv_mac = ALG#alg.recv_mac,
recv_mac_size = mac_digest_size(ALG#alg.recv_mac),
- compress = ALG#alg.compress,
decompress = ALG#alg.decompress,
c_lng = ALG#alg.c_lng,
s_lng = ALG#alg.s_lng,
- algorithms = undefined
+ send_ext_info = ALG#alg.send_ext_info,
+ recv_ext_info = ALG#alg.recv_ext_info
}.
-alg_init(SSH0) ->
+
+alg_init(snd, SSH0) ->
{ok,SSH1} = send_mac_init(SSH0),
- {ok,SSH2} = recv_mac_init(SSH1),
- {ok,SSH3} = encrypt_init(SSH2),
- {ok,SSH4} = decrypt_init(SSH3),
- {ok,SSH5} = compress_init(SSH4),
- {ok,SSH6} = decompress_init(SSH5),
- SSH6.
-
-alg_final(SSH0) ->
+ {ok,SSH2} = encrypt_init(SSH1),
+ {ok,SSH3} = compress_init(SSH2),
+ SSH3;
+
+alg_init(rcv, SSH0) ->
+ {ok,SSH1} = recv_mac_init(SSH0),
+ {ok,SSH2} = decrypt_init(SSH1),
+ {ok,SSH3} = decompress_init(SSH2),
+ SSH3.
+
+
+alg_final(snd, SSH0) ->
{ok,SSH1} = send_mac_final(SSH0),
- {ok,SSH2} = recv_mac_final(SSH1),
- {ok,SSH3} = encrypt_final(SSH2),
- {ok,SSH4} = decrypt_final(SSH3),
- {ok,SSH5} = compress_final(SSH4),
- {ok,SSH6} = decompress_final(SSH5),
- SSH6.
+ {ok,SSH2} = encrypt_final(SSH1),
+ {ok,SSH3} = compress_final(SSH2),
+ SSH3;
+
+alg_final(rcv, SSH0) ->
+ {ok,SSH1} = recv_mac_final(SSH0),
+ {ok,SSH2} = decrypt_final(SSH1),
+ {ok,SSH3} = decompress_final(SSH2),
+ SSH3.
+
select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS ->
A = CL -- SL, %% algortihms only used by client
@@ -993,7 +1119,7 @@ padding_length(Size, #ssh{encrypt_block_size = BlockSize,
end,
PadBlockSize = max(BlockSize,4),
MaxExtraBlocks = (max(RandomLengthPadding,MinPaddingLen) - MinPaddingLen) div PadBlockSize,
- ExtraPaddingLen = try crypto:rand_uniform(0,MaxExtraBlocks)*PadBlockSize
+ ExtraPaddingLen = try (rand:uniform(MaxExtraBlocks+1) - 1) * PadBlockSize
catch _:_ -> 0
end,
MinPaddingLen + ExtraPaddingLen.
@@ -1082,29 +1208,37 @@ payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) ->
<<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding,
Payload.
-sign(SigData, Hash, #'DSAPrivateKey'{} = Key) ->
- DerSignature = public_key:sign(SigData, Hash, Key),
+sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) ->
+ DerSignature = public_key:sign(SigData, HashAlg, Key),
#'Dss-Sig-Value'{r = R, s = S} = public_key:der_decode('Dss-Sig-Value', DerSignature),
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>;
-sign(SigData, Hash, Key = #'ECPrivateKey'{}) ->
- DerEncodedSign = public_key:sign(SigData, Hash, Key),
+sign(SigData, HashAlg, Key = #'ECPrivateKey'{}) ->
+ DerEncodedSign = public_key:sign(SigData, HashAlg, Key),
#'ECDSA-Sig-Value'{r=R, s=S} = public_key:der_decode('ECDSA-Sig-Value', DerEncodedSign),
<<?Empint(R),?Empint(S)>>;
-sign(SigData, Hash, Key) ->
- public_key:sign(SigData, Hash, Key).
-
-verify(PlainText, Hash, Sig, {_, #'Dss-Parms'{}} = Key) ->
- <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> = Sig,
- Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}),
- public_key:verify(PlainText, Hash, Signature, Key);
-verify(PlainText, Hash, Sig, {#'ECPoint'{},_} = Key) ->
- <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
- ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> = Sig,
- Sval = #'ECDSA-Sig-Value'{r=R, s=S},
- DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval),
- public_key:verify(PlainText, Hash, DerEncodedSig, Key);
-verify(PlainText, Hash, Sig, Key) ->
- public_key:verify(PlainText, Hash, Sig, Key).
+sign(SigData, HashAlg, Key) ->
+ public_key:sign(SigData, HashAlg, Key).
+
+verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key) ->
+ case Sig of
+ <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>> ->
+ Signature = public_key:der_encode('Dss-Sig-Value', #'Dss-Sig-Value'{r = R, s = S}),
+ public_key:verify(PlainText, HashAlg, Signature, Key);
+ _ ->
+ false
+ end;
+verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key) ->
+ case Sig of
+ <<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
+ ?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> ->
+ Sval = #'ECDSA-Sig-Value'{r=R, s=S},
+ DerEncodedSig = public_key:der_encode('ECDSA-Sig-Value',Sval),
+ public_key:verify(PlainText, HashAlg, DerEncodedSig, Key);
+ _ ->
+ false
+ end;
+verify(PlainText, HashAlg, Sig, Key) ->
+ public_key:verify(PlainText, HashAlg, Sig, Key).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1113,6 +1247,51 @@ verify(PlainText, Hash, Sig, Key) ->
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Unit: bytes
+
+-record(cipher_data, {
+ key_bytes,
+ iv_bytes,
+ block_bytes
+ }).
+
+%%% Start of a more parameterized crypto handling.
+cipher('AEAD_AES_128_GCM') ->
+ #cipher_data{key_bytes = 16,
+ iv_bytes = 12,
+ block_bytes = 16};
+
+cipher('AEAD_AES_256_GCM') ->
+ #cipher_data{key_bytes = 32,
+ iv_bytes = 12,
+ block_bytes = 16};
+
+cipher('3des-cbc') ->
+ #cipher_data{key_bytes = 24,
+ iv_bytes = 8,
+ block_bytes = 8};
+
+cipher('aes128-cbc') ->
+ #cipher_data{key_bytes = 16,
+ iv_bytes = 16,
+ block_bytes = 16};
+
+cipher('aes128-ctr') ->
+ #cipher_data{key_bytes = 16,
+ iv_bytes = 16,
+ block_bytes = 16};
+
+cipher('aes192-ctr') ->
+ #cipher_data{key_bytes = 24,
+ iv_bytes = 16,
+ block_bytes = 16};
+
+cipher('aes256-ctr') ->
+ #cipher_data{key_bytes = 32,
+ iv_bytes = 16,
+ block_bytes = 16}.
+
+
encrypt_init(#ssh{encrypt = none} = Ssh) ->
{ok, Ssh};
encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) ->
@@ -1493,11 +1672,11 @@ send_mac_init(SSH) ->
common ->
case SSH#ssh.role of
client ->
- KeySize = mac_key_size(SSH#ssh.send_mac),
+ KeySize = 8*mac_key_bytes(SSH#ssh.send_mac),
Key = hash(SSH, "E", KeySize),
{ok, SSH#ssh { send_mac_key = Key }};
server ->
- KeySize = mac_key_size(SSH#ssh.send_mac),
+ KeySize = 8*mac_key_bytes(SSH#ssh.send_mac),
Key = hash(SSH, "F", KeySize),
{ok, SSH#ssh { send_mac_key = Key }}
end;
@@ -1516,10 +1695,10 @@ recv_mac_init(SSH) ->
common ->
case SSH#ssh.role of
client ->
- Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
+ Key = hash(SSH, "F", 8*mac_key_bytes(SSH#ssh.recv_mac)),
{ok, SSH#ssh { recv_mac_key = Key }};
server ->
- Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
+ Key = hash(SSH, "E", 8*mac_key_bytes(SSH#ssh.recv_mac)),
{ok, SSH#ssh { recv_mac_key = Key }}
end;
aead ->
@@ -1545,99 +1724,116 @@ mac('hmac-sha2-256', Key, SeqNum, Data) ->
mac('hmac-sha2-512', Key, SeqNum, Data) ->
crypto:hmac(sha512, Key, [<<?UINT32(SeqNum)>>, Data]).
-%% return N hash bytes (HASH)
-hash(SSH, Char, Bits) ->
- HASH =
- case SSH#ssh.kex of
- 'diffie-hellman-group1-sha1' ->
- fun(Data) -> crypto:hash(sha, Data) end;
- 'diffie-hellman-group14-sha1' ->
- fun(Data) -> crypto:hash(sha, Data) end;
-
- 'diffie-hellman-group-exchange-sha1' ->
- fun(Data) -> crypto:hash(sha, Data) end;
- 'diffie-hellman-group-exchange-sha256' ->
- fun(Data) -> crypto:hash(sha256, Data) end;
-
- 'ecdh-sha2-nistp256' ->
- fun(Data) -> crypto:hash(sha256,Data) end;
- 'ecdh-sha2-nistp384' ->
- fun(Data) -> crypto:hash(sha384,Data) end;
- 'ecdh-sha2-nistp521' ->
- fun(Data) -> crypto:hash(sha512,Data) end;
- _ ->
- exit({bad_algorithm,SSH#ssh.kex})
- end,
- hash(SSH, Char, Bits, HASH).
-hash(_SSH, _Char, 0, _HASH) ->
+%%%----------------------------------------------------------------
+%% return N hash bytes (HASH)
+hash(_SSH, _Char, 0) ->
<<>>;
-hash(SSH, Char, N, HASH) ->
- K = ssh_bits:mpint(SSH#ssh.shared_secret),
+hash(SSH, Char, N) ->
+ HashAlg = sha(SSH#ssh.kex),
+ K = SSH#ssh.shared_secret,
H = SSH#ssh.exchanged_hash,
- SessionID = SSH#ssh.session_id,
- K1 = HASH([K, H, Char, SessionID]),
+ K1 = crypto:hash(HashAlg, [K, H, Char, SSH#ssh.session_id]),
Sz = N div 8,
- <<Key:Sz/binary, _/binary>> = hash(K, H, K1, N-128, HASH),
+ <<Key:Sz/binary, _/binary>> = hash(K, H, K1, N-128, HashAlg),
Key.
-hash(_K, _H, Ki, N, _HASH) when N =< 0 ->
+hash(_K, _H, Ki, N, _HashAlg) when N =< 0 ->
Ki;
-hash(K, H, Ki, N, HASH) ->
- Kj = HASH([K, H, Ki]),
- hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH).
-
-kex_h(SSH, Key, E, F, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>,
- crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-
-kex_h(SSH, Curve, Key, Q_c, Q_s, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(Q_c), ?Empint(Q_s), ?Empint(K)>>,
- crypto:hash(sha(Curve), L).
-
-kex_h(SSH, Key, Min, NBits, Max, Prime, Gen, E, F, K) ->
- KeyBin = public_key:ssh_encode(Key, ssh2_pubkey),
- L = if Min==-1; Max==-1 ->
- %% flag from 'ssh_msg_kex_dh_gex_request_old'
- %% It was like this before that message was supported,
- %% why?
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Empint(E), ?Empint(F), ?Empint(K)>>;
- true ->
- <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
- ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit), ?Ebinary(KeyBin),
- ?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
- ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>
- end,
- crypto:hash(sha((SSH#ssh.algorithms)#alg.kex), L).
-
+hash(K, H, Ki, N, HashAlg) ->
+ Kj = crypto:hash(HashAlg, [K, H, Ki]),
+ hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HashAlg).
+
+%%%----------------------------------------------------------------
+kex_hash(SSH, Key, SignAlg, HashAlg, Args) ->
+ crypto:hash(HashAlg, kex_plaintext(SSH,Key,SignAlg,Args)).
+
+kex_plaintext(SSH, Key, SignAlg, Args) ->
+ EncodedKey = public_key:ssh_encode({Key,SignAlg}, ssh2_pubkey),
+ <<?Estring(SSH#ssh.c_version), ?Estring(SSH#ssh.s_version),
+ ?Ebinary(SSH#ssh.c_keyinit), ?Ebinary(SSH#ssh.s_keyinit),
+ ?Ebinary(EncodedKey),
+ (kex_alg_dependent(Args))/binary>>.
+
+kex_alg_dependent({E, F, K}) ->
+ %% diffie-hellman and ec diffie-hellman (with E = Q_c, F = Q_s)
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({-1, _, -1, _, _, E, F, K}) ->
+ %% ssh_msg_kex_dh_gex_request_old
+ <<?Empint(E), ?Empint(F), ?Empint(K)>>;
+
+kex_alg_dependent({Min, NBits, Max, Prime, Gen, E, F, K}) ->
+ %% diffie-hellman group exchange
+ <<?Euint32(Min), ?Euint32(NBits), ?Euint32(Max),
+ ?Empint(Prime), ?Empint(Gen), ?Empint(E), ?Empint(F), ?Empint(K)>>.
+
+%%%----------------------------------------------------------------
+
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(#'RSAPublicKey'{}, 'ssh-rsa' ) -> true;
+
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-512') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-384') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'rsa-sha2-256') -> true;
+valid_key_sha_alg(#'RSAPrivateKey'{}, 'ssh-rsa' ) -> true;
+
+valid_key_sha_alg({_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
+valid_key_sha_alg(#'DSAPrivateKey'{}, 'ssh-dss') -> true;
+
+valid_key_sha_alg({#'ECPoint'{},{namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
+valid_key_sha_alg(#'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) -> sha(OID) == sha(Alg);
+valid_key_sha_alg(_, _) -> false.
+
+
+public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2
+public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
+public_algo({#'ECPoint'{},{namedCurve,OID}}) ->
+ Curve = public_key:oid2ssh_curvename(OID),
+ list_to_atom("ecdsa-sha2-" ++ binary_to_list(Curve)).
+
+
+
+
+sha('ssh-rsa') -> sha;
+sha('rsa-sha2-256') -> sha256;
+sha('rsa-sha2-384') -> sha384;
+sha('rsa-sha2-512') -> sha512;
+sha('ssh-dss') -> sha;
+sha('ecdsa-sha2-nistp256') -> sha(secp256r1);
+sha('ecdsa-sha2-nistp384') -> sha(secp384r1);
+sha('ecdsa-sha2-nistp521') -> sha(secp521r1);
sha(secp256r1) -> sha256;
sha(secp384r1) -> sha384;
sha(secp521r1) -> sha512;
sha('diffie-hellman-group1-sha1') -> sha;
sha('diffie-hellman-group14-sha1') -> sha;
+sha('diffie-hellman-group14-sha256') -> sha256;
+sha('diffie-hellman-group16-sha512') -> sha512;
+sha('diffie-hellman-group18-sha512') -> sha512;
sha('diffie-hellman-group-exchange-sha1') -> sha;
sha('diffie-hellman-group-exchange-sha256') -> sha256;
sha(?'secp256r1') -> sha(secp256r1);
sha(?'secp384r1') -> sha(secp384r1);
-sha(?'secp521r1') -> sha(secp521r1).
-
-
-mac_key_size('hmac-sha1') -> 20*8;
-mac_key_size('hmac-sha1-96') -> 20*8;
-mac_key_size('hmac-md5') -> 16*8;
-mac_key_size('hmac-md5-96') -> 16*8;
-mac_key_size('hmac-sha2-256')-> 32*8;
-mac_key_size('hmac-sha2-512')-> 512;
-mac_key_size(none) -> 0.
+sha(?'secp521r1') -> sha(secp521r1);
+sha('ecdh-sha2-nistp256') -> sha(secp256r1);
+sha('ecdh-sha2-nistp384') -> sha(secp384r1);
+sha('ecdh-sha2-nistp521') -> sha(secp521r1);
+sha(Str) when is_list(Str), length(Str)<50 -> sha(list_to_atom(Str)).
+
+
+mac_key_bytes('hmac-sha1') -> 20;
+mac_key_bytes('hmac-sha1-96') -> 20;
+mac_key_bytes('hmac-md5') -> 16;
+mac_key_bytes('hmac-md5-96') -> 16;
+mac_key_bytes('hmac-sha2-256')-> 32;
+mac_key_bytes('hmac-sha2-512')-> 64;
+mac_key_bytes('AEAD_AES_128_GCM') -> 0;
+mac_key_bytes('AEAD_AES_256_GCM') -> 0;
+mac_key_bytes(none) -> 0.
mac_digest_size('hmac-sha1') -> 20;
mac_digest_size('hmac-sha1-96') -> 12;
@@ -1649,9 +1845,6 @@ mac_digest_size('AEAD_AES_128_GCM') -> 16;
mac_digest_size('AEAD_AES_256_GCM') -> 16;
mac_digest_size(none) -> 0.
-peer_name({Host, _}) ->
- Host.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Diffie-Hellman utils
@@ -1659,9 +1852,19 @@ peer_name({Host, _}) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dh_group('diffie-hellman-group1-sha1') -> ?dh_group1;
-dh_group('diffie-hellman-group14-sha1') -> ?dh_group14.
+dh_group('diffie-hellman-group14-sha1') -> ?dh_group14;
+dh_group('diffie-hellman-group14-sha256') -> ?dh_group14;
+dh_group('diffie-hellman-group16-sha512') -> ?dh_group16;
+dh_group('diffie-hellman-group18-sha512') -> ?dh_group18.
%%%----------------------------------------------------------------
+parallell_gen_key(Ssh = #ssh{keyex_key = {x, {G, P}},
+ algorithms = Algs}) ->
+ Sz = dh_bits(Algs),
+ {Public, Private} = generate_key(dh, [P,G,2*Sz]),
+ Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}.
+
+
generate_key(Algorithm, Args) ->
{Public,Private} = crypto:generate_key(Algorithm, Args),
{crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}.
@@ -1672,6 +1875,15 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) ->
crypto:bytes_to_integer(Shared).
+dh_bits(#alg{encrypt = Encrypt,
+ send_mac = SendMac}) ->
+ C = cipher(Encrypt),
+ 8 * lists:max([C#cipher_data.key_bytes,
+ C#cipher_data.block_bytes,
+ C#cipher_data.iv_bytes,
+ mac_key_bytes(SendMac)
+ ]).
+
ecdh_curve('ecdh-sha2-nistp256') -> secp256r1;
ecdh_curve('ecdh-sha2-nistp384') -> secp384r1;
ecdh_curve('ecdh-sha2-nistp521') -> secp521r1.
@@ -1734,10 +1946,6 @@ len_supported(Name, Len) ->
same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
-
-%% default_algorithms(kex) -> % Example of how to disable an algorithm
-%% supported_algorithms(kex, ['ecdh-sha2-nistp521']);
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Other utils
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index f91cb1dd63..87c3719514 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@
-define(SSH_MSG_DEBUG, 4).
-define(SSH_MSG_SERVICE_REQUEST, 5).
-define(SSH_MSG_SERVICE_ACCEPT, 6).
+-define(SSH_MSG_EXT_INFO, 7).
-define(SSH_MSG_KEXINIT, 20).
-define(SSH_MSG_NEWKEYS, 21).
@@ -88,6 +89,20 @@
name %% string
}).
+-record(ssh_msg_ext_info,
+ {
+ nr_extensions, %% uint32
+
+ %% repeat the following 2 fields "nr-extensions" times:
+ %% string extension-name
+ %% string extension-value
+
+ data %% [{extension-name, %% string
+ %% extension-value}, %% string
+ %% ...
+ %% ]
+ }).
+
-record(ssh_msg_kexinit,
{
cookie, %% random(16)
@@ -112,7 +127,7 @@
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% diffie-hellman-group1-sha1 | diffie-hellman-group14-sha1
+%% diffie-hellman-group*-sha*
-define(SSH_MSG_KEXDH_INIT, 30).
-define(SSH_MSG_KEXDH_REPLY, 31).
@@ -238,4 +253,15 @@
-define(dh_group14,
{2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF}).
+%%% rfc 3526, ch5
+%%% Size 4096-bit
+-define(dh_group16,
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF}).
+
+%%% rfc 3526, ch7
+%%% Size 8192-bit
+-define(dh_group18,
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}).
+
+
-endif. % -ifdef(ssh_transport).
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index 15858f36e1..133b2c6450 100644
--- a/lib/ssh/src/sshc_sup.erl
+++ b/lib/ssh/src/sshc_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -27,23 +27,25 @@
-behaviour(supervisor).
--export([start_link/1, start_child/1, stop_child/1]).
+-export([start_link/0, start_child/1, stop_child/1]).
%% Supervisor callback
-export([init/1]).
+-define(SSHC_SUP, ?MODULE).
+
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Args) ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, [Args]).
+start_link() ->
+ supervisor:start_link({local,?SSHC_SUP}, ?MODULE, []).
start_child(Args) ->
supervisor:start_child(?MODULE, Args).
stop_child(Client) ->
spawn(fun() ->
- ClientSup = whereis(?MODULE),
+ ClientSup = whereis(?SSHC_SUP),
supervisor:terminate_child(ClientSup, Client)
end),
ok.
@@ -51,22 +53,17 @@ stop_child(Client) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init(Args) ->
- RestartStrategy = simple_one_for_one,
- MaxR = 0,
- MaxT = 3600,
- {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec(Args)]}}.
-
-%%%=========================================================================
-%%% Internal functions
-%%%=========================================================================
-child_spec(_) ->
- Name = undefined, % As simple_one_for_one is used.
- StartFunc = {ssh_connection_handler, start_link, []},
- Restart = temporary,
- Shutdown = 4000,
- Modules = [ssh_connection_handler],
- Type = worker,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+init(_) ->
+ SupFlags = #{strategy => simple_one_for_one,
+ intensity => 0,
+ period => 3600
+ },
+ ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
+ start => {ssh_connection_handler, start_link, []},
+ restart => temporary,
+ shutdown => 4000,
+ type => worker,
+ modules => [ssh_connection_handler]
+ }
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index 04d2df30f7..c23e65d955 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
%%
%%
%%----------------------------------------------------------------------
-%% Purpose: The top supervisor for ssh servers hangs under
+%% Purpose: The top supervisor for ssh servers hangs under
%% ssh_sup.
%%----------------------------------------------------------------------
@@ -29,90 +29,79 @@
-include("ssh.hrl").
--export([start_link/1, start_child/1, stop_child/1,
- stop_child/3, system_name/1]).
+-export([start_link/0,
+ start_child/4,
+ stop_child/1,
+ stop_child/3
+]).
%% Supervisor callback
-export([init/1]).
+-define(SSHD_SUP, ?MODULE).
+
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Servers) ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]).
+start_link() ->
+ %% No children are start now. We wait until the user calls ssh:daemon
+ %% and uses start_child/4 to create the children
+ supervisor:start_link({local,?SSHD_SUP}, ?MODULE, []).
-start_child(ServerOpts) ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- Profile = proplists:get_value(profile, proplists:get_value(ssh_opts, ServerOpts), ?DEFAULT_PROFILE),
+start_child(Address, Port, Profile, Options) ->
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, Profile),
- supervisor:delete_child(?MODULE, Name),
- supervisor:start_child(?MODULE, Spec);
- Reply ->
- Reply
- end;
+ %% Here we start listening on a new Host/Port/Profile
+ Spec = child_spec(Address, Port, Profile, Options),
+ supervisor:start_child(?SSHD_SUP, Spec);
Pid ->
+ %% Here we resume listening on a new Host/Port/Profile after
+ %% haveing stopped listening to he same with ssh:stop_listen(Pid)
AccPid = ssh_system_sup:acceptor_supervisor(Pid),
- ssh_acceptor_sup:start_child(AccPid, ServerOpts)
+ ssh_acceptor_sup:start_child(AccPid, Address, Port, Profile, Options),
+ {ok,Pid}
end.
-stop_child(Name) ->
- supervisor:terminate_child(?MODULE, Name).
+stop_child(ChildId) when is_tuple(ChildId) ->
+ supervisor:terminate_child(?SSHD_SUP, ChildId);
+stop_child(ChildPid) when is_pid(ChildPid)->
+ stop_child(system_name(ChildPid)).
-stop_child(Address, Port, Profile) ->
- Name = id(Address, Port, Profile),
- stop_child(Name).
-system_name(SysSup) ->
- Children = supervisor:which_children(sshd_sup),
- system_name(SysSup, Children).
+stop_child(Address, Port, Profile) ->
+ Id = id(Address, Port, Profile),
+ stop_child(Id).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
--spec init( [term()] ) -> {ok,{supervisor:sup_flags(),[supervisor:child_spec()]}} | ignore .
-
-init([Servers]) ->
- RestartStrategy = one_for_one,
- MaxR = 10,
- MaxT = 3600,
- Fun = fun(ServerOpts) ->
- Address = proplists:get_value(address, ServerOpts),
- Port = proplists:get_value(port, ServerOpts),
- child_spec(Address, Port, ServerOpts)
- end,
- Children = lists:map(Fun, Servers),
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+init(_) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [
+ ],
+ {ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-child_spec(Address, Port, ServerOpts) ->
- 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,
- Modules = [ssh_system_sup],
- Type = supervisor,
- {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+child_spec(Address, Port, Profile, Options) ->
+ #{id => id(Address, Port, Profile),
+ start => {ssh_system_sup, start_link, [Address, Port, Profile, Options]},
+ restart => temporary,
+ shutdown => infinity,
+ type => supervisor,
+ modules => [ssh_system_sup]
+ }.
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}
+ {server, ssh_system_sup, Address, Port, Profile}.
+
+system_name(SysSup) ->
+ case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHD_SUP)) of
+ {Name, SysSup, _, _} -> Name;
+ false -> undefind
end.
-system_name([], _ ) ->
- undefined;
-system_name(SysSup, [{Name, SysSup, _, _} | _]) ->
- Name;
-system_name(SysSup, [_ | Rest]) ->
- system_name(SysSup, Rest).
diff --git a/lib/ssh/test/Makefile b/lib/ssh/test/Makefile
index 6ce6d6f537..32e76cf077 100644
--- a/lib/ssh/test/Makefile
+++ b/lib/ssh/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2004-2016. All Rights Reserved.
+# Copyright Ericsson AB 2004-2017. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -36,7 +36,7 @@ MODULES= \
ssh_options_SUITE \
ssh_renegotiate_SUITE \
ssh_basic_SUITE \
- ssh_benchmark_SUITE \
+ ssh_bench_SUITE \
ssh_connection_SUITE \
ssh_protocol_SUITE \
ssh_sftp_SUITE \
@@ -50,9 +50,11 @@ MODULES= \
ssh_key_cb_options \
ssh_trpt_test_lib \
ssh_echo_server \
+ ssh_bench_dev_null \
ssh_peername_sockname_server \
ssh_test_cli \
- ssh_relay
+ ssh_relay \
+ ssh_eqc_event_handler
HRL_FILES_NEEDED_IN_TEST= \
$(ERL_TOP)/lib/ssh/test/ssh_test_lib.hrl \
diff --git a/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl b/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl
new file mode 100644
index 0000000000..c07140dc43
--- /dev/null
+++ b/lib/ssh/test/property_test/ssh_eqc_client_info_timing.erl
@@ -0,0 +1,92 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ssh_eqc_client_info_timing).
+
+-compile(export_all).
+
+-proptest(eqc).
+-proptest([triq,proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC,true).
+%%-define(PROPER,true).
+%%-define(TRIQ,true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+
+%%% Properties:
+
+prop_seq(_Config) ->
+ {ok,Pid} = ssh_eqc_event_handler:add_report_handler(),
+ {_, _, Port} = init_daemon(),
+ numtests(1000,
+ ?FORALL(Delay, choose(0,100),%% Micro seconds
+ try
+ send_bad_sequence(Port, Delay, Pid),
+ not any_relevant_error_report(Pid)
+ catch
+ C:E -> io:format('~p:~p~n',[C,E]),
+ false
+ end
+ )).
+
+send_bad_sequence(Port, Delay, Pid) ->
+ {ok,S} = gen_tcp:connect("localhost",Port,[]),
+ gen_tcp:send(S,"Illegal info-string\r\n"),
+ ssh_test_lib:sleep_microsec(Delay),
+ gen_tcp:close(S).
+
+any_relevant_error_report(Pid) ->
+ {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid),
+ lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) ->
+ lists:member({reason,{badmatch,{error,closed}}}, L);
+ (_) ->
+ false
+ end, Reports).
+
+%%%================================================================
+init_daemon() ->
+ ok = begin ssh:stop(), ssh:start() end,
+ ssh_test_lib:daemon([]).
+
diff --git a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
index dc3b7dc7e6..165274241c 100644
--- a/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
+++ b/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -54,15 +54,18 @@
-endif.
-endif.
+%% Public key records:
+-include_lib("public_key/include/public_key.hrl").
%%% Properties:
prop_ssh_decode() ->
- ?FORALL(Msg, ssh_msg(),
- try ssh_message:decode(Msg)
+ ?FORALL({Msg,KexFam}, ?LET(KF, kex_family(), {ssh_msg(KF),KF} ),
+ try ssh_message:decode(decode_state(Msg,KexFam))
of
_ -> true
catch
+
C:E -> io:format('~p:~p~n',[C,E]),
false
end
@@ -71,122 +74,101 @@ prop_ssh_decode() ->
%%% This fails because ssh_message is not symmetric in encode and decode regarding data types
prop_ssh_decode_encode() ->
- ?FORALL(Msg, ssh_msg(),
- Msg == ssh_message:encode(ssh_message:decode(Msg))
+ ?FORALL({Msg,KexFam}, ?LET(KF, kex_family(), {ssh_msg(KF),KF} ),
+ Msg == ssh_message:encode(
+ fix_asym(
+ ssh_message:decode(decode_state(Msg,KexFam))))
).
%%%================================================================
%%%
-%%% Scripts to generate message generators
-%%%
-
-%% awk '/^( |\t)+byte( |\t)+SSH/,/^( |\t)*$/{print}' rfc425?.txt | sed 's/^\( \|\\t\)*//' > msgs.txt
-
-%% awk '/^byte( |\t)+SSH/{print $2","}' < msgs.txt
-
-%% awk 'BEGIN{print "%%%---- BEGIN GENERATED";prev=0} END{print " >>.\n%%%---- END GENERATED"} /^byte( |\t)+SSH/{if (prev==1) print " >>.\n"; prev=1; printf "%c%s%c",39,$2,39; print "()->\n <<?"$2;next} /^string( |\t)+\"/{print " ,"$2;next} /^string( |\t)+.*address/{print " ,(ssh_string_address())/binary %%",$2,$3,$4,$5,$6;next}/^string( |\t)+.*US-ASCII/{print " ,(ssh_string_US_ASCII())/binary %%",$2,$3,$4,$5,$6;next} /^string( |\t)+.*UTF-8/{print " ,(ssh_string_UTF_8())/binary %% ",$2,$3,$4,$5,$6;next} /^[a-z0-9]+( |\t)/{print " ,(ssh_"$1"())/binary %%",$2,$3,$4,$5,$6;next} /^byte\[16\]( |\t)+/{print" ,(ssh_byte_16())/binary %%",$2,$3,$4,$5,$6;next} /^name-list( |\t)+/{print" ,(ssh_name_list())/binary %%",$2,$3,$4,$5,$6;next} /./{print "?? %%",$0}' < msgs.txt > gen.txt
-
-%%%================================================================
-%%%
%%% Generators
%%%
-ssh_msg() -> ?LET(M,oneof(
-[[msg_code('SSH_MSG_CHANNEL_CLOSE'),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_DATA'),gen_uint32(),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_EOF'),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_EXTENDED_DATA'),gen_uint32(),gen_uint32(),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_FAILURE'),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("direct-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("forwarded-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("session"),gen_uint32(),gen_uint32(),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("x11"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN_CONFIRMATION'),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_OPEN_FAILURE'),gen_uint32(),gen_uint32(),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("env"),gen_boolean(),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exec"),gen_boolean(),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-signal"),0,gen_string( ),gen_boolean(),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-status"),0,gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("pty-req"),gen_boolean(),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("shell"),gen_boolean()],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("signal"),0,gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("subsystem"),gen_boolean(),gen_string( )],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("window-change"),0,gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("x11-req"),gen_boolean(),gen_boolean(),gen_string( ),gen_string( ),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("xon-xoff"),0,gen_boolean()],
- [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string( ),gen_boolean()],
- [msg_code('SSH_MSG_CHANNEL_SUCCESS'),gen_uint32()],
- [msg_code('SSH_MSG_CHANNEL_WINDOW_ADJUST'),gen_uint32(),gen_uint32()],
-%%Assym [msg_code('SSH_MSG_DEBUG'),gen_boolean(),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_DISCONNECT'),gen_uint32(),gen_string( ),gen_string( )],
-%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("cancel-tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
-%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
-%%Assym [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string( ),gen_boolean()],
- [msg_code('SSH_MSG_IGNORE'),gen_string( )],
- %% [msg_code('SSH_MSG_KEXDH_INIT'),gen_mpint()],
- %% [msg_code('SSH_MSG_KEXDH_REPLY'),gen_string( ),gen_mpint(),gen_string( )],
- %% [msg_code('SSH_MSG_KEXINIT'),gen_byte(16),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_boolean(),gen_uint32()],
- [msg_code('SSH_MSG_KEX_DH_GEX_GROUP'),gen_mpint(),gen_mpint()],
- [msg_code('SSH_MSG_NEWKEYS')],
- [msg_code('SSH_MSG_REQUEST_FAILURE')],
- [msg_code('SSH_MSG_REQUEST_SUCCESS')],
- [msg_code('SSH_MSG_REQUEST_SUCCESS'),gen_uint32()],
- [msg_code('SSH_MSG_SERVICE_ACCEPT'),gen_string( )],
- [msg_code('SSH_MSG_SERVICE_REQUEST'),gen_string( )],
- [msg_code('SSH_MSG_UNIMPLEMENTED'),gen_uint32()],
- [msg_code('SSH_MSG_USERAUTH_BANNER'),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_USERAUTH_FAILURE'),gen_name_list(),gen_boolean()],
- [msg_code('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ'),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_USERAUTH_PK_OK'),gen_string( ),gen_string( )],
- [msg_code('SSH_MSG_USERAUTH_SUCCESS')]
-]
-
-), list_to_binary(M)).
-
-
-%%%================================================================
-%%%
-%%% Generator
-%%%
-
-do() ->
- io_lib:format('[~s~n]',
- [write_gen(
- files(["rfc4254.txt",
- "rfc4253.txt",
- "rfc4419.txt",
- "rfc4252.txt",
- "rfc4256.txt"]))]).
-
-
-write_gen(L) when is_list(L) ->
- string:join(lists:map(fun write_gen/1, L), ",\n ");
-write_gen({MsgName,Args}) ->
- lists:flatten(["[",generate_args([MsgName|Args]),"]"]).
-
-generate_args(As) -> string:join([generate_arg(A) || A <- As], ",").
-
-generate_arg({<<"string">>, <<"\"",B/binary>>}) ->
- S = get_string($",B),
- ["gen_string(\"",S,"\")"];
-generate_arg({<<"string">>, _}) -> "gen_string( )";
-generate_arg({<<"byte[",B/binary>>, _}) ->
- io_lib:format("gen_byte(~p)",[list_to_integer(get_string($],B))]);
-generate_arg({<<"byte">> ,_}) -> "gen_byte()";
-generate_arg({<<"uint16">>,_}) -> "gen_uint16()";
-generate_arg({<<"uint32">>,_}) -> "gen_uint32()";
-generate_arg({<<"uint64">>,_}) -> "gen_uint64()";
-generate_arg({<<"mpint">>,_}) -> "gen_mpint()";
-generate_arg({<<"name-list">>,_}) -> "gen_name_list()";
-generate_arg({<<"boolean">>,<<"FALSE">>}) -> "0";
-generate_arg({<<"boolean">>,<<"TRUE">>}) -> "1";
-generate_arg({<<"boolean">>,_}) -> "gen_boolean()";
-generate_arg({<<"....">>,_}) -> ""; %% FIXME
-generate_arg(Name) when is_binary(Name) ->
- lists:flatten(["msg_code('",binary_to_list(Name),"')"]).
-
+ssh_msg(<<"dh">>) ->
+ ?LET(M,oneof(
+ [
+ [msg_code('SSH_MSG_KEXDH_INIT'),gen_mpint()], % 30
+ [msg_code('SSH_MSG_KEXDH_REPLY'),gen_pubkey_string(rsa),gen_mpint(),gen_signature_string(rsa)] % 31
+ | rest_ssh_msgs()
+ ]),
+ list_to_binary(M));
+
+ssh_msg(<<"dh_gex">>) ->
+ ?LET(M,oneof(
+ [
+ [msg_code('SSH_MSG_KEX_DH_GEX_REQUEST_OLD'),gen_uint32()], % 30
+ [msg_code('SSH_MSG_KEX_DH_GEX_GROUP'),gen_mpint(),gen_mpint()] % 31
+ | rest_ssh_msgs()
+ ]),
+ list_to_binary(M));
+
+ ssh_msg(<<"ecdh">>) ->
+ ?LET(M,oneof(
+ [
+ [msg_code('SSH_MSG_KEX_ECDH_INIT'),gen_mpint()], % 30
+ [msg_code('SSH_MSG_KEX_ECDH_REPLY'),gen_pubkey_string(ecdsa),gen_mpint(),gen_signature_string(ecdsa)] % 31
+ | rest_ssh_msgs()
+ ]),
+ list_to_binary(M)).
+
+
+rest_ssh_msgs() ->
+ [%% SSH_MSG_USERAUTH_INFO_RESPONSE
+ %% hard args SSH_MSG_USERAUTH_INFO_REQUEST
+ %% rfc4252 p12 error SSH_MSG_USERAUTH_REQUEST
+ [msg_code('SSH_MSG_KEX_DH_GEX_REQUEST'),gen_uint32(),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_KEX_DH_GEX_INIT'),gen_mpint()],
+ [msg_code('SSH_MSG_KEX_DH_GEX_REPLY'),gen_pubkey_string(rsa),gen_mpint(),gen_signature_string(rsa)],
+ [msg_code('SSH_MSG_CHANNEL_CLOSE'),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_DATA'),gen_uint32(),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_EOF'),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_EXTENDED_DATA'),gen_uint32(),gen_uint32(),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_FAILURE'),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("direct-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("forwarded-tcpip"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32(),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("session"),gen_uint32(),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string("x11"),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN'),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN_CONFIRMATION'),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_OPEN_FAILURE'),gen_uint32(),gen_uint32(),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("env"),gen_boolean(),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exec"),gen_boolean(),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-signal"),0,gen_string( ),gen_boolean(),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("exit-status"),0,gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("pty-req"),gen_boolean(),gen_string( ),gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32(),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("shell"),gen_boolean()],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("signal"),0,gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("subsystem"),gen_boolean(),gen_string( )],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("window-change"),0,gen_uint32(),gen_uint32(),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("x11-req"),gen_boolean(),gen_boolean(),gen_string( ),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string("xon-xoff"),0,gen_boolean()],
+ [msg_code('SSH_MSG_CHANNEL_REQUEST'),gen_uint32(),gen_string( ),gen_boolean()],
+ [msg_code('SSH_MSG_CHANNEL_SUCCESS'),gen_uint32()],
+ [msg_code('SSH_MSG_CHANNEL_WINDOW_ADJUST'),gen_uint32(),gen_uint32()],
+ [msg_code('SSH_MSG_DEBUG'),gen_boolean(),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_DISCONNECT'),gen_uint32(),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("cancel-tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string("tcpip-forward"),gen_boolean(),gen_string( ),gen_uint32()],
+ [msg_code('SSH_MSG_GLOBAL_REQUEST'),gen_string( ),gen_boolean()],
+ [msg_code('SSH_MSG_IGNORE'),gen_string( )],
+ [msg_code('SSH_MSG_KEXINIT'),gen_byte(16),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_name_list(),gen_boolean(),gen_uint32()],
+ [msg_code('SSH_MSG_NEWKEYS')],
+ [msg_code('SSH_MSG_REQUEST_FAILURE')],
+ [msg_code('SSH_MSG_REQUEST_SUCCESS')],
+ [msg_code('SSH_MSG_REQUEST_SUCCESS'),gen_uint32()],
+ [msg_code('SSH_MSG_SERVICE_ACCEPT'),gen_string( )],
+ [msg_code('SSH_MSG_SERVICE_REQUEST'),gen_string( )],
+ [msg_code('SSH_MSG_UNIMPLEMENTED'),gen_uint32()],
+ [msg_code('SSH_MSG_USERAUTH_BANNER'),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_USERAUTH_FAILURE'),gen_name_list(),gen_boolean()],
+ [msg_code('SSH_MSG_USERAUTH_PASSWD_CHANGEREQ'),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_USERAUTH_PK_OK'),gen_string( ),gen_string( )],
+ [msg_code('SSH_MSG_USERAUTH_SUCCESS')]
+ ].
+
+kex_family() -> oneof([<<"dh">>, <<"dh_gex">>, <<"ecdh">>]).
gen_boolean() -> choose(0,1).
@@ -202,10 +184,7 @@ gen_byte(N) when N>0 -> [gen_byte() || _ <- lists:seq(1,N)].
gen_char() -> choose($a,$z).
-gen_mpint() -> ?LET(Size, choose(1,20),
- ?LET(Str, vector(Size, gen_byte()),
- gen_string( strip_0s(Str) )
- )).
+gen_mpint() -> ?LET(I, largeint(), ssh_bits:mpint(I)).
strip_0s([0|T]) -> strip_0s(T);
strip_0s(X) -> X.
@@ -230,13 +209,22 @@ gen_name() -> gen_string().
uint32_to_list(I) -> binary_to_list(<<I:32/unsigned-big-integer>>).
-%%%----
-get_string(Delim, B) ->
- binary_to_list( element(1, split_binary(B, count_string_chars(Delim,B,0))) ).
-
-count_string_chars(Delim, <<Delim,_/binary>>, Acc) -> Acc;
-count_string_chars(Delim, <<_,B/binary>>, Acc) -> count_string_chars(Delim, B, Acc+1).
+gen_pubkey_string(Type) ->
+ PubKey = case Type of
+ rsa -> #'RSAPublicKey'{modulus = 12345,publicExponent = 2};
+ ecdsa -> {#'ECPoint'{point=[1,2,3,4,5]},
+ {namedCurve,{1,2,840,10045,3,1,7}}} % 'secp256r1' nistp256
+ end,
+ gen_string(public_key:ssh_encode(PubKey, ssh2_pubkey)).
+
+gen_signature_string(Type) ->
+ Signature = <<"hejhopp">>,
+ Id = case Type of
+ rsa -> "ssh-rsa";
+ ecdsa -> "ecdsa-sha2-nistp256"
+ end,
+ gen_string(gen_string(Id) ++ gen_string(Signature)).
-define(MSG_CODE(Name,Num),
msg_code(Name) -> Num;
@@ -273,124 +261,44 @@ msg_code(Num) -> Name
?MSG_CODE('SSH_MSG_CHANNEL_FAILURE', 100);
?MSG_CODE('SSH_MSG_USERAUTH_INFO_REQUEST', 60);
?MSG_CODE('SSH_MSG_USERAUTH_INFO_RESPONSE', 61);
+?MSG_CODE('SSH_MSG_KEXDH_INIT', 30);
+?MSG_CODE('SSH_MSG_KEXDH_REPLY', 31);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST_OLD', 30);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_REQUEST', 34);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_GROUP', 31);
?MSG_CODE('SSH_MSG_KEX_DH_GEX_INIT', 32);
-?MSG_CODE('SSH_MSG_KEX_DH_GEX_REPLY', 33).
-
-%%%=============================================================================
-%%%=============================================================================
-%%%=============================================================================
-
-files(Fs) ->
- Defs = lists:usort(lists:flatten(lists:map(fun file/1, Fs))),
- DefinedIDs = lists:usort([binary_to_list(element(1,D)) || D <- Defs]),
- WantedIDs = lists:usort(wanted_messages()),
- Missing = WantedIDs -- DefinedIDs,
- case Missing of
- [] -> ok;
- _ -> io:format('%% Warning: missing ~p~n', [Missing])
- end,
- Defs.
-
-
-file(F) ->
- {ok,B} = file:read_file(F),
- hunt_msg_def(B).
-
-
-hunt_msg_def(<<"\n",B/binary>>) -> some_hope(skip_blanks(B));
-hunt_msg_def(<<_, B/binary>>) -> hunt_msg_def(B);
-hunt_msg_def(<<>>) -> [].
-
-some_hope(<<"byte ", B/binary>>) -> try_message(skip_blanks(B));
-some_hope(B) -> hunt_msg_def(B).
-
-try_message(B = <<"SSH_MSG_",_/binary>>) ->
- {ID,Rest} = get_id(B),
- case lists:member(binary_to_list(ID), wanted_messages()) of
- true ->
- {Lines,More} = get_def_lines(skip_blanks(Rest), []),
- [{ID,lists:reverse(Lines)} | hunt_msg_def(More)];
- false ->
- hunt_msg_def(Rest)
- end;
-try_message(B) -> hunt_msg_def(B).
-
+?MSG_CODE('SSH_MSG_KEX_DH_GEX_REPLY', 33);
+?MSG_CODE('SSH_MSG_KEX_ECDH_INIT', 30);
+?MSG_CODE('SSH_MSG_KEX_ECDH_REPLY', 31).
+
+%%%====================================================
+%%%=== WARNING: Knowledge of the test object ahead! ===
+%%%====================================================
+
+%% SSH message records:
+-include_lib("ssh/src/ssh_connect.hrl").
+-include_lib("ssh/src/ssh_transport.hrl").
+
+%%% Encoding and decodeing is asymetric so out=binary in=string. Sometimes. :(
+-define(fix_asym_Xdh_reply(S),
+ fix_asym(#S{public_host_key = Key, h_sig = {Alg,Sig}} = M) ->
+ M#S{public_host_key = {Key, list_to_atom(Alg)}, h_sig = Sig}
+).
-skip_blanks(<<32, B/binary>>) -> skip_blanks(B);
-skip_blanks(<< 9, B/binary>>) -> skip_blanks(B);
-skip_blanks(B) -> B.
-
-get_def_lines(B0 = <<"\n",B/binary>>, Acc) ->
- {ID,Rest} = get_id(skip_blanks(B)),
- case {size(ID), skip_blanks(Rest)} of
- {0,<<"....",More/binary>>} ->
- {Text,LineEnd} = get_to_eol(skip_blanks(More)),
- get_def_lines(LineEnd, [{<<"....">>,Text}|Acc]);
- {0,_} ->
- {Acc,B0};
- {_,Rest1} ->
- {Text,LineEnd} = get_to_eol(Rest1),
- get_def_lines(LineEnd, [{ID,Text}|Acc])
- end;
-get_def_lines(B, Acc) ->
- {Acc,B}.
-
-get_to_eol(B) -> split_binary(B, count_to_eol(B,0)).
+fix_asym(#ssh_msg_global_request{name=N} = M) -> M#ssh_msg_global_request{name = binary_to_list(N)};
+fix_asym(#ssh_msg_debug{message=D,language=L} = M) -> M#ssh_msg_debug{message = binary_to_list(D),
+ language = binary_to_list(L)};
+fix_asym(#ssh_msg_kexinit{cookie=C} = M) -> M#ssh_msg_kexinit{cookie = <<C:128>>};
+?fix_asym_Xdh_reply(ssh_msg_kexdh_reply);
+?fix_asym_Xdh_reply(ssh_msg_kex_dh_gex_reply);
+?fix_asym_Xdh_reply(ssh_msg_kex_ecdh_reply);
+fix_asym(M) -> M.
-count_to_eol(<<"\n",_/binary>>, Acc) -> Acc;
-count_to_eol(<<>>, Acc) -> Acc;
-count_to_eol(<<_,B/binary>>, Acc) -> count_to_eol(B,Acc+1).
-
-get_id(B) -> split_binary(B, count_id_chars(B,0)).
-
-count_id_chars(<<C,B/binary>>, Acc) when $A=<C,C=<$Z -> count_id_chars(B,Acc+1);
-count_id_chars(<<C,B/binary>>, Acc) when $a=<C,C=<$z -> count_id_chars(B,Acc+1);
-count_id_chars(<<C,B/binary>>, Acc) when $0=<C,C=<$9 -> count_id_chars(B,Acc+1);
-count_id_chars(<<"_",B/binary>>, Acc) -> count_id_chars(B,Acc+1);
-count_id_chars(<<"-",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g name-list
-count_id_chars(<<"[",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g byte[16]
-count_id_chars(<<"]",B/binary>>, Acc) -> count_id_chars(B,Acc+1); %% e.g byte[16]
-count_id_chars(_, Acc) -> Acc.
-
-wanted_messages() ->
- ["SSH_MSG_CHANNEL_CLOSE",
- "SSH_MSG_CHANNEL_DATA",
- "SSH_MSG_CHANNEL_EOF",
- "SSH_MSG_CHANNEL_EXTENDED_DATA",
- "SSH_MSG_CHANNEL_FAILURE",
- "SSH_MSG_CHANNEL_OPEN",
- "SSH_MSG_CHANNEL_OPEN_CONFIRMATION",
- "SSH_MSG_CHANNEL_OPEN_FAILURE",
- "SSH_MSG_CHANNEL_REQUEST",
- "SSH_MSG_CHANNEL_SUCCESS",
- "SSH_MSG_CHANNEL_WINDOW_ADJUST",
- "SSH_MSG_DEBUG",
- "SSH_MSG_DISCONNECT",
- "SSH_MSG_GLOBAL_REQUEST",
- "SSH_MSG_IGNORE",
- "SSH_MSG_KEXDH_INIT",
- "SSH_MSG_KEXDH_REPLY",
- "SSH_MSG_KEXINIT",
- "SSH_MSG_KEX_DH_GEX_GROUP",
- "SSH_MSG_KEX_DH_GEX_REQUEST",
- "SSH_MSG_KEX_DH_GEX_REQUEST_OLD",
- "SSH_MSG_NEWKEYS",
- "SSH_MSG_REQUEST_FAILURE",
- "SSH_MSG_REQUEST_SUCCESS",
- "SSH_MSG_SERVICE_ACCEPT",
- "SSH_MSG_SERVICE_REQUEST",
- "SSH_MSG_UNIMPLEMENTED",
- "SSH_MSG_USERAUTH_BANNER",
- "SSH_MSG_USERAUTH_FAILURE",
-%% hard args "SSH_MSG_USERAUTH_INFO_REQUEST",
-%% "SSH_MSG_USERAUTH_INFO_RESPONSE",
- "SSH_MSG_USERAUTH_PASSWD_CHANGEREQ",
- "SSH_MSG_USERAUTH_PK_OK",
-%%rfc4252 p12 error "SSH_MSG_USERAUTH_REQUEST",
- "SSH_MSG_USERAUTH_SUCCESS"].
+%%% Message codes 30 and 31 are overloaded depending on kex family so arrange the decoder
+%%% input as the test object does
+decode_state(<<30,_/binary>>=Msg, KexFam) -> <<KexFam/binary, Msg/binary>>;
+decode_state(<<31,_/binary>>=Msg, KexFam) -> <<KexFam/binary, Msg/binary>>;
+decode_state(Msg, _) -> Msg.
diff --git a/lib/ssh/test/ssh.cover b/lib/ssh/test/ssh.cover
index a4221fbbbe..69d2a1c4f8 100644
--- a/lib/ssh/test/ssh.cover
+++ b/lib/ssh/test/ssh.cover
@@ -1,2 +1,3 @@
{incl_app,ssh,details}.
+{excl_mods, ssh, [ssh_dbg, ssh_info, ssh_server_key_api, ssh_sftpd_file_api]}. \ No newline at end of file
diff --git a/lib/ssh/test/ssh.spec b/lib/ssh/test/ssh.spec
index 0076fc275e..b4e3d36072 100644
--- a/lib/ssh/test/ssh.spec
+++ b/lib/ssh/test/ssh.spec
@@ -1,6 +1,8 @@
{suites,"../ssh_test",all}.
-{skip_suites, "../ssh_test", [ssh_benchmark_SUITE],
+{skip_suites, "../ssh_test", [ssh_bench_SUITE,
+ ssh_upgrade_SUITE
+ ],
"Benchmarks run separately"}.
diff --git a/lib/ssh/test/ssh_algorithms_SUITE.erl b/lib/ssh/test/ssh_algorithms_SUITE.erl
index 0f68130a05..98964a2c8a 100644
--- a/lib/ssh/test/ssh_algorithms_SUITE.erl
+++ b/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -58,15 +58,17 @@ groups() ->
|| {Tag,Algs} <- ErlAlgos,
lists:member(Tag,tags())
],
+
+ TypeSSH = ssh_test_lib:ssh_type(),
AlgoTcSet =
- [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos)}
+ [{Alg, [parallel], specific_test_cases(Tag,Alg,SshcAlgos,SshdAlgos,TypeSSH)}
|| {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
Alg <- Algs],
TagGroupSet ++ AlgoTcSet.
-tags() -> [kex,cipher,mac,compression].
+tags() -> [kex,cipher,mac,compression,public_key].
two_way_tags() -> [cipher,mac,compression].
%%--------------------------------------------------------------------
@@ -121,20 +123,40 @@ init_per_group(Group, Config) ->
Tag = proplists:get_value(name,
hd(proplists:get_value(tc_group_path, Config))),
Alg = Group,
- PA =
- case split(Alg) of
- [_] ->
- [Alg];
- [A1,A2] ->
- [{client2server,[A1]},
- {server2client,[A2]}]
- end,
- ct:log("Init tests for tag=~p alg=~p",[Tag,PA]),
- PrefAlgs = {preferred_algorithms,[{Tag,PA}]},
- start_std_daemon([PrefAlgs],
- [{pref_algs,PrefAlgs} | Config])
+ init_per_group(Tag, Alg, Config)
end.
+
+init_per_group(public_key=Tag, Alg, Config) ->
+ ct:log("Init tests for public_key ~p",[Alg]),
+ PrefAlgs = {preferred_algorithms,[{Tag,[Alg]}]},
+ %% Daemon started later in init_per_testcase
+ try
+ setup_pubkey(Alg,
+ [{pref_algs,PrefAlgs},
+ {tag_alg,{Tag,Alg}}
+ | Config])
+ catch
+ _:_ -> {skip, io_lib:format("Unsupported: ~p",[Alg])}
+ end;
+
+init_per_group(Tag, Alg, Config) ->
+ PA =
+ case split(Alg) of
+ [_] ->
+ [Alg];
+ [A1,A2] ->
+ [{client2server,[A1]},
+ {server2client,[A2]}]
+ end,
+ ct:log("Init tests for tag=~p alg=~p",[Tag,PA]),
+ PrefAlgs = {preferred_algorithms,[{Tag,PA}]},
+ start_std_daemon([PrefAlgs],
+ [{pref_algs,PrefAlgs},
+ {tag_alg,{Tag,Alg}}
+ | Config]).
+
+
end_per_group(_Alg, Config) ->
case proplists:get_value(srvr_pid,Config) of
Pid when is_pid(Pid) ->
@@ -146,22 +168,55 @@ end_per_group(_Alg, Config) ->
-init_per_testcase(sshc_simple_exec_os_cmd, Config) ->
- start_pubkey_daemon([proplists:get_value(pref_algs,Config)], Config);
-init_per_testcase(_TC, Config) ->
+init_per_testcase(TC, Config) ->
+ init_per_testcase(TC, proplists:get_value(tag_alg,Config), Config).
+
+
+init_per_testcase(TC, {public_key,Alg}, Config) ->
+ ExtraOpts = case TC of
+ simple_connect ->
+ [{user_dir, proplists:get_value(priv_dir,Config)}];
+ _ ->
+ []
+ end,
+ Opts = pubkey_opts(Config) ++ ExtraOpts,
+ case {ssh_file:user_key(Alg,Opts), ssh_file:host_key(Alg,Opts)} of
+ {{ok,_}, {ok,_}} ->
+ start_pubkey_daemon([proplists:get_value(pref_algs,Config)
+ | ExtraOpts],
+ [{extra_daemon,true}|Config]);
+ {{ok,_}, {error,Err}} ->
+ {skip, io_lib:format("No host key: ~p",[Err])};
+
+ {{error,Err}, {ok,_}} ->
+ {skip, io_lib:format("No user key: ~p",[Err])};
+
+ _ ->
+ {skip, "Neither host nor user key"}
+ end;
+
+init_per_testcase(sshc_simple_exec_os_cmd, _, Config) ->
+ start_pubkey_daemon([proplists:get_value(pref_algs,Config)],
+ [{extra_daemon,true}|Config]);
+
+init_per_testcase(_, _, Config) ->
Config.
-end_per_testcase(sshc_simple_exec_os_cmd, Config) ->
- case proplists:get_value(srvr_pid,Config) of
- Pid when is_pid(Pid) ->
- ssh:stop_daemon(Pid),
- ct:log("stopped ~p",[proplists:get_value(srvr_addr,Config)]);
- _ ->
- ok
- end;
end_per_testcase(_TC, Config) ->
- Config.
+ case proplists:get_value(extra_daemon, Config, false) of
+ true ->
+ case proplists:get_value(srvr_pid,Config) of
+ Pid when is_pid(Pid) ->
+ ssh:stop_daemon(Pid),
+ ct:log("stopped ~p",[proplists:get_value(srvr_addr,Config)]),
+ Config;
+ _ ->
+ Config
+ end;
+ _ ->
+ Config
+ end.
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
@@ -178,40 +233,50 @@ simple_exec(Config) ->
ssh_test_lib:std_simple_exec(Host, Port, Config).
%%--------------------------------------------------------------------
+%% A simple exec call
+simple_connect(Config) ->
+ {Host,Port} = proplists:get_value(srvr_addr, Config),
+ Opts =
+ case proplists:get_value(tag_alg, Config) of
+ {public_key,Alg} -> [{pref_public_key_algs,[Alg]}];
+ _ -> []
+ end,
+ ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
+ ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]),
+ ssh:close(ConnectionRef).
+
+%%--------------------------------------------------------------------
%% Testing if no group matches
simple_exec_groups_no_match_too_small(Config) ->
- try simple_exec_group({400,500,600}, Config)
- of
- _ -> ct:fail("Exec though no group available")
- catch
- error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} ->
- ok
- end.
+ try_exec_simple_group({400,500,600}, Config).
simple_exec_groups_no_match_too_large(Config) ->
- try simple_exec_group({9200,9500,9700}, Config)
+ try_exec_simple_group({9200,9500,9700}, Config).
+
+
+try_exec_simple_group(Group, Config) ->
+ try simple_exec_group(Group, Config)
of
_ -> ct:fail("Exec though no group available")
catch
- error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} ->
- ok
+ error:{badmatch,{error,"No possible diffie-hellman-group-exchange group found"}} -> ok;
+ error:{badmatch,{error,"Connection closed"}} -> ok
end.
%%--------------------------------------------------------------------
%% Testing all default groups
-simple_exec_groups() -> [{timetrap,{minutes,5}}].
-
+simple_exec_groups() ->
+ [{timetrap,{seconds,120}}].
+
simple_exec_groups(Config) ->
Sizes = interpolate( public_key:dh_gex_group_sizes() ),
lists:foreach(
fun(Sz) ->
ct:log("Try size ~p",[Sz]),
ct:comment(Sz),
- case simple_exec_group(Sz, Config) of
- expected -> ct:log("Size ~p ok",[Sz]);
- _ -> ct:log("Size ~p not ok",[Sz])
- end
+ simple_exec_group(Sz, Config),
+ ct:log("Size ~p ok",[Sz])
end, Sizes),
ct:comment("~p",[lists:map(fun({_,I,_}) -> I;
(I) -> I
@@ -236,13 +301,12 @@ sshc_simple_exec_os_cmd(Config) ->
Parent = self(),
Client = spawn(
fun() ->
- Cmd = lists:concat(["ssh -p ",Port,
- " -C"
- " -o UserKnownHostsFile=",KnownHosts,
- " -o StrictHostKeyChecking=no"
- " ",Host," 1+1."]),
- Result = os:cmd(Cmd),
- ct:log("~p~n = ~p",[Cmd, Result]),
+ Result = ssh_test_lib:open_sshc(Host, Port,
+ [" -C"
+ " -o UserKnownHostsFile=",KnownHosts,
+ " -o StrictHostKeyChecking=no"
+ ],
+ " 1+1."),
Parent ! {result, self(), Result, "2"}
end),
receive
@@ -262,9 +326,16 @@ sshc_simple_exec_os_cmd(Config) ->
%%--------------------------------------------------------------------
%% Connect to the ssh server of the OS
-sshd_simple_exec(_Config) ->
+sshd_simple_exec(Config) ->
+ ClientPubKeyOpts =
+ case proplists:get_value(tag_alg,Config) of
+ {public_key,Alg} -> [{pref_public_key_algs,[Alg]}];
+ _ -> []
+ end,
ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ proplists:get_value(pref_algs,Config),
+ {user_interaction, false}
+ | ClientPubKeyOpts]),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"echo testing", infinity),
@@ -319,35 +390,33 @@ concat(A1, A2) -> list_to_atom(lists:concat([A1," + ",A2])).
split(Alg) -> ssh_test_lib:to_atoms(string:tokens(atom_to_list(Alg), " + ")).
-specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos) ->
- [simple_exec, simple_sftp] ++
- case supports(Tag, Alg, SshcAlgos) of
- true ->
- case ssh_test_lib:ssh_type() of
- openSSH ->
- [sshc_simple_exec_os_cmd];
- _ ->
- []
- end;
- false ->
- []
- end ++
- case supports(Tag, Alg, SshdAlgos) of
- true ->
- [sshd_simple_exec];
- _ ->
- []
- end ++
- case {Tag,Alg} of
- {kex,_} when Alg == 'diffie-hellman-group-exchange-sha1' ;
- Alg == 'diffie-hellman-group-exchange-sha256' ->
- [simple_exec_groups,
- simple_exec_groups_no_match_too_large,
- simple_exec_groups_no_match_too_small
- ];
- _ ->
- []
- end.
+specific_test_cases(Tag, Alg, SshcAlgos, SshdAlgos, TypeSSH) ->
+ case Tag of
+ public_key -> [simple_connect];
+ _ -> [simple_connect, simple_exec, simple_sftp]
+ end
+ ++ case supports(Tag, Alg, SshcAlgos) of
+ true when TypeSSH == openSSH ->
+ [sshc_simple_exec_os_cmd];
+ _ ->
+ []
+ end ++
+ case supports(Tag, Alg, SshdAlgos) of
+ true ->
+ [sshd_simple_exec];
+ _ ->
+ []
+ end ++
+ case {Tag,Alg} of
+ {kex,_} when Alg == 'diffie-hellman-group-exchange-sha1' ;
+ Alg == 'diffie-hellman-group-exchange-sha256' ->
+ [simple_exec_groups,
+ simple_exec_groups_no_match_too_large,
+ simple_exec_groups_no_match_too_small
+ ];
+ _ ->
+ []
+ end.
supports(Tag, Alg, Algos) ->
lists:all(fun(A) ->
@@ -377,19 +446,46 @@ start_std_daemon(Opts, Config) ->
ct:log("started ~p:~p ~p",[Host,Port,Opts]),
[{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
+
start_pubkey_daemon(Opts0, Config) ->
- Opts = [{auth_methods,"publickey"}|Opts0],
- {Pid, Host, Port} = ssh_test_lib:std_daemon1(Config, Opts),
- ct:log("started pubkey_daemon ~p:~p ~p",[Host,Port,Opts]),
+ ct:log("starting pubkey_daemon",[]),
+ Opts = pubkey_opts(Config) ++ Opts0,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{failfun, fun ssh_test_lib:failfun/2}
+ | Opts]),
+ ct:log("started ~p:~p ~p",[Host,Port,Opts]),
[{srvr_pid,Pid},{srvr_addr,{Host,Port}} | Config].
+pubkey_opts(Config) ->
+ SystemDir = filename:join(proplists:get_value(priv_dir,Config), "system"),
+ [{auth_methods,"publickey"},
+ {system_dir, SystemDir}].
+
+
setup_pubkey(Config) ->
DataDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, UserDir),
- ssh_test_lib:setup_rsa(DataDir, UserDir),
- ssh_test_lib:setup_ecdsa("256", DataDir, UserDir),
+ Keys =
+ [ssh_test_lib:setup_dsa(DataDir, UserDir),
+ ssh_test_lib:setup_rsa(DataDir, UserDir),
+ ssh_test_lib:setup_ecdsa("256", DataDir, UserDir)
+ ],
+ ssh_test_lib:write_auth_keys(Keys, UserDir), % 'authorized_keys' shall contain ALL pub keys
+ Config.
+
+setup_pubkey(Alg, Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+ ct:log("Setup keys for ~p",[Alg]),
+ case Alg of
+ 'ssh-dss' -> ssh_test_lib:setup_dsa(DataDir, UserDir);
+ 'ssh-rsa' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
+ 'rsa-sha2-256' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
+ 'rsa-sha2-512' -> ssh_test_lib:setup_rsa(DataDir, UserDir);
+ 'ecdsa-sha2-nistp256' -> ssh_test_lib:setup_ecdsa("256", DataDir, UserDir);
+ 'ecdsa-sha2-nistp384' -> ssh_test_lib:setup_ecdsa("384", DataDir, UserDir);
+ 'ecdsa-sha2-nistp521' -> ssh_test_lib:setup_ecdsa("521", DataDir, UserDir)
+ end,
Config.
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384
new file mode 100644
index 0000000000..4c39e916e9
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDAughXu55DNyhxe6x+MNjv4oZKWUDh7bhi4CqjvxhCp9KMpsybltcq+
+lsuKTarzTdKgBwYFK4EEACKhZANiAASu1vvDL0SQoXGtzlltaPHPyDfEVMG/sKLA
+pqv8vfRN5Wcs7+yaRKw92nYEKGXfZLbhVX8ArFPMtXPWHcRHCntvL1Acn2kJQ8Gc
+7iL4NAr8JhTIUBv4YMhHDa9Pv/CH2zk=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384.pub
new file mode 100644
index 0000000000..caa9604c84
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBK7W+8MvRJChca3OWW1o8c/IN8RUwb+wosCmq/y99E3lZyzv7JpErD3adgQoZd9ktuFVfwCsU8y1c9YdxEcKe28vUByfaQlDwZzuIvg0CvwmFMhQG/hgyEcNr0+/8IfbOQ== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521
new file mode 100644
index 0000000000..1e16fcbd57
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEEWXGoVLiNwQVUwAGZWxOu6uxtU8ntxyZNlcWU4Z8pze9kq3eK7a9XH
+l/wxL75Vk1QdOiR/rE3s/L/zOuChp44o1aAHBgUrgQQAI6GBiQOBhgAEAfCrtwjO
+kQYKr4/F3uanS7Eby1+SYDdRl1ABuDFhNC3CivVBFt4CnRneV+Mf0viDAxD+HEpd
+/GaE2CdsFoVpglN5AVG+fEePY2PiCLHmjc4/pBuR+tWhErzcWAd0KLBCBuc4OAvl
+aLLYV1NAJI6COnnfGTCVvYYE5nKMG4LLX0zaWtWl
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521.pub
new file mode 100644
index 0000000000..069683eba7
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/id_ecdsa521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHwq7cIzpEGCq+Pxd7mp0uxG8tfkmA3UZdQAbgxYTQtwor1QRbeAp0Z3lfjH9L4gwMQ/hxKXfxmhNgnbBaFaYJTeQFRvnxHj2Nj4gix5o3OP6QbkfrVoRK83FgHdCiwQgbnODgL5Wiy2FdTQCSOgjp53xkwlb2GBOZyjBuCy19M2lrVpQ== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384
new file mode 100644
index 0000000000..5835bcd74c
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDB+l0+SMLYgQ3ZRzg2Pn5u+1ZwKbEnJzXsTKTJM9QSJbKkbA7uCnjdS
+CvEW+66CoHqgBwYFK4EEACKhZANiAAT6awCCIrcCr9H4wq0bJ/rQou3tpLHyyf33
+c8D6FPn48/hNqinpx7b0le/0D+Rrhdl9edIplAf6oki7yoFFGl4yuzWtv7rag9jB
+vv6w1508ChOmyQ094rFt/xj4KVBhEHI=
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384.pub
new file mode 100644
index 0000000000..714fc4eb89
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBPprAIIitwKv0fjCrRsn+tCi7e2ksfLJ/fdzwPoU+fjz+E2qKenHtvSV7/QP5GuF2X150imUB/qiSLvKgUUaXjK7Na2/utqD2MG+/rDXnTwKE6bJDT3isW3/GPgpUGEQcg== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521
new file mode 100644
index 0000000000..81aa8df39f
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHbAgEBBEHHxgYEfDclsu5bW+pZfg+bkaqWpgEpXtuzLVm++FFPjhAPhMkurSRj
+WQ+CuI2TxgYkBbYFNjn9JqgdMF7FzaiojKAHBgUrgQQAI6GBiQOBhgAEAFTM8TKG
+xexxmfAGuyl/Tpk4wytB/OyuVfkF+Q3H1v17HLcpMacA5xUFr80+D5XnjxGttBsS
++X0uexR7QbPbhhPqADgQzFqvTsB1mUNAZnJBD6QNCZkfWwRRwFYQWSmisb43H6G3
+iUTKqiCXMXO8drKLA+Wi+L7VyfoI1CvatBBlDHbV
+-----END EC PRIVATE KEY-----
diff --git a/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521.pub
new file mode 100644
index 0000000000..17b9a1d834
--- /dev/null
+++ b/lib/ssh/test/ssh_algorithms_SUITE_data/ssh_host_ecdsa_key521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBABUzPEyhsXscZnwBrspf06ZOMMrQfzsrlX5BfkNx9b9exy3KTGnAOcVBa/NPg+V548RrbQbEvl9LnsUe0Gz24YT6gA4EMxar07AdZlDQGZyQQ+kDQmZH1sEUcBWEFkporG+Nx+ht4lEyqoglzFzvHayiwPlovi+1cn6CNQr2rQQZQx21Q== uabhnil@elxadlj3q32
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index 733414e23a..62e2a585e4 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -46,11 +46,17 @@
exec_key_differs2/1,
exec_key_differs3/1,
exec_key_differs_fail/1,
- idle_time/1,
+ idle_time_client/1,
+ idle_time_server/1,
inet6_option/1,
inet_option/1,
internal_error/1,
- known_hosts/1,
+ known_hosts/1,
+ login_bad_pwd_no_retry1/1,
+ login_bad_pwd_no_retry2/1,
+ login_bad_pwd_no_retry3/1,
+ login_bad_pwd_no_retry4/1,
+ login_bad_pwd_no_retry5/1,
misc_ssh_options/1,
openssh_zlib_basic_test/1,
packet_size_zero/1,
@@ -62,7 +68,8 @@
shell_unicode_string/1,
ssh_info_print/1,
key_callback/1,
- key_callback_options/1
+ key_callback_options/1,
+ shell_exit_status/1
]).
%%% Common test callbacks
@@ -100,7 +107,9 @@ all() ->
daemon_opt_fd,
multi_daemon_opt_fd,
packet_size_zero,
- ssh_info_print
+ ssh_info_print,
+ {group, login_bad_pwd_no_retry},
+ shell_exit_status
].
groups() ->
@@ -116,7 +125,13 @@ groups() ->
{dsa_pass_key, [], [pass_phrase]},
{rsa_pass_key, [], [pass_phrase]},
{key_cb, [], [key_callback, key_callback_options]},
- {internal_error, [], [internal_error]}
+ {internal_error, [], [internal_error]},
+ {login_bad_pwd_no_retry, [], [login_bad_pwd_no_retry1,
+ login_bad_pwd_no_retry2,
+ login_bad_pwd_no_retry3,
+ login_bad_pwd_no_retry4,
+ login_bad_pwd_no_retry5
+ ]}
].
@@ -125,7 +140,7 @@ basic_tests() ->
exec, exec_compressed,
shell, shell_no_unicode, shell_unicode_string,
cli, known_hosts,
- idle_time, openssh_zlib_basic_test,
+ idle_time_client, idle_time_server, openssh_zlib_basic_test,
misc_ssh_options, inet_option, inet6_option].
@@ -138,15 +153,27 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(dsa_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- Config;
+ case lists:member('ssh-dss',
+ ssh_transport:default_algorithms(public_key)) of
+ true ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ Config;
+ false ->
+ {skip, unsupported_pub_key}
+ end;
init_per_group(rsa_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa(DataDir, PrivDir),
- Config;
+ case lists:member('ssh-rsa',
+ ssh_transport:default_algorithms(public_key)) of
+ true ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ Config;
+ false ->
+ {skip, unsupported_pub_key}
+ end;
init_per_group(ecdsa_sha2_nistp256_key, Config) ->
case lists:member('ecdsa-sha2-nistp256',
ssh_transport:default_algorithms(public_key)) of
@@ -181,15 +208,27 @@ init_per_group(ecdsa_sha2_nistp521_key, Config) ->
{skip, unsupported_pub_key}
end;
init_per_group(rsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_rsa_pass_pharse(DataDir, PrivDir, "Password"),
- [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
+ case lists:member('ssh-rsa',
+ ssh_transport:default_algorithms(public_key)) of
+ true ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_rsa_pass_pharse(DataDir, PrivDir, "Password"),
+ [{pass_phrase, {rsa_pass_phrase, "Password"}}| Config];
+ false ->
+ {skip, unsupported_pub_key}
+ end;
init_per_group(dsa_pass_key, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa_pass_pharse(DataDir, PrivDir, "Password"),
- [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
+ case lists:member('ssh-dss',
+ ssh_transport:default_algorithms(public_key)) of
+ true ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_dsa_pass_pharse(DataDir, PrivDir, "Password"),
+ [{pass_phrase, {dsa_pass_phrase, "Password"}}| Config];
+ false ->
+ {skip, unsupported_pub_key}
+ end;
init_per_group(host_user_key_differs, Config) ->
Data = proplists:get_value(data_dir, Config),
Sys = filename:join(proplists:get_value(priv_dir, Config), system_rsa),
@@ -206,10 +245,16 @@ init_per_group(host_user_key_differs, Config) ->
ssh_test_lib:setup_rsa_known_host(Sys, Usr),
Config;
init_per_group(key_cb, Config) ->
- DataDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, PrivDir),
- Config;
+ case lists:member('ssh-rsa',
+ ssh_transport:default_algorithms(public_key)) of
+ true ->
+ DataDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_rsa(DataDir, PrivDir),
+ Config;
+ false ->
+ {skip, unsupported_pub_key}
+ end;
init_per_group(internal_error, Config) ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
@@ -279,7 +324,7 @@ end_per_group(rsa_pass_key, Config) ->
Config;
end_per_group(key_cb, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:clean_dsa(PrivDir),
+ ssh_test_lib:clean_rsa(PrivDir),
Config;
end_per_group(internal_error, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -301,9 +346,9 @@ init_per_testcase(TC, Config) when TC==shell_no_unicode ;
{user_passwords, [{"foo", "bar"}]}]),
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, UserDir,
- [{silently_accept_hosts, true},
- {user,"foo"},{password,"bar"}]),
+ Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
+ {silently_accept_hosts, true},
+ {user,"foo"},{password,"bar"}]),
ct:log("IO=~p, Shell=~p, self()=~p",[IO,Shell,self()]),
ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p",
[file:native_name_encoding(),io:getopts()]),
@@ -329,14 +374,15 @@ end_per_testcase(TC, Config) when TC==shell_no_unicode ;
TC==shell_unicode_string ->
case proplists:get_value(sftpd, Config) of
{Pid, _, _} ->
- ssh:stop_daemon(Pid),
- ssh:stop();
+ catch ssh:stop_daemon(Pid);
_ ->
- ssh:stop()
- end;
+ ok
+ end,
+ end_per_testcase(Config);
end_per_testcase(_TestCase, Config) ->
end_per_testcase(Config).
-end_per_testcase(_Config) ->
+
+end_per_testcase(_Config) ->
ssh:stop(),
ok.
@@ -477,8 +523,8 @@ exec_compressed(Config) when is_list(Config) ->
end.
%%--------------------------------------------------------------------
-%%% Idle timeout test
-idle_time(Config) ->
+%%% Idle timeout test, client
+idle_time_client(Config) ->
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
@@ -499,6 +545,28 @@ idle_time(Config) ->
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+%%% Idle timeout test, server
+idle_time_server(Config) ->
+ SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {idle_time, 2000},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ {ok, Id} = ssh_connection:session_channel(ConnectionRef, 1000),
+ ssh_connection:close(ConnectionRef, Id),
+ receive
+ after 10000 ->
+ {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000)
+ end,
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
%%% Test that ssh:shell/2 works
shell(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -510,7 +578,7 @@ shell(Config) when is_list(Config) ->
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
+ Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]),
receive
{'EXIT', _, _} ->
ct:fail(no_ssh_connection);
@@ -544,14 +612,14 @@ exec_key_differs(Config, UserPKAlgs) ->
{_Pid, _Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, SystemUserDir},
{preferred_algorithms,
- [{public_key,['ssh-rsa']}]}]),
+ [{public_key,['ssh-rsa'|UserPKAlgs]}]}]),
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, UserDir,
- [{preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,UserPKAlgs}
- ]),
+ Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
+ {preferred_algorithms,[{public_key,['ssh-rsa']}]},
+ {pref_public_key_algs,UserPKAlgs}
+ ]),
receive
@@ -582,9 +650,10 @@ exec_key_differs_fail(Config) when is_list(Config) ->
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- ssh_test_lib:start_shell(Port, IO, UserDir,
- [{preferred_algorithms,[{public_key,['ssh-rsa']}]},
- {pref_public_key_algs,['ssh-dss']}]),
+ ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir},
+ {recv_ext_info, false},
+ {preferred_algorithms,[{public_key,['ssh-rsa']}]},
+ {pref_public_key_algs,['ssh-dss']}]),
receive
{'EXIT', _, _} ->
ok;
@@ -674,7 +743,8 @@ known_hosts(Config) when is_list(Config) ->
Lines = string:tokens(binary_to_list(Binary), "\n"),
[Line] = Lines,
[HostAndIp, Alg, _KeyData] = string:tokens(Line, " "),
- [Host, _Ip] = string:tokens(HostAndIp, ","),
+ [StoredHost, _Ip] = string:tokens(HostAndIp, ","),
+ true = ssh_test_lib:match_ip(StoredHost, Host),
"ssh-" ++ _ = Alg,
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
@@ -735,7 +805,7 @@ key_callback_options(Config) when is_list(Config) ->
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
- {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_dsa")),
+ {ok, PrivKey} = file:read_file(filename:join(UserDir, "id_rsa")),
ConnectOpts = [{silently_accept_hosts, true},
{user_dir, NoPubKeyDir},
@@ -1090,6 +1160,96 @@ ssh_info_print(Config) ->
%%--------------------------------------------------------------------
+%% Check that a basd pwd is not tried more times. Could cause lock-out
+%% on server
+
+login_bad_pwd_no_retry1(Config) ->
+ login_bad_pwd_no_retry(Config, "keyboard-interactive,password").
+
+login_bad_pwd_no_retry2(Config) ->
+ login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
+
+login_bad_pwd_no_retry3(Config) ->
+ login_bad_pwd_no_retry(Config, "password,publickey,keyboard-interactive").
+
+login_bad_pwd_no_retry4(Config) ->
+ login_bad_pwd_no_retry(Config, "password,keyboard-interactive").
+
+login_bad_pwd_no_retry5(Config) ->
+ login_bad_pwd_no_retry(Config, "password,keyboard-interactive,password,password").
+
+
+login_bad_pwd_no_retry(Config, AuthMethods) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(data_dir, Config),
+
+ Parent = self(),
+ PwdFun = fun(_, _, _, undefined) -> {false, 1};
+ (_, _, _, _) -> Parent ! retry_bad_pwd,
+ false
+ end,
+
+ {DaemonRef, _Host, Port} =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {auth_methods, AuthMethods},
+ {user_passwords, [{"foo","somepwd"}]},
+ {pwdfun, PwdFun}
+ ]),
+
+ ConnRes = ssh:connect("localhost", Port,
+ [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "badpwd"},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+
+ receive
+ retry_bad_pwd ->
+ ssh:stop_daemon(DaemonRef),
+ {fail, "Retry bad password"}
+ after 0 ->
+ case ConnRes of
+ {error,"Unable to connect using the available authentication methods"} ->
+ ssh:stop_daemon(DaemonRef),
+ ok;
+ {ok,Conn} ->
+ ssh:close(Conn),
+ ssh:stop_daemon(DaemonRef),
+ {fail, "Connect erroneosly succeded"}
+ end
+ end.
+
+
+%%----------------------------------------------------------------------------
+%%% Test that when shell REPL exit with reason normal client receives status 0
+shell_exit_status(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(priv_dir, Config),
+
+ ShellFun = fun (_User) -> spawn(fun() -> ok end) end,
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {user_passwords, [{"vego", "morot"}]},
+ {shell, ShellFun},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user, "vego"},
+ {password, "morot"},
+ {user_interaction, false}]),
+
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ok = ssh_connection:shell(ConnectionRef, ChannelId),
+ ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
+ ssh:stop_daemon(Pid).
+
+
+%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
%% Due to timing the error message may or may not be delivered to
@@ -1098,7 +1258,7 @@ check_error("Invalid state") ->
ok;
check_error("Connection closed") ->
ok;
-check_error("Selection of key exchange algorithm failed") ->
+check_error("Selection of key exchange algorithm failed"++_) ->
ok;
check_error(Error) ->
ct:fail(Error).
@@ -1204,13 +1364,25 @@ new_do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
ct:log("Skip newline ~p",[_X]),
new_do_shell(IO, N, Ops);
- <<Pfx:PfxSize/binary,P1,"> ">> when (P1-$0)==N ->
+ <<P1,"> ">> when (P1-$0)==N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,"> ">> when (P1-$0)==N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,"> ">> when (P1-$0)==N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
- <<Pfx:PfxSize/binary,P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ <<P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
- <<Pfx:PfxSize/binary,P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ <<P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"(",Pfx:PfxSize/binary,")",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
+ new_do_shell_prompt(IO, N, Order, Arg, More);
+ <<"('",Pfx:PfxSize/binary,"')",P1,P2,P3,"> ">> when (P1-$0)*100 + (P2-$0)*10 + (P3-$0) == N ->
new_do_shell_prompt(IO, N, Order, Arg, More);
Err when element(1,Err)==error ->
@@ -1246,7 +1418,7 @@ prompt_prefix() ->
case node() of
nonode@nohost -> <<>>;
Node -> list_to_binary(
- lists:concat(["(",Node,")"]))
+ atom_to_list(Node))
end.
diff --git a/lib/ssh/test/ssh_bench.spec b/lib/ssh/test/ssh_bench.spec
index 029f0bd074..b0b64713cf 100644
--- a/lib/ssh/test/ssh_bench.spec
+++ b/lib/ssh/test/ssh_bench.spec
@@ -1 +1,2 @@
-{suites,"../ssh_test",[ssh_benchmark_SUITE]}.
+{suites,"../ssh_test",[ssh_bench_SUITE
+ ]}.
diff --git a/lib/ssh/test/ssh_bench_SUITE.erl b/lib/ssh/test/ssh_bench_SUITE.erl
new file mode 100644
index 0000000000..2c0cd8fc8e
--- /dev/null
+++ b/lib/ssh/test/ssh_bench_SUITE.erl
@@ -0,0 +1,252 @@
+%%%-------------------------------------------------------------------
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(ssh_bench_SUITE).
+-compile(export_all).
+
+-include_lib("common_test/include/ct_event.hrl").
+-include_lib("common_test/include/ct.hrl").
+
+-include_lib("ssh/src/ssh.hrl").
+-include_lib("ssh/src/ssh_transport.hrl").
+-include_lib("ssh/src/ssh_connect.hrl").
+-include_lib("ssh/src/ssh_userauth.hrl").
+
+%%%================================================================
+%%%
+%%% Suite declarations
+%%%
+
+suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]},
+ {timetrap,{minutes,1}}
+ ].
+all() -> [connect,
+ transfer_text
+ ].
+
+-define(UID, "foo").
+-define(PWD, "bar").
+-define(Nruns, 8).
+
+%%%================================================================
+%%%
+%%% Init per suite
+%%%
+
+init_per_suite(Config) ->
+ catch ssh:stop(),
+ try
+ ok = ssh:start()
+ of
+ ok ->
+ DataSize = 1000000,
+ SystemDir = proplists:get_value(data_dir, Config),
+ Algs = insert_none(ssh:default_algorithms()),
+ {_ServerPid, _Host, Port} =
+ ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_passwords, [{?UID,?PWD}]},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {preferred_algorithms, Algs},
+ {max_random_length_padding, 0},
+ {subsystems, [{"/dev/null", {ssh_bench_dev_null,[DataSize]}}]}
+ ]),
+ [{host,"localhost"}, {port,Port}, {uid,?UID}, {pwd,?PWD}, {data_size,DataSize} | Config]
+ catch
+ C:E ->
+ {skip, io_lib:format("Couldn't start ~p:~p",[C,E])}
+ end.
+
+end_per_suite(_Config) ->
+ catch ssh:stop(),
+ ok.
+
+%%%================================================================
+%%%
+%%% Init per testcase
+%%%
+
+init_per_testcase(_Func, Conf) ->
+ Conf.
+
+end_per_testcase(_Func, _Conf) ->
+ ok.
+
+%%%================================================================
+%%%
+%%% Testcases
+%%%
+
+%%%----------------------------------------------------------------
+%%% Measure the time for an Erlang client to connect to an Erlang
+%%% server on the localhost
+
+connect(Config) ->
+ KexAlgs = proplists:get_value(kex, ssh:default_algorithms()),
+ ct:log("KexAlgs = ~p",[KexAlgs]),
+ lists:foreach(
+ fun(KexAlg) ->
+ PrefAlgs = preferred_algorithms(KexAlg),
+ report([{value, measure_connect(Config,
+ [{preferred_algorithms,PrefAlgs}])},
+ {suite, ?MODULE},
+ {name, mk_name(["Connect erlc erld ",KexAlg," [µs]"])}
+ ])
+ end, KexAlgs).
+
+
+measure_connect(Config, Opts) ->
+ Port = proplists:get_value(port, Config),
+ ConnectOptions = [{user, proplists:get_value(uid, Config)},
+ {password, proplists:get_value(pwd, Config)},
+ {user_dir, proplists:get_value(priv_dir, Config)},
+ {silently_accept_hosts, true},
+ {user_interaction, false},
+ {max_random_length_padding, 0}
+ ] ++ Opts,
+ median(
+ [begin
+ {Time, {ok,Pid}} = timer:tc(ssh,connect,["localhost", Port, ConnectOptions]),
+ ssh:close(Pid),
+ Time
+ end || _ <- lists:seq(1,?Nruns)]).
+
+%%%----------------------------------------------------------------
+%%% Measure the time to transfer a set of data with
+%%% and without crypto
+
+transfer_text(Config) ->
+ Port = proplists:get_value(port, Config),
+ Options = [{user, proplists:get_value(uid, Config)},
+ {password, proplists:get_value(pwd, Config)},
+ {user_dir, proplists:get_value(priv_dir, Config)},
+ {silently_accept_hosts, true},
+ {user_interaction, false},
+ {max_random_length_padding, 0}
+ ],
+ Data = gen_data(proplists:get_value(data_size,Config)),
+
+ [connect_measure(Port, Crypto, Mac, Data, Options)
+ || {Crypto,Mac} <- [{ none, none},
+ {'aes128-ctr', 'hmac-sha1'},
+ {'aes256-ctr', 'hmac-sha1'},
+%% {'[email protected]', 'hmac-sha1'},
+ {'aes128-cbc', 'hmac-sha1'},
+ {'3des-cbc', 'hmac-sha1'},
+ {'aes128-ctr', 'hmac-sha2-256'},
+ {'aes128-ctr', 'hmac-sha2-512'}
+ ],
+ crypto_mac_supported(Crypto,Mac)].
+
+
+crypto_mac_supported(none, none) ->
+ true;
+crypto_mac_supported(C, M) ->
+ Algs = ssh:default_algorithms(),
+ [{_,Cs},_] = proplists:get_value(cipher, Algs),
+ [{_,Ms},_] = proplists:get_value(mac, Algs),
+ lists:member(C,Cs) andalso lists:member(M,Ms).
+
+
+gen_data(DataSz) ->
+ Data0 = << <<C>> || _ <- lists:seq(1,DataSz div 256),
+ C <- lists:seq(0,255) >>,
+ Data1 = << <<C>> || C <- lists:seq(0,(DataSz rem 256) - 1) >>,
+ <<Data0/binary, Data1/binary>>.
+
+
+%% connect_measure(Port, Cipher, Mac, Data, Options) ->
+%% report([{value, 1},
+%% {suite, ?MODULE},
+%% {name, mk_name(["Transfer 1M bytes ",Cipher,"/",Mac," [µs]"])}]);
+connect_measure(Port, Cipher, Mac, Data, Options) ->
+ Times =
+ [begin
+ {ok,C} = ssh:connect("localhost", Port, [{preferred_algorithms, [{cipher,[Cipher]},
+ {mac,[Mac]}]}
+ |Options]),
+ {ok,Ch} = ssh_connection:session_channel(C, 10000),
+ success = ssh_connection:subsystem(C, Ch, "/dev/null", 10000),
+ {Time,ok} = timer:tc(?MODULE, send_wait_acc, [C, Ch, Data]),
+ ok = ssh_connection:send_eof(C, Ch),
+ ssh:close(C),
+ Time
+ end || _ <- lists:seq(1,?Nruns)],
+
+ report([{value, median(Times)},
+ {suite, ?MODULE},
+ {name, mk_name(["Transfer 1M bytes ",Cipher,"/",Mac," [µs]"])}]).
+
+send_wait_acc(C, Ch, Data) ->
+ ssh_connection:send(C, Ch, Data),
+ receive
+ {ssh_cm, C, {data, Ch, 0, <<"READY">>}} -> ok
+ end.
+
+
+%%%================================================================
+%%%
+%%% Private
+%%%
+
+%%%----------------------------------------------------------------
+insert_none(L) ->
+ lists:foldl(fun insert_none/2, [], L).
+
+insert_none({T,L}, Acc) when T==cipher ;
+ T==mac ->
+ [{T, [{T1,L1++[none]} || {T1,L1} <- L]} | Acc];
+insert_none(_, Acc) ->
+ Acc.
+
+%%%----------------------------------------------------------------
+mk_name(Name) -> [char(C) || C <- lists:concat(Name)].
+
+char($-) -> $_;
+char(C) -> C.
+
+%%%----------------------------------------------------------------
+preferred_algorithms(KexAlg) ->
+ [{kex, [KexAlg]},
+ {public_key, ['ssh-rsa']},
+ {cipher, ['aes128-ctr']},
+ {mac, ['hmac-sha1']},
+ {compression, [none]}
+ ].
+
+%%%----------------------------------------------------------------
+median(Data) when is_list(Data) ->
+ SortedData = lists:sort(Data),
+ N = length(Data),
+ Median =
+ case N rem 2 of
+ 0 ->
+ MeanOfMiddle = (lists:nth(N div 2, SortedData) +
+ lists:nth(N div 2 + 1, SortedData)) / 2,
+ round(MeanOfMiddle);
+ 1 ->
+ lists:nth(N div 2 + 1, SortedData)
+ end,
+ ct:log("median(~p) = ~p",[SortedData,Median]),
+ Median.
+
+
+report(Data) ->
+ ct:log("EventData = ~p",[Data]),
+ ct_event:notify(#event{name = benchmark_data,
+ data = Data}).
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_dsa b/lib/ssh/test/ssh_bench_SUITE_data/id_dsa
index d306f8b26e..d306f8b26e 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_dsa
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_dsa
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256 b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256
index 4b1eb12eaa..4b1eb12eaa 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256.pub b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256.pub
index a0147e60fa..a0147e60fa 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa256.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa256.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384 b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384
index 4e8aa40959..4e8aa40959 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384.pub b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384.pub
index 41e722e545..41e722e545 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa384.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa384.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521 b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521
index 7196f46e97..7196f46e97 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521.pub b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521.pub
index 8f059120bc..8f059120bc 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_ecdsa521.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_ecdsa521.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/id_rsa b/lib/ssh/test/ssh_bench_SUITE_data/id_rsa
index 9d7e0dd5fb..9d7e0dd5fb 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/id_rsa
+++ b/lib/ssh/test/ssh_bench_SUITE_data/id_rsa
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key
index 51ab6fbd88..51ab6fbd88 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key.pub
index 4dbb1305b0..4dbb1305b0 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_dsa_key.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_dsa_key.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256 b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256
index 2979ea88ed..2979ea88ed 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256.pub b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256.pub
index 85dc419345..85dc419345 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key256.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key256.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384 b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384
index fb1a862ded..fb1a862ded 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384.pub b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384.pub
index 428d5fb7d7..428d5fb7d7 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key384.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key384.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521 b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521
index 3e51ec2ecd..3e51ec2ecd 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521.pub b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521.pub
index 017a29f4da..017a29f4da 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_ecdsa_key521.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_ecdsa_key521.pub
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key
index 79968bdd7d..79968bdd7d 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key
diff --git a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key.pub
index 75d2025c71..75d2025c71 100644
--- a/lib/ssh/test/ssh_benchmark_SUITE_data/ssh_host_rsa_key.pub
+++ b/lib/ssh/test/ssh_bench_SUITE_data/ssh_host_rsa_key.pub
diff --git a/lib/ssh/test/ssh_bench_dev_null.erl b/lib/ssh/test/ssh_bench_dev_null.erl
new file mode 100644
index 0000000000..5166247714
--- /dev/null
+++ b/lib/ssh/test/ssh_bench_dev_null.erl
@@ -0,0 +1,58 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+
+%%% Description: Example ssh server
+-module(ssh_bench_dev_null).
+-behaviour(ssh_daemon_channel).
+
+-record(state, {
+ cm,
+ chid,
+ n,
+ sum = 0
+ }).
+
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+init([N]) -> {ok, #state{n=N}}.
+
+handle_msg({ssh_channel_up, ChId, CM}, S) ->
+ {ok, S#state{cm = CM,
+ chid = ChId}}.
+
+
+
+handle_ssh_msg({ssh_cm, CM, {data,ChId,0,Data}}, #state{n=N, sum=Sum0, cm=CM, chid=ChId} = S) ->
+ Sum = Sum0 + size(Data),
+ if Sum == N ->
+ %% Got all
+ ssh_connection:send(CM, ChId, <<"READY">>),
+ {ok, S#state{sum=Sum}};
+ Sum < N ->
+ %% Expects more
+ {ok, S#state{sum=Sum}}
+ end;
+handle_ssh_msg({ssh_cm, _, {exit_signal,ChId,_,_,_}}, S) -> {stop, ChId, S};
+handle_ssh_msg({ssh_cm, _, {exit_status,ChId,_} }, S) -> {stop, ChId, S};
+handle_ssh_msg({ssh_cm, _, _ }, S) -> {ok, S}.
+
+terminate(_, _) -> ok.
diff --git a/lib/ssh/test/ssh_benchmark_SUITE.erl b/lib/ssh/test/ssh_benchmark_SUITE.erl
deleted file mode 100644
index c2bfc48449..0000000000
--- a/lib/ssh/test/ssh_benchmark_SUITE.erl
+++ /dev/null
@@ -1,563 +0,0 @@
-%%%-------------------------------------------------------------------
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2015-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
--module(ssh_benchmark_SUITE).
--compile(export_all).
-
--include_lib("common_test/include/ct_event.hrl").
--include_lib("common_test/include/ct.hrl").
-
--include_lib("ssh/src/ssh.hrl").
--include_lib("ssh/src/ssh_transport.hrl").
--include_lib("ssh/src/ssh_connect.hrl").
--include_lib("ssh/src/ssh_userauth.hrl").
-
-
-suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]},
- {timetrap,{minutes,3}}
- ].
-%%suite() -> [{ct_hooks,[ts_install_cth]}].
-
-all() -> [{group, opensshc_erld}
-%% {group, erlc_opensshd}
- ].
-
-groups() ->
- [{opensshc_erld, [{repeat, 3}], [openssh_client_shell,
- openssh_client_sftp]}
- ].
-
-
-init_per_suite(Config) ->
- catch ssh:stop(),
- try
- report_client_algorithms(),
- ok = ssh:start(),
- {ok,TracerPid} = erlang_trace(),
- [{tracer_pid,TracerPid} | init_sftp_dirs(Config)]
- catch
- C:E ->
- {skip, io_lib:format("Couldn't start ~p:~p",[C,E])}
- end.
-
-end_per_suite(_Config) ->
- catch ssh:stop(),
- ok.
-
-
-
-init_per_group(opensshc_erld, Config) ->
- case ssh_test_lib:ssh_type() of
- openSSH ->
- DataDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
- ssh_test_lib:setup_dsa(DataDir, UserDir),
- ssh_test_lib:setup_rsa(DataDir, UserDir),
- ssh_test_lib:setup_ecdsa("256", DataDir, UserDir),
- Common = ssh_test_lib:intersect_bi_dir(
- ssh_test_lib:intersection(ssh:default_algorithms(),
- ssh_test_lib:default_algorithms(sshc))),
- [{c_kexs, ssh_test_lib:sshc(kex)},
- {c_ciphers, ssh_test_lib:sshc(cipher)},
- {common_algs, Common}
- | Config];
- _ ->
- {skip, "No OpenSsh client found"}
- end;
-
-init_per_group(erlc_opensshd, _) ->
- {skip, "Group erlc_opensshd not implemented"};
-
-init_per_group(_GroupName, Config) ->
- Config.
-
-end_per_group(_GroupName, _Config) ->
- ok.
-
-
-init_per_testcase(_Func, Conf) ->
- Conf.
-
-end_per_testcase(_Func, _Conf) ->
- ok.
-
-
-init_sftp_dirs(Config) ->
- UserDir = proplists:get_value(priv_dir, Config),
- SrcDir = filename:join(UserDir, "sftp_src"),
- ok = file:make_dir(SrcDir),
- SrcFile = "big_data",
- DstDir = filename:join(UserDir, "sftp_dst"),
- ok = file:make_dir(DstDir),
- N = 100 * 1024*1024,
- ok = file:write_file(filename:join(SrcDir,SrcFile), crypto:strong_rand_bytes(N)),
- [{sftp_src_dir,SrcDir}, {sftp_dst_dir,DstDir}, {src_file,SrcFile}, {sftp_size,N}
- | Config].
-
-%%%================================================================
-openssh_client_shell(Config) ->
- lists:foreach(
- fun(PrefAlgs=[{kex,[Kex]}]) when Kex == 'diffie-hellman-group-exchange-sha256' ->
- lists:foreach(
- fun(Grp) ->
- openssh_client_shell(Config,
- [{preferred_algorithms, PrefAlgs},
- {dh_gex_groups, [Grp]}
- ])
- end, moduli());
- (PrefAlgs) ->
- openssh_client_shell(Config,
- [{preferred_algorithms, PrefAlgs}])
- end, variants(kex,Config) ++ variants(cipher,Config)
- ).
-
-
-openssh_client_shell(Config, Options) ->
- SystemDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(UserDir, "known_hosts"),
-
- {ok, TracerPid} = erlang_trace(),
- {ServerPid, _Host, Port} =
- ssh_test_lib:daemon([{system_dir, SystemDir},
- {public_key_alg, ssh_dsa},
- {failfun, fun ssh_test_lib:failfun/2} |
- Options]),
- ct:sleep(500),
-
- Data = lists:duplicate(100000, $a),
- Cmd = lists:concat(["ssh -p ",Port,
- " -o UserKnownHostsFile=", KnownHosts,
- " -o \"StrictHostKeyChecking no\"",
- " localhost '\"",Data,"\"'."]),
-%% ct:pal("Cmd ="++Cmd),
-
- Parent = self(),
- SlavePid = spawn(fun() ->
- Parent ! {self(),os:cmd(Cmd)}
- end),
- receive
- {SlavePid, _ClientResponse} ->
-%% ct:pal("ClientResponse = ~p",[_ClientResponse]),
- {ok, List} = get_trace_list(TracerPid),
- Times = find_times(List, [accept_to_hello, kex, kex_to_auth, auth, to_prompt]),
- Algs = find_algs(List),
- ct:pal("Algorithms = ~p~n~nTimes = ~p",[Algs,Times]),
- lists:foreach(
- fun({Tag,Value,Unit}) ->
- EventData =
- case Tag of
- {A,B} when A==encrypt ; A==decrypt ->
- [{value, Value},
- {suite, ?MODULE},
- {name, mk_name(["Cipher ",A," ",B," [",Unit,"]"])}
- ];
- kex ->
- KexAlgStr = fmt_alg(Algs#alg.kex, List),
- [{value, Value},
- {suite, ?MODULE},
- {name, mk_name(["Erl server kex ",KexAlgStr," [",Unit,"]"])}
- ];
- _ when is_atom(Tag) ->
- [{value, Value},
- {suite, ?MODULE},
- {name, mk_name(["Erl server ",Tag," [",Unit,"]"])}
- ]
- end,
- ct:pal("ct_event:notify ~p",[EventData]),
- ct_event:notify(#event{name = benchmark_data,
- data = EventData})
- end, Times),
- ssh:stop_daemon(ServerPid),
- ok
- after 60*1000 ->
- ssh:stop_daemon(ServerPid),
- exit(SlavePid, kill),
- {fail, timeout}
- end.
-
-
-%%%================================================================
-openssh_client_sftp(Config) ->
- lists:foreach(
- fun(PrefAlgs) ->
- openssh_client_sftp(Config, [{preferred_algorithms,PrefAlgs}])
- end, variants(cipher,Config)).
-
-
-openssh_client_sftp(Config, Options) ->
- SystemDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
- SftpSrcDir = proplists:get_value(sftp_src_dir, Config),
- SrcFile = proplists:get_value(src_file, Config),
- SrcSize = proplists:get_value(sftp_size, Config),
- KnownHosts = filename:join(UserDir, "known_hosts"),
-
- {ok, TracerPid} = erlang_trace(),
- {ServerPid, _Host, Port} =
- ssh_test_lib:daemon([{system_dir, SystemDir},
- {public_key_alg, ssh_dsa},
- {subsystems,[ssh_sftpd:subsystem_spec([%{cwd, SftpSrcDir},
- {root, SftpSrcDir}])]},
- {failfun, fun ssh_test_lib:failfun/2}
- | Options]),
- ct:pal("ServerPid = ~p",[ServerPid]),
- ct:sleep(500),
- Cmd = lists:concat(["sftp",
- " -b -",
- " -P ",Port,
- " -o UserKnownHostsFile=", KnownHosts,
- " -o \"StrictHostKeyChecking no\"",
- " localhost:",SrcFile
- ]),
-%% ct:pal("Cmd = ~p",[Cmd]),
-
- Parent = self(),
- SlavePid = spawn(fun() ->
- Parent ! {self(),os:cmd(Cmd)}
- end),
- receive
- {SlavePid, _ClientResponse} ->
- ct:pal("ClientResponse = ~p~nServerPid = ~p",[_ClientResponse,ServerPid]),
- {ok, List} = get_trace_list(TracerPid),
-%%ct:pal("List=~p",[List]),
- Times = find_times(List, [channel_open_close]),
- Algs = find_algs(List),
- ct:pal("Algorithms = ~p~n~nTimes = ~p",[Algs,Times]),
- lists:foreach(
- fun({{A,B},Value,Unit}) when A==encrypt ; A==decrypt ->
- Data = [{value, Value},
- {suite, ?MODULE},
- {name, mk_name(["Sftp Cipher ",A," ",B," [",Unit,"]"])}
- ],
- ct:pal("sftp ct_event:notify ~p",[Data]),
- ct_event:notify(#event{name = benchmark_data,
- data = Data});
- ({channel_open_close,Value,Unit}) ->
- Cipher = fmt_alg(Algs#alg.encrypt, List),
- Data = [{value, round( (1024*Value) / SrcSize )},
- {suite, ?MODULE},
- {name, mk_name(["Sftp transfer ",Cipher," [",Unit," per kbyte]"])}
- ],
- ct:pal("sftp ct_event:notify ~p",[Data]),
- ct_event:notify(#event{name = benchmark_data,
- data = Data});
- (_) ->
- skip
- end, Times),
- ssh:stop_daemon(ServerPid),
- ok
- after 2*60*1000 ->
- ssh:stop_daemon(ServerPid),
- exit(SlavePid, kill),
- {fail, timeout}
- end.
-
-%%%================================================================
-variants(Tag, Config) ->
- TagType =
- case proplists:get_value(Tag, ssh:default_algorithms()) of
- [{_,_}|_] -> one_way;
- [A|_] when is_atom(A) -> two_way
- end,
- [ [{Tag,tag_value(TagType,Alg)}]
- || Alg <- proplists:get_value(Tag, proplists:get_value(common_algs,Config))
- ].
-
-tag_value(two_way, Alg) -> [Alg];
-tag_value(one_way, Alg) -> [{client2server,[Alg]},
- {server2client,[Alg]}].
-
-%%%----------------------------------------------------------------
-fmt_alg(Alg, List) when is_atom(Alg) ->
- fmt_alg(atom_to_list(Alg), List);
-fmt_alg(Alg = "diffie-hellman-group-exchange-sha" ++ _, List) ->
- try
- integer_to_list(find_gex_size_string(List))
- of
- GexSize -> lists:concat([Alg," ",GexSize])
- catch
- _:_ -> Alg
- end;
-fmt_alg(Alg, _List) ->
- Alg.
-
-%%%----------------------------------------------------------------
-mk_name(Name) -> [char(C) || C <- lists:concat(Name)].
-
-char($-) -> $_;
-char(C) -> C.
-
-%%%----------------------------------------------------------------
-find_times(L, Xs) ->
- [find_time(X,L) || X <- Xs] ++
- function_algs_times_sizes([{ssh_transport,encrypt,2},
- {ssh_transport,decrypt,2},
- {ssh_message,decode,1},
- {ssh_message,encode,1}], L).
-
--record(call, {
- mfa,
- pid,
- t_call,
- t_return,
- args,
- result
- }).
-
-%%%----------------
--define(send(M), fun(C=#call{mfa = {ssh_message,encode,1},
- args = [M]}) ->
- C#call.t_return
- end).
-
--define(recv(M), fun(C=#call{mfa = {ssh_message,decode,1},
- result = M}) ->
- C#call.t_call
- end).
-
-find_time(accept_to_hello, L) ->
- [T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) ->
- C#call.t_call
- end,
- ?LINE,
- fun(C=#call{mfa = {ssh_connection_handler,handle_event,4},
- args = [_, {version_exchange,_}, {hello,_}, _]}) ->
- C#call.t_call
- end,
- ?LINE
- ], L, []),
- {accept_to_hello, now2micro_sec(now_diff(T1,T0)), microsec};
-find_time(kex, L) ->
- [T0,T1] = find([fun(C=#call{mfa = {ssh_connection_handler,handle_event,4},
- args = [_, {version_exchange,_}, {hello,_}, _]}) ->
- C#call.t_call
- end,
- ?LINE,
- ?send(#ssh_msg_newkeys{}),
- ?LINE
- ], L, []),
- {kex, now2micro_sec(now_diff(T1,T0)), microsec};
-find_time(kex_to_auth, L) ->
- [T0,T1] = find([?send(#ssh_msg_newkeys{}),
- ?LINE,
- ?recv(#ssh_msg_userauth_request{}),
- ?LINE
- ], L, []),
- {kex_to_auth, now2micro_sec(now_diff(T1,T0)), microsec};
-find_time(auth, L) ->
- [T0,T1] = find([?recv(#ssh_msg_userauth_request{}),
- ?LINE,
- ?send(#ssh_msg_userauth_success{}),
- ?LINE
- ], L, []),
- {auth, now2micro_sec(now_diff(T1,T0)), microsec};
-find_time(to_prompt, L) ->
- [T0,T1] = find([fun(C=#call{mfa = {ssh_acceptor,handle_connection,5}}) ->
- C#call.t_call
- end,
- ?LINE,
- ?recv(#ssh_msg_channel_request{request_type="env"}),
- ?LINE
- ], L, []),
- {to_prompt, now2micro_sec(now_diff(T1,T0)), microsec};
-find_time(channel_open_close, L) ->
- [T0,T1] = find([?recv(#ssh_msg_channel_request{request_type="subsystem"}),
- ?LINE,
- ?send(#ssh_msg_channel_close{}),
- ?LINE
- ], L, []),
- {channel_open_close, now2micro_sec(now_diff(T1,T0)), microsec}.
-
-
-
-find([F,Id|Fs], [C|Cs], Acc) when is_function(F,1) ->
- try
- F(C)
- of
- T -> find(Fs, Cs, [T|Acc])
- catch
- _:_ -> find([F,Id|Fs], Cs, Acc)
- end;
-find([], _, Acc) ->
- lists:reverse(Acc).
-
-
-find_algs(L) ->
- {value, #call{result={ok,Algs}}} =
- lists:keysearch({ssh_transport,select_algorithm,3}, #call.mfa, L),
- Algs.
-
-find_gex_size_string(L) ->
- %% server
- {value, #call{result={ok,{Size, _}}}} =
- lists:keysearch({public_key,dh_gex_group,4}, #call.mfa, L),
- Size.
-
-%%%----------------
-function_algs_times_sizes(EncDecs, L) ->
- Raw = [begin
- {Tag,Size} = function_ats_result(EncDec, C),
- {Tag, Size, now2micro_sec(now_diff(T1,T0))}
- end
- || EncDec <- EncDecs,
- C = #call{mfa = ED,
- % args = Args, %%[S,Data],
- t_call = T0,
- t_return = T1} <- L,
- ED == EncDec
- ],
- [{Alg, round(1024*Time/Size), "microsec per kbyte"} % Microseconds per 1k bytes.
- || {Alg,Size,Time} <- lists:foldl(fun increment/2, [], Raw)].
-
-function_ats_result({ssh_transport,encrypt,2}, #call{args=[S,Data]}) ->
- {{encrypt,S#ssh.encrypt}, size(Data)};
-function_ats_result({ssh_transport,decrypt,2}, #call{args=[S,Data]}) ->
- {{decrypt,S#ssh.decrypt}, size(Data)};
-function_ats_result({ssh_message,encode,1}, #call{result=Data}) ->
- {encode, size(Data)};
-function_ats_result({ssh_message,decode,1}, #call{args=[Data]}) ->
- {decode, size(Data)}.
-
-
-increment({Alg,Sz,T}, [{Alg,SumSz,SumT}|Acc]) ->
- [{Alg,SumSz+Sz,SumT+T} | Acc];
-increment(Spec, [X|Acc]) ->
- [X | increment(Spec,Acc)]; % Not so many Alg, 2 or 3
-increment({Alg,Sz,T},[]) ->
- [{Alg,Sz,T}].
-
-%%%----------------------------------------------------------------
-%%%
-%%% API for the traceing
-%%%
-get_trace_list(TracerPid) ->
- MonRef = monitor(process, TracerPid),
- TracerPid ! {get_trace_list,self()},
- receive
- {trace_list,L} ->
- demonitor(MonRef),
- {ok, pair_events(lists:reverse(L))};
- {'DOWN', MonRef, process, TracerPid, Info} ->
- {error, {tracer_down,Info}}
-
- after 3*60*1000 ->
- demonitor(MonRef),
- {error,no_reply}
- end.
-
-erlang_trace() ->
- TracerPid = spawn(fun trace_loop/0),
- 0 = erlang:trace(new, true, [call,timestamp,{tracer,TracerPid}]),
- [init_trace(MFA, tp(MFA))
- || MFA <- [{ssh_acceptor,handle_connection,5},
-%% {ssh_connection_handler,hello,2},
- {ssh_message,encode,1},
- {ssh_message,decode,1},
- {ssh_transport,select_algorithm,3},
- {ssh_transport,encrypt,2},
- {ssh_transport,decrypt,2},
- {ssh_message,encode,1},
- {ssh_message,decode,1},
- {public_key,dh_gex_group,4} % To find dh_gex group size
- ]],
- init_trace({ssh_connection_handler,handle_event,4},
- [{['_', {version_exchange,'_'}, {hello,'_'}, '_'],
- [],
- [return_trace]}]),
- {ok, TracerPid}.
-
-tp({_M,_F,Arity}) ->
- [{lists:duplicate(Arity,'_'), [], [{return_trace}]}].
-
-%%%----------------------------------------------------------------
-init_trace(MFA = {Module,_,_}, TP) ->
- case code:is_loaded(Module) of
- false -> code:load_file(Module);
- _ -> ok
- end,
- erlang:trace_pattern(MFA, TP, [local]).
-
-
-trace_loop() ->
- trace_loop([]).
-
-trace_loop(L) ->
- receive
- {get_trace_list, From} ->
- From ! {trace_list, L},
- trace_loop(L);
- Ev ->
- trace_loop([Ev|L])
- end.
-
-pair_events(L) ->
- pair_events(L, []).
-
-pair_events([{trace_ts,Pid,call,{M,F,Args},TS0} | L], Acc) ->
- Arity = length(Args),
- {ReturnValue,TS1} = find_return(Pid, {M,F,Arity}, L),
- pair_events(L, [#call{mfa = {M,F,Arity},
- pid = Pid,
- t_call = TS0,
- t_return = TS1,
- args = Args,
- result = ReturnValue} | Acc]);
-pair_events([_|L], Acc) ->
- pair_events(L, Acc);
-pair_events([], Acc) ->
- lists:reverse(Acc).
-
-
-find_return(Pid, MFA,
- [{trace_ts, Pid, return_from, MFA, ReturnValue, TS}|_]) ->
- {ReturnValue, TS};
-find_return(Pid, MFA, [_|L]) ->
- find_return(Pid, MFA, L);
-find_return(_, _, []) ->
- {undefined, undefined}.
-
-%%%----------------------------------------------------------------
-report_client_algorithms() ->
- try
- ssh_test_lib:extract_algos( ssh_test_lib:default_algorithms(sshc) )
- of
- ClientAlgs ->
- ct:pal("The client supports:~n~p",[ClientAlgs])
- catch
- Cls:Err ->
- ct:pal("Testing client about algorithms failed:~n~p ~p",[Cls,Err])
- end.
-
-%%%----------------------------------------------------------------
-
-
-now2sec({A,B,C}) -> A*1000000 + B + C/1000000.
-
-now2micro_sec({A,B,C}) -> (A*1000000 + B)*1000000 + C.
-
-now_diff({A1,B1,C1}, {A0,B0,C0}) -> {A1-A0, B1-B0, C1-C0}.
-
-%%%================================================================
-moduli() ->
- [{1023, 5, 16#CF973CD39DC7D62F2C45AAC5180491104C76E0FE5D80A10E6C06AE442F1F373167B0FCBC931F3C157B10A5557008FDE20D68051E6A4DB11CEE0B0749F76D7134B937A59DA998C42BC234A5C1A3CFCD70E624D253D7694076F7B1FD7B8D3427849C9377B3555796ACA58C69DFF542EEEC9859D3ADCE5CC88DF6F7817C9D182EB7},
- {2047, 5, 16#F7693FC11FDDEAA493D3BA36F1FFF9264AA9952209203192A88A697BE9D0E306E306A27430BD87AB9EE9DB4BC78C41950C2EB0E5E4C686E8B1BA6D6A2B1FE91EF40C5EA32C51018323E1D305FE637F35ACABDBFC40AD683F779570A76869EB90015A342B2D1F7C81602688081FCAAA8D623090258D9C5C729C8CDDC0C12CA2D561DD987DB79B6AD7A2A509EBC383BF223FD95BC5A2FCC26FB3F3A0DD3FDC1228E338D3290235A596F9465F7BF490974847E616229A9E60B8F4AA161C52F655843CCCAE8821B40C426B535DE087964778652BBD4EC601C0456AE7128B593FCC64402C891227AE6EE88CC839416FBF462B4852999C646BE0BED7D8CF2BE5E381EF},
- {4095, 2, 16#C8842271626E53546E0C712FA265713F2EE073C20A0723C96B6B182B1EAACC96233D4A199BD0E85F264078A513AD2454F284B8DF543D85019D1E70F2FF54BA43EFBC64AF465C170C3E376F5EC328F98E33E1ED8BED84FA097ABE584152B0E9827ED5CC2B1D4F5ECF2DC46F45C59816D02698EA26F319311E2B6973E83C37021CC8B416AEF653896A1764EE0CEE718A45E8B47CB960BD5907D0E843E8A8E7D4698363C3C3FB3ADC512368B72CAF16510C69052EA2AF51BE00BC8CA04DF1F00A00CC2CA4D74254A1E8738460FD244DDB446CB36554B0A24EEF3710E44DBCF39881E7D3F9AE223388084E7A49A3CB12612AE36416C0EB5628DF1477FEE4A5CF77CDC09AA0E2C989C0B7D1310AFA44B81DA79A65226C7EA510057991EABF9388DC5EA9F52FEA5D3B0872843F50878740794E523E9DC60E0EA1FC8746A7B2AA31FCA89AAA2FA907BED116C69D98F912DD5089BECF28577064225DE96FC214ED1794E7CCE8024F94036D915A123A464C951DA96A5ED7F286F205BEE71BDE2D133FD1891B31178FF25D31611A5B7839F0E68EAF0F8901A571E6917C580F31842A9F19C47E0638483B7947DDCD7864660AC2F8B2C430F1E7FC0F22FA51F96F0499332C5AD3FF9DC7F4332DD5BCCA820CC779B90C0F4C5F0CA52E96FAA187361753FBADC5C80D0492CD80A3EEA5D578772DA9FC1C0E10A0203098AF36D0ED2156BA7321EB},
- {6143, 5, 16#FD9E6B52785CD7BE64D396A599DA4B97CD0BB49183F932A97694D80CA553354DBC26E77B8A0EC002257AADDF6AD27819CE64A06416E4A80B6EA92F28EA8D5B96C774109EEE5816B4B18F84368D1B41864C11AA73D6881675D779B174F6B4E344303F3EFD11BD7DE468467242372FD00908F296F5A2B20E2684F9122D08A46D647B05E298F0BCDAB60468349CCA6DA1B9FEBBC69D256FB9A3F1980F68466364FCEF1C98C1405191A6737A3627BA7F7313A8A18FC0B8521BF3430B1C6805CB44BCEB39904DD30130D24B225B598ED83C5FD757B80189FD9D5C2F9596687C40BAB1C6ED6244944629849D074A4C33FB15DDB3F9760FC59C44BEBB0EC032177147F61789769DAAAE2123CE488F7ECF19BDA051925BA9ED11EAA72DF70C9ECC8F714B4C35728E6679E66A1B56CCAE0FBBD3F9EBF950D4D623ED78E77CC3AD604E91F304EA78CE876F036214BD6F1977BD04C9ADD707D7A3BCCE87AD5D5A11C95E7025B0EA9C649DCB37942A3970A4FB04C284E4DDB4DC90163353B98B1C254FFD28443353F17A87C02E0BDB9F05424CC44C86309F1D73706F039CDAAC3EDC1A64F38FB42707D351DB5360C2680ADC1CC8D1C4AD312ACC904382C26BE33DA0E61429A5940820356ED28586BEB629ED1521D12D25B4DA01926295F3DA504DC9F431B719AC63277BE675E6F6DD4F7499CA11A23744577D653941963E8DAB610F7F226DB52CE5C683F72AEED2B6CE35ED07C29410397A6F7F606477CCC0EDE18CD0D96A7863BC4606193A8799B5AC1EEE6AC5EE36AC3077EC8DAB30EE94434B45B78BC13D96F74D6C4056EAA528CD3C68D308344808819B12F2BFB95A5C1A7DEEE188BF139216DDB7D757D7A50D3C46CE18881D776D617DCFFAA62276045373AA4D9446D7570338F99C0CA8A08851B4F9D388B4C275D3F9B7BA25F235D4329F63F7457C2EB5C68CE2A96D19766F0ED8E19F66DF3C5E29A38795B2F92291BB6EAB6F70A7E89DC9691F28486E9CF87FF11D5DF2E6B030A30B5D476AD59A34EE7262712ED96CEF4A5CAC3F08B3563D44683F746DA094C9CDB34427AF8D8CC2AE1B23C3BEB637},
- {8191, 2, 16#DC61EF13E4F3FC10CC946EEABC33F83EFCB35E0F47E4EC25C1CCBB2C7B502B2EFB0691AA231C8476DD51BA73204E6EA10B1A970FE2CF14AF01E72E1AEA87519A91D00D1499189F94A6CDA9E29C05F11F17FE74A4919A710A2787E180744465DF81C62AA65662FDA46FA6175E8A31E5B29E66DED6701C8FC4217E91D733FE94380F046680967D4CEA7BAC8F3916CDF96AA2C474FAD9650F48403FD0B5B756D34667D36A07767FA33027AE55484D0F701C3CA16632F413A14E4B8645AFAF15B78978C19A7661EDC569BEC72394B1204B166A48FCD5F56BE29840C7794CA6D3440356F15858CDCA9B429C7EA92E17242893FDC8C9C63841A382C32F20CFAB121B4BCAFD7BF9EF07FBF7CDFFECA0CEF3A49C3E2B24FA836F3318435255655E1B281071F62D5E4CD63361299B7828F72936E3FEA9E8044562A6F6ADD5321187C3101E4669C6271598FE1A866C93FE2870A4CEB9254BA32A4719E439317EA42200A335B5CFFA7946A7D0F1BD1A69AA11288B73C71C80B77FE3707CB077DDDEA5CA36A449FAB230C9625A0B12F8275D3FF82F5DA380E7A3F11B6F155FE7E91AC960BD95D9B13F7423AB9B15CC3C4DC34EF296033F009468EA16A721AD659F56C18516025050749ABF05E6D3EBD9778142A530979291F46DAA399A86B7BCDF09CC3E6EEF101419762A306DB45AEFC96C64E83F28338D55905F6A387E0F515E580C3A9B35330E21C32198CDEE3AFB355967A098F635FCA7C49CB4E1E82464B2B390EF1F259E40B9A06235C0273F76284FE6BD534EF3AF7CB01A4A5252B8B94CADC2850B2E56D53F9A31D7C029DF967D0A30C05BC64E119BED6076818FABC8CDD93F3255693E14EFC1A740A5D63A5E847FFE87BAB1DDE0506E1762EA61EFA9F9756151ECCCADD91B98A961A901A2D8B01ABDDD29EC804E8C8D28214BBA26048F924CA66316696E51A49D02FF034D20E44914B1115339CAD3819E0CB1640F0084886FEDDE5E28C29DC48ED30A8C3D789734338F5A9DF42584326E536FD1CF30BC85B8DCBD6120D127C98FE4B3614074F13C2CA4854E6D794156C185C40EB3DA7619CE96ADAF0941BD5499848B034C2B11DFECC0BDFA81C594241F759EF53FC7CDE7F2DE4F23CF81A5A0B7D62E31DABB9198D40307F7824DD130B7D1B80E9B6D322FEEDB5ACE34944F0BFB7D016762A9B2E173BFDD69303766AFBAB45FAB75D05430B4A3515858C4B7F04E23414E4AD03842CB0A20D8FF4B59B7C852BA9A5BE982A8ADA5CB70C36CE2A4D2C31A7015C9F3275E43D192C1B2924424088907A057DA7F2D32A2149922AB2E33F2147D637A3508911CB3FEA5E1AAB4525BACF27B6DD7A3E0AFA978FC3A39DE8882FB22688C3CCC92B6E69ACB0BBF575AB3368E51A2F6A20C414C6F146727CC0045F29061E695D29F7C030CE6929EB3AD11A5CBD0CDEE37347869A3}].
diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl
index bcf3b01824..9bbd9da817 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-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -89,7 +89,7 @@ end_per_suite(Config) ->
%%--------------------------------------------------------------------
init_per_group(openssh, Config) ->
- case gen_tcp:connect("localhost", 22, []) of
+ case ssh_test_lib:gen_tcp_connect("localhost", 22, []) of
{error,econnrefused} ->
{skip,"No openssh deamon"};
{ok, Socket} ->
@@ -126,7 +126,7 @@ simple_exec(Config) when is_list(Config) ->
simple_exec_sock(_Config) ->
- {ok, Sock} = gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, [{active,false}]),
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, [{active,false}]),
{ok, ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
{user_interaction, false}]),
do_simple_exec(ConnectionRef).
@@ -179,13 +179,13 @@ daemon_sock_not_tcp(_Config) ->
%%--------------------------------------------------------------------
connect_sock_not_passive(_Config) ->
- {ok,Sock} = gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, []),
+ {ok,Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, []),
{error, not_passive_mode} = ssh:connect(Sock, []),
gen_tcp:close(Sock).
%%--------------------------------------------------------------------
daemon_sock_not_passive(_Config) ->
- {ok,Sock} = gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, []),
+ {ok,Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, []),
{error, not_passive_mode} = ssh:daemon(Sock),
gen_tcp:close(Sock).
@@ -381,13 +381,13 @@ do_interrupted_send(Config, SendSize, EchoSize) ->
{password, "morot"},
{subsystems, [{"echo_n",EchoSS_spec}]}]),
- ct:log("connect", []),
+ ct:log("~p:~p connect", [?MODULE,?LINE]),
ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
{password, "morot"},
{user_interaction, false},
{user_dir, UserDir}]),
- ct:log("connected", []),
+ ct:log("~p:~p connected", [?MODULE,?LINE]),
%% build big binary
Data = << <<X:32>> || X <- lists:seq(1,SendSize div 4)>>,
@@ -399,58 +399,80 @@ do_interrupted_send(Config, SendSize, EchoSize) ->
Parent = self(),
ResultPid = spawn(
fun() ->
- ct:log("open channel",[]),
+ ct:log("~p:~p open channel",[?MODULE,?LINE]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
- ct:log("start subsystem", []),
+ ct:log("~p:~p start subsystem", [?MODULE,?LINE]),
case ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity) of
success ->
Parent ! {self(), channelId, ChannelId},
Result =
- try collect_data(ConnectionRef, ChannelId)
+ try collect_data(ConnectionRef, ChannelId, EchoSize)
of
ExpectedData ->
+ ct:log("~p:~p got expected data",[?MODULE,?LINE]),
ok;
- _ ->
- {fail,"unexpected result"}
+ Other ->
+ ct:log("~p:~p unexpect: ~p", [?MODULE,?LINE,Other]),
+ {fail,"unexpected result in listener"}
catch
Class:Exception ->
- {fail, io_lib:format("Exception ~p:~p",[Class,Exception])}
+ {fail, io_lib:format("Listener exception ~p:~p",[Class,Exception])}
end,
- Parent ! {self(), Result};
+ Parent ! {self(), result, Result};
Other ->
Parent ! {self(), channelId, error, Other}
end
end),
receive
+ {ResultPid, channelId, error, Other} ->
+ ct:log("~p:~p channelId error ~p", [?MODULE,?LINE,Other]),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid),
+ {fail, "ssh_connection:subsystem"};
+
{ResultPid, channelId, ChannelId} ->
- %% pre-adjust receive window so the other end doesn't block
- ct:log("adjust window", []),
- ssh_connection:adjust_window(ConnectionRef, ChannelId, size(ExpectedData) + 1),
-
- ct:log("going to send ~p bytes", [size(Data)]),
- case ssh_connection:send(ConnectionRef, ChannelId, Data, 30000) of
- {error, closed} ->
- ct:log("{error,closed} - That's what we expect :)", []),
- ok;
- Msg ->
- ct:log("Got ~p - that's bad, very bad indeed",[Msg]),
- ct:fail({expected,{error,closed}, got, Msg})
- end,
- ct:log("going to check the result (if it is available)", []),
+ ct:log("~p:~p ~p going to send ~p bytes", [?MODULE,?LINE,self(),size(Data)]),
+ SenderPid = spawn(fun() ->
+ Parent ! {self(), ssh_connection:send(ConnectionRef, ChannelId, Data, 30000)}
+ end),
receive
- {ResultPid, Result} ->
- ct:log("Got result: ~p", [Result]),
+ {ResultPid, result, {fail, Fail}} ->
+ ct:log("~p:~p Listener failed: ~p", [?MODULE,?LINE,Fail]),
+ {fail, Fail};
+
+ {ResultPid, result, Result} ->
+ ct:log("~p:~p Got result: ~p", [?MODULE,?LINE,Result]),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid),
- Result
- end;
+ ct:log("~p:~p Check sender", [?MODULE,?LINE]),
+ receive
+ {SenderPid, {error, closed}} ->
+ ct:log("~p:~p {error,closed} - That's what we expect :)",[?MODULE,?LINE]),
+ ok;
+ Msg ->
+ ct:log("~p:~p Not expected send result: ~p",[?MODULE,?LINE,Msg]),
+ {fail, "Not expected msg"}
+ end;
+
+ {SenderPid, {error, closed}} ->
+ ct:log("~p:~p {error,closed} - That's what we expect, but client channel handler has not reported yet",[?MODULE,?LINE]),
+ receive
+ {ResultPid, result, Result} ->
+ ct:log("~p:~p Now got the result: ~p", [?MODULE,?LINE,Result]),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid),
+ ok;
+ Msg ->
+ ct:log("~p:~p Got an unexpected msg ~p",[?MODULE,?LINE,Msg]),
+ {fail, "Un-expected msg"}
+ end;
- {ResultPid, channelId, error, Other} ->
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid),
- {fail, io_lib:format("ssh_connection:subsystem: ~p",[Other])}
+ Msg ->
+ ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,Msg]),
+ {fail, "Unexpected msg"}
+ end
end.
%%--------------------------------------------------------------------
@@ -563,12 +585,13 @@ start_shell_sock_exec_fun(Config) when is_list(Config) ->
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
- {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "morot"},
- {exec, fun ssh_exec/1}]),
+ {Pid, HostD, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {exec, fun ssh_exec/1}]),
+ Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(HostD)),
- {ok, Sock} = gen_tcp:connect(Host, Port, [{active,false}]),
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect(Host, Port, [{active,false}]),
{ok,ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
{user, "foo"},
{password, "morot"},
@@ -601,7 +624,7 @@ start_shell_sock_daemon_exec(Config) ->
{ok,{_IP,Port}} = inet:sockname(Sl), % _IP is likely to be {0,0,0,0}. Win don't like...
spawn_link(fun() ->
- {ok,Ss} = gen_tcp:connect("localhost", Port, [{active,false}]),
+ {ok,Ss} = ssh_test_lib:gen_tcp_connect("localhost", Port, [{active,false}]),
{ok, _Pid} = ssh:daemon(Ss, [{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
@@ -636,10 +659,10 @@ gracefull_invalid_version(Config) when is_list(Config) ->
SysDir = proplists:get_value(data_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "morot"}]),
+ {user_dir, UserDir},
+ {password, "morot"}]),
- {ok, S} = gen_tcp:connect(Host, Port, []),
+ {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
ok = gen_tcp:send(S, ["SSH-8.-1","\r\n"]),
receive
Verstring ->
@@ -658,10 +681,10 @@ gracefull_invalid_start(Config) when is_list(Config) ->
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "morot"}]),
+ {user_dir, UserDir},
+ {password, "morot"}]),
- {ok, S} = gen_tcp:connect(Host, Port, []),
+ {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
ok = gen_tcp:send(S, ["foobar","\r\n"]),
receive
Verstring ->
@@ -680,10 +703,10 @@ gracefull_invalid_long_start(Config) when is_list(Config) ->
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "morot"}]),
+ {user_dir, UserDir},
+ {password, "morot"}]),
- {ok, S} = gen_tcp:connect(Host, Port, []),
+ {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]),
receive
Verstring ->
@@ -703,10 +726,10 @@ gracefull_invalid_long_start_no_nl(Config) when is_list(Config) ->
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "morot"}]),
+ {user_dir, UserDir},
+ {password, "morot"}]),
- {ok, S} = gen_tcp:connect(Host, Port, []),
+ {ok, S} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
ok = gen_tcp:send(S, [lists:duplicate(257, $a), "\r\n"]),
receive
Verstring ->
@@ -757,22 +780,21 @@ stop_listener(Config) when is_list(Config) ->
ct:fail("Exec Timeout")
end,
- {ok, HostAddr} = inet:getaddr(Host, inet),
- case ssh_test_lib:daemon(HostAddr, Port, [{system_dir, SysDir},
- {user_dir, UserDir},
- {password, "potatis"},
- {exec, fun ssh_exec/1}]) of
- {Pid1, HostAddr, Port} ->
+ case ssh_test_lib:daemon(Port, [{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "potatis"},
+ {exec, fun ssh_exec/1}]) of
+ {Pid1, Host, Port} ->
ConnectionRef1 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
{password, "potatis"},
{user_interaction, true},
{user_dir, UserDir}]),
{error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
- {user, "foo"},
- {password, "morot"},
- {user_interaction, true},
- {user_dir, UserDir}]),
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
ssh:close(ConnectionRef0),
ssh:close(ConnectionRef1),
ssh:stop_daemon(Pid0),
@@ -909,36 +931,46 @@ big_cat_rx(ConnectionRef, ChannelId, Acc) ->
timeout
end.
-collect_data(ConnectionRef, ChannelId) ->
- ct:log("Listener ~p running! ConnectionRef=~p, ChannelId=~p",[self(),ConnectionRef,ChannelId]),
- collect_data(ConnectionRef, ChannelId, [], 0).
+collect_data(ConnectionRef, ChannelId, EchoSize) ->
+ ct:log("~p:~p Listener ~p running! ConnectionRef=~p, ChannelId=~p",[?MODULE,?LINE,self(),ConnectionRef,ChannelId]),
+ collect_data(ConnectionRef, ChannelId, EchoSize, [], 0).
-collect_data(ConnectionRef, ChannelId, Acc, Sum) ->
+collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum) ->
TO = 5000,
receive
{ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} when is_binary(Data) ->
- ct:log("collect_data: received ~p bytes. total ~p bytes",[size(Data),Sum+size(Data)]),
- collect_data(ConnectionRef, ChannelId, [Data | Acc], Sum+size(Data));
- {ssh_cm, ConnectionRef, {eof, ChannelId}} ->
- try
- iolist_to_binary(lists:reverse(Acc))
- of
- Bin ->
- ct:log("collect_data: received eof.~nGot in total ~p bytes",[size(Bin)]),
- Bin
- catch
- C:E ->
- ct:log("collect_data: received eof.~nAcc is strange...~nException=~p:~p~nAcc=~p",
- [C,E,Acc]),
- {error,{C,E}}
- end;
+ ct:log("~p:~p collect_data: received ~p bytes. total ~p bytes, want ~p more",
+ [?MODULE,?LINE,size(Data),Sum+size(Data),EchoSize-Sum]),
+ ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
+ collect_data(ConnectionRef, ChannelId, EchoSize, [Data | Acc], Sum+size(Data));
+ {ssh_cm, ConnectionRef, Msg={eof, ChannelId}} ->
+ collect_data_report_end(Acc, Msg, EchoSize);
+
+ {ssh_cm, ConnectionRef, Msg={closed,ChannelId}} ->
+ collect_data_report_end(Acc, Msg, EchoSize);
+
Msg ->
- ct:log("collect_data: ***** unexpected message *****~n~p",[Msg]),
- collect_data(ConnectionRef, ChannelId, Acc, Sum)
+ ct:log("~p:~p collect_data: ***** unexpected message *****~n~p",[?MODULE,?LINE,Msg]),
+ collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
after TO ->
- ct:log("collect_data: ----- Nothing received for ~p seconds -----~n",[]),
- collect_data(ConnectionRef, ChannelId, Acc, Sum)
+ ct:log("~p:~p collect_data: ----- Nothing received for ~p seconds -----~n",[?MODULE,?LINE,TO]),
+ collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
+ end.
+
+collect_data_report_end(Acc, Msg, EchoSize) ->
+ try
+ iolist_to_binary(lists:reverse(Acc))
+ of
+ Bin ->
+ ct:log("~p:~p collect_data: received ~p.~nGot in total ~p bytes, want ~p more",
+ [?MODULE,?LINE,Msg,size(Bin),EchoSize,size(Bin)]),
+ Bin
+ catch
+ C:E ->
+ ct:log("~p:~p collect_data: received ~p.~nAcc is strange...~nException=~p:~p~nAcc=~p",
+ [?MODULE,?LINE,Msg,C,E,Acc]),
+ {error,{C,E}}
end.
%%%-------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_eqc_event_handler.erl b/lib/ssh/test/ssh_eqc_event_handler.erl
new file mode 100644
index 0000000000..233965012a
--- /dev/null
+++ b/lib/ssh/test/ssh_eqc_event_handler.erl
@@ -0,0 +1,43 @@
+-module(ssh_eqc_event_handler).
+
+-compile(export_all).
+
+-behaviour(gen_event).
+
+add_report_handler() ->
+ error_logger:add_report_handler(?MODULE, [self(),Ref=make_ref()]),
+ receive
+ {event_handler_started,HandlerPid,Ref} ->
+ {ok,HandlerPid}
+ end.
+
+get_reports(Pid) ->
+ Pid ! {get_reports,self(),Ref=make_ref()},
+ receive
+ {reports,Reports,Ref} ->
+ {ok,Reports}
+ end.
+
+%%%================================================================
+
+-record(state, {
+ reports = []
+ }).
+
+%% error_logger:add_report_handler(ssh_eqc_event_handler, [self()]).
+
+init([CallerPid,Ref]) ->
+ CallerPid ! {event_handler_started,self(),Ref},
+ {ok, #state{}}.
+
+handle_event(Event, State) ->
+ {ok, State#state{reports = [Event|State#state.reports]}}.
+
+handle_info({get_reports,From,Ref}, State) ->
+ From ! {reports, lists:reverse(State#state.reports), Ref},
+ {ok, State#state{reports=[]}}.
+
+handle_call(_Request, State) -> {ok,reply,State}.
+terminate(_Arg, _State) -> stop.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
diff --git a/lib/ssh/test/ssh_key_cb.erl b/lib/ssh/test/ssh_key_cb.erl
index 388ec2ecc1..5564b9d873 100644
--- a/lib/ssh/test/ssh_key_cb.erl
+++ b/lib/ssh/test/ssh_key_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,9 +33,9 @@ add_host_key(_, _, _) ->
is_host_key(_, _, _, _) ->
true.
-user_key('ssh-dss', Opts) ->
+user_key('ssh-rsa', Opts) ->
UserDir = proplists:get_value(user_dir, Opts),
- KeyFile = filename:join(filename:dirname(UserDir), "id_dsa"),
+ KeyFile = filename:join(filename:dirname(UserDir), "id_rsa"),
{ok, KeyBin} = file:read_file(KeyFile),
[Entry] = public_key:pem_decode(KeyBin),
Key = public_key:pem_entry_decode(Entry),
diff --git a/lib/ssh/test/ssh_key_cb_options.erl b/lib/ssh/test/ssh_key_cb_options.erl
index afccb34f0f..c104a2f129 100644
--- a/lib/ssh/test/ssh_key_cb_options.erl
+++ b/lib/ssh/test/ssh_key_cb_options.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@ add_host_key(_, _, _) ->
is_host_key(_, _, _, _) ->
true.
-user_key('ssh-dss', Opts) ->
+user_key('ssh-rsa', Opts) ->
KeyCbOpts = proplists:get_value(key_cb_private, Opts),
KeyBin = proplists:get_value(priv_key, KeyCbOpts),
[Entry] = public_key:pem_decode(KeyBin),
diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl
index 61883c0647..8b454ffe5d 100644
--- a/lib/ssh/test/ssh_options_SUITE.erl
+++ b/lib/ssh/test/ssh_options_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -61,7 +61,14 @@
unexpectedfun_option_client/1,
unexpectedfun_option_server/1,
user_dir_option/1,
- connectfun_disconnectfun_server/1
+ connectfun_disconnectfun_server/1,
+ hostkey_fingerprint_check/1,
+ hostkey_fingerprint_check_md5/1,
+ hostkey_fingerprint_check_sha/1,
+ hostkey_fingerprint_check_sha256/1,
+ hostkey_fingerprint_check_sha384/1,
+ hostkey_fingerprint_check_sha512/1,
+ hostkey_fingerprint_check_list/1
]).
%%% Common test callbacks
@@ -100,6 +107,13 @@ all() ->
disconnectfun_option_client,
unexpectedfun_option_server,
unexpectedfun_option_client,
+ hostkey_fingerprint_check,
+ hostkey_fingerprint_check_md5,
+ hostkey_fingerprint_check_sha,
+ hostkey_fingerprint_check_sha256,
+ hostkey_fingerprint_check_sha384,
+ hostkey_fingerprint_check_sha512,
+ hostkey_fingerprint_check_list,
id_string_no_opt_client,
id_string_own_string_client,
id_string_random_client,
@@ -136,6 +150,7 @@ init_per_group(hardening_tests, Config) ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
ssh_test_lib:setup_dsa(DataDir, PrivDir),
+ ssh_test_lib:setup_rsa(DataDir, PrivDir),
Config;
init_per_group(dir_options, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -540,10 +555,18 @@ connectfun_disconnectfun_server(Config) ->
{disconnect,Ref,R} ->
ct:log("Disconnect result: ~p",[R]),
ssh:stop_daemon(Pid)
- after 2000 ->
+ after 10000 ->
+ receive
+ X -> ct:log("received ~p",[X])
+ after 0 -> ok
+ end,
{fail, "No disconnectfun action"}
end
- after 2000 ->
+ after 10000 ->
+ receive
+ X -> ct:log("received ~p",[X])
+ after 0 -> ok
+ end,
{fail, "No connectfun action"}
end.
@@ -649,7 +672,7 @@ disconnectfun_option_server(Config) ->
ct:log("Server detected disconnect: ~p",[Reason]),
ssh:stop_daemon(Pid),
ok
- after 3000 ->
+ after 5000 ->
receive
X -> ct:log("received ~p",[X])
after 0 -> ok
@@ -774,6 +797,106 @@ unexpectedfun_option_client(Config) ->
end.
%%--------------------------------------------------------------------
+hostkey_fingerprint_check(Config) ->
+ do_hostkey_fingerprint_check(Config, old).
+
+hostkey_fingerprint_check_md5(Config) ->
+ do_hostkey_fingerprint_check(Config, md5).
+
+hostkey_fingerprint_check_sha(Config) ->
+ do_hostkey_fingerprint_check(Config, sha).
+
+hostkey_fingerprint_check_sha256(Config) ->
+ do_hostkey_fingerprint_check(Config, sha256).
+
+hostkey_fingerprint_check_sha384(Config) ->
+ do_hostkey_fingerprint_check(Config, sha384).
+
+hostkey_fingerprint_check_sha512(Config) ->
+ do_hostkey_fingerprint_check(Config, sha512).
+
+hostkey_fingerprint_check_list(Config) ->
+ do_hostkey_fingerprint_check(Config, [sha,md5,sha256]).
+
+%%%----
+do_hostkey_fingerprint_check(Config, HashAlg) ->
+ case supported_hash(HashAlg) of
+ true ->
+ really_do_hostkey_fingerprint_check(Config, HashAlg);
+ false ->
+ {skip,{unsupported_hash,HashAlg}}
+ end.
+
+supported_hash(old) -> true;
+supported_hash(HashAlg) ->
+ Hs = if is_atom(HashAlg) -> [HashAlg];
+ is_list(HashAlg) -> HashAlg
+ end,
+ [] == (Hs -- proplists:get_value(hashs, crypto:supports(), [])).
+
+
+really_do_hostkey_fingerprint_check(Config, HashAlg) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDirServer = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ file:make_dir(UserDirServer),
+ SysDir = proplists:get_value(data_dir, Config),
+
+ UserDirClient =
+ ssh_test_lib:create_random_dir(Config), % Ensure no 'known_hosts' disturbs
+
+ %% All host key fingerprints. Trust that public_key has checked the ssh_hostkey_fingerprint
+ %% function since that function is used by the ssh client...
+ FPs0 = [case HashAlg of
+ old -> public_key:ssh_hostkey_fingerprint(Key);
+ _ -> public_key:ssh_hostkey_fingerprint(HashAlg, Key)
+ end
+ || FileCandidate <- begin
+ {ok,KeyFileCands} = file:list_dir(SysDir),
+ KeyFileCands
+ end,
+ nomatch =/= re:run(FileCandidate, ".*\\.pub", []),
+ {Key,_Cmnts} <- begin
+ {ok,Bin} = file:read_file(filename:join(SysDir, FileCandidate)),
+ try public_key:ssh_decode(Bin, public_key)
+ catch
+ _:_ -> []
+ end
+ end],
+ FPs = if is_atom(HashAlg) -> FPs0;
+ is_list(HashAlg) -> lists:concat(FPs0)
+ end,
+ ct:log("Fingerprints(~p) = ~p",[HashAlg,FPs]),
+
+ %% Start daemon with the public keys that we got fingerprints from
+ {Pid, Host0, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDirServer},
+ {password, "morot"}]),
+ Host = ssh_test_lib:ntoa(Host0),
+ FP_check_fun = fun(PeerName, FP) ->
+ ct:log("PeerName = ~p, FP = ~p",[PeerName,FP]),
+ HostCheck = ssh_test_lib:match_ip(Host, PeerName),
+ FPCheck =
+ if is_atom(HashAlg) -> lists:member(FP, FPs);
+ is_list(HashAlg) -> lists:all(fun(FP1) -> lists:member(FP1,FPs) end,
+ FP)
+ end,
+ ct:log("check ~p == ~p (~p) and ~n~p~n in ~p (~p)~n",
+ [PeerName,Host,HostCheck,FP,FPs,FPCheck]),
+ HostCheck and FPCheck
+ end,
+
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts,
+ case HashAlg of
+ old -> FP_check_fun;
+ _ -> {HashAlg, FP_check_fun}
+ end},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDirClient},
+ {user_interaction, false}]),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
%%% Test connect_timeout option in ssh:connect/4
ssh_connect_timeout(_Config) ->
ConnTimeout = 2000,
@@ -929,20 +1052,20 @@ id_string_random_client(Config) ->
%%--------------------------------------------------------------------
id_string_no_opt_server(Config) ->
{_Server, Host, Port} = ssh_test_lib:std_daemon(Config, []),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
+ {ok,S1}=ssh_test_lib: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} = ssh_test_lib:std_daemon(Config, [{id_string,"Olle"}]),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
+ {ok,S1}=ssh_test_lib: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} = ssh_test_lib:std_daemon(Config, [{id_string,random}]),
- {ok,S1}=gen_tcp:connect(Host,Port,[{active,false},{packet,line}]),
+ {ok,S1}=ssh_test_lib: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]),
@@ -963,18 +1086,25 @@ ssh_connect_negtimeout(Config, Parallel) ->
ct:log("Parallel: ~p",[Parallel]),
{_Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},{user_dir, UserDir},
- {parallel_login, Parallel},
- {negotiation_timeout, NegTimeOut},
- {failfun, fun ssh_test_lib:failfun/2}]),
-
- {ok,Socket} = gen_tcp:connect(Host, Port, []),
+ {parallel_login, Parallel},
+ {negotiation_timeout, NegTimeOut},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+
+ {ok,Socket} = ssh_test_lib:gen_tcp_connect(Host, Port, []),
Factor = 2,
ct:log("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");
+ {ok,_} ->
+ %% Give it another chance...
+ ct:log("Sleep more...",[]),
+ ct:sleep(round(Factor * NegTimeOut)),
+ case inet:sockname(Socket) of
+ {ok,_} -> ct:fail("Socket not closed");
+ {error,_} -> ok
+ end;
{error,_} -> ok
end.
@@ -1003,7 +1133,7 @@ ssh_connect_nonegtimeout_connected(Config, Parallel) ->
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- Shell = ssh_test_lib:start_shell(Port, IO, UserDir),
+ Shell = ssh_test_lib:start_shell(Port, IO, [{user_dir,UserDir}]),
receive
Error = {'EXIT', _, _} ->
ct:log("~p",[Error]),
diff --git a/lib/ssh/test/ssh_property_test_SUITE.erl b/lib/ssh/test/ssh_property_test_SUITE.erl
index c8aabcedb7..3318b86d39 100644
--- a/lib/ssh/test/ssh_property_test_SUITE.erl
+++ b/lib/ssh/test/ssh_property_test_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
-include_lib("common_test/include/ct.hrl").
all() -> [{group, messages},
+ client_sends_info_timing,
{group, client_server}
].
@@ -54,6 +55,9 @@ groups() ->
init_per_suite(Config) ->
ct_property_test:init_per_suite(Config).
+end_per_suite(Config) ->
+ Config.
+
%%% One group in this suite happens to support only QuickCheck, so skip it
%%% if we run proper.
init_per_group(client_server, Config) ->
@@ -67,9 +71,6 @@ init_per_group(_, Config) ->
end_per_group(_, Config) ->
Config.
-%%% Always skip the testcase that is not quite in phase with the
-%%% ssh_message.erl code
-init_per_testcase(decode_encode, _) -> {skip, "Fails - testcase is not ok"};
init_per_testcase(_TestCase, Config) -> Config.
end_per_testcase(_TestCase, Config) -> Config.
@@ -106,3 +107,9 @@ client_server_parallel_multi(Config) ->
ssh_eqc_client_server:prop_parallel_multi(Config),
Config
).
+
+client_sends_info_timing(Config) ->
+ ct_property_test:quickcheck(
+ ssh_eqc_client_info_timing:prop_seq(Config),
+ Config
+ ).
diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl
index 4fac1f718a..0837fe7eaf 100644
--- a/lib/ssh/test/ssh_protocol_SUITE.erl
+++ b/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. 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
@@ -34,6 +34,12 @@
-define(NEWLINE, <<"\r\n">>).
-define(REKEY_DATA_TMO, 65000).
+%%-define(DEFAULT_KEX, 'diffie-hellman-group1-sha1').
+-define(DEFAULT_KEX, 'diffie-hellman-group14-sha256').
+
+-define(CIPHERS, ['aes256-ctr','aes192-ctr','aes128-ctr','aes128-cbc','3des-cbc']).
+-define(DEFAULT_CIPHERS, [{client2server,?CIPHERS}, {server2client,?CIPHERS}]).
+
-define(v(Key, Config), proplists:get_value(Key, Config)).
-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
@@ -48,11 +54,13 @@ suite() ->
all() ->
[{group,tool_tests},
+ client_info_line,
{group,kex},
{group,service_requests},
{group,authentication},
{group,packet_size_error},
- {group,field_size_error}
+ {group,field_size_error},
+ {group,ext_info}
].
groups() ->
@@ -83,7 +91,12 @@ groups() ->
bad_service_name_then_correct
]},
{authentication, [], [client_handles_keyboard_interactive_0_pwds
- ]}
+ ]},
+ {ext_info, [], [no_ext_info_s1,
+ no_ext_info_s2,
+ ext_info_s,
+ ext_info_c
+ ]}
].
@@ -96,7 +109,9 @@ end_per_suite(Config) ->
init_per_testcase(no_common_alg_server_disconnects, Config) ->
- start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']}]}]);
+ start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}]);
init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
TC == gex_client_init_option_groups_moduli_file ;
@@ -106,7 +121,10 @@ init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
TC == gex_client_old_request_noexact ->
Opts = case TC of
gex_client_init_option_groups ->
- [{dh_gex_groups, [{2345, 3, 41}]}];
+ [{dh_gex_groups,
+ [{1023, 5,
+ 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F
+ }]}];
gex_client_init_option_groups_file ->
DataDir = proplists:get_value(data_dir, Config),
F = filename:join(DataDir, "dh_group_test"),
@@ -118,16 +136,19 @@ init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
_ when TC == gex_server_gex_limit ;
TC == gex_client_old_request_exact ;
TC == gex_client_old_request_noexact ->
- [{dh_gex_groups, [{ 500, 3, 17},
- {1000, 7, 91},
- {3000, 5, 61}]},
- {dh_gex_limits,{500,1500}}
+ [{dh_gex_groups,
+ [{1023, 2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323},
+ {1535, 5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827},
+ {3071, 2, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9429825A2B}
+ ]},
+ {dh_gex_limits, {1023,2000}}
];
_ ->
[]
end,
start_std_daemon(Config,
- [{preferred_algorithms, ssh:default_algorithms()}
+ [{preferred_algorithms,[{cipher,?DEFAULT_CIPHERS}
+ ]}
| Opts]);
init_per_testcase(_TestCase, Config) ->
check_std_daemon_works(Config, ?LINE).
@@ -236,7 +257,10 @@ lib_works_as_server(Config) ->
%% and finally connect to it with a regular Erlang SSH client:
{ok,_} = std_connect(HostPort, Config,
- [{preferred_algorithms,[{kex,['diffie-hellman-group1-sha1']}]}]
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}
+ ]
).
%%--------------------------------------------------------------------
@@ -276,7 +300,9 @@ no_common_alg_server_disconnects(Config) ->
[{silently_accept_hosts, true},
{user_dir, user_dir(Config)},
{user_interaction, false},
- {preferred_algorithms,[{public_key,['ssh-dss']}]}
+ {preferred_algorithms,[{public_key,['ssh-dss']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}
]},
receive_hello,
{send, hello},
@@ -310,7 +336,7 @@ no_common_alg_client_disconnects(Config) ->
{match, #ssh_msg_kexinit{_='_'}, receive_msg},
{send, #ssh_msg_kexinit{ % with unsupported "SOME-UNSUPPORTED"
cookie = <<80,158,95,51,174,35,73,130,246,141,200,49,180,190,82,234>>,
- kex_algorithms = ["diffie-hellman-group1-sha1"],
+ kex_algorithms = [atom_to_list(?DEFAULT_KEX)],
server_host_key_algorithms = ["SOME-UNSUPPORTED"], % SIC!
encryption_algorithms_client_to_server = ["aes128-ctr"],
encryption_algorithms_server_to_client = ["aes128-ctr"],
@@ -331,7 +357,9 @@ no_common_alg_client_disconnects(Config) ->
%% and finally connect to it with a regular Erlang SSH client
%% which of course does not support SOME-UNSUPPORTED as pub key algo:
- Result = std_connect(HostPort, Config, [{preferred_algorithms,[{public_key,['ssh-dss']}]}]),
+ Result = std_connect(HostPort, Config, [{preferred_algorithms,[{public_key,['ssh-dss']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}]),
ct:log("Result of connect is ~p",[Result]),
receive
@@ -350,20 +378,25 @@ no_common_alg_client_disconnects(Config) ->
%%%--------------------------------------------------------------------
gex_client_init_option_groups(Config) ->
- do_gex_client_init(Config, {2000, 2048, 4000},
- {3,41}).
+ do_gex_client_init(Config, {512, 2048, 4000},
+ {5,16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F}
+ ).
gex_client_init_option_groups_file(Config) ->
do_gex_client_init(Config, {2000, 2048, 4000},
- {5,61}).
+ {5, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9424273F1F}
+ ).
gex_client_init_option_groups_moduli_file(Config) ->
do_gex_client_init(Config, {2000, 2048, 4000},
- {5,16#B7}).
+ {5, 16#DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E58281FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA45C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231BBE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116B8119892C604293683A9635F}
+ ).
gex_server_gex_limit(Config) ->
do_gex_client_init(Config, {1000, 3000, 4000},
- {7,91}).
+ %% {7,91}).
+ {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827}
+ ).
do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
@@ -375,7 +408,9 @@ do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
[{silently_accept_hosts, true},
{user_dir, user_dir(Config)},
{user_interaction, false},
- {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']}]}
+ {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}
]},
receive_hello,
{send, hello},
@@ -389,8 +424,15 @@ do_gex_client_init(Config, {Min,N,Max}, {G,P}) ->
).
%%%--------------------------------------------------------------------
-gex_client_old_request_exact(Config) -> do_gex_client_init_old(Config, 500, {3,17}).
-gex_client_old_request_noexact(Config) -> do_gex_client_init_old(Config, 800, {7,91}).
+gex_client_old_request_exact(Config) ->
+ do_gex_client_init_old(Config, 1023,
+ {2, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A771225323}
+ ).
+
+gex_client_old_request_noexact(Config) ->
+ do_gex_client_init_old(Config, 1400,
+ {5, 16#D1391174233D315398FE2830AC6B2B66BCCD01B0A634899F339B7879F1DB85712E9DC4E4B1C6C8355570C1D2DCB53493DF18175A9C53D1128B592B4C72D97136F5542FEB981CBFE8012FDD30361F288A42BD5EBB08BAB0A5640E1AC48763B2ABD1945FEE36B2D55E1D50A1C86CED9DD141C4E7BE2D32D9B562A0F8E2E927020E91F58B57EB9ACDDA106A59302D7E92AD5F6E851A45FA1CFE86029A0F727F65A8F475F33572E2FDAB6073F0C21B8B54C3823DB2EF068927E5D747498F96E1E827}
+ ).
do_gex_client_init_old(Config, N, {G,P}) ->
{ok,_} =
@@ -401,7 +443,9 @@ do_gex_client_init_old(Config, N, {G,P}) ->
[{silently_accept_hosts, true},
{user_dir, user_dir(Config)},
{user_interaction, false},
- {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']}]}
+ {preferred_algorithms,[{kex,['diffie-hellman-group-exchange-sha1']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}
]},
receive_hello,
{send, hello},
@@ -422,7 +466,7 @@ bad_long_service_name(Config) ->
bad_very_long_service_name(Config) ->
bad_service_name(Config,
- lists:duplicate(4*?SSH_MAX_PACKET_SIZE, $a)).
+ lists:duplicate(?SSH_MAX_PACKET_SIZE+5, $a)).
empty_service_name(Config) ->
bad_service_name(Config, "").
@@ -571,10 +615,148 @@ client_handles_keyboard_interactive_0_pwds(Config) ->
%% and finally connect to it with a regular Erlang SSH client:
{ok,_} = std_connect(HostPort, Config,
- [{preferred_algorithms,[{kex,['diffie-hellman-group1-sha1']}]}]
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}]
).
+
+%%%--------------------------------------------------------------------
+client_info_line(_Config) ->
+ %% A client must not send an info-line. If it does, the server should handle
+ %% handle this gracefully
+ {ok,Pid} = ssh_eqc_event_handler:add_report_handler(),
+ {_, _, Port} = ssh_test_lib:daemon([]),
+
+ %% Fake client:
+ {ok,S} = gen_tcp:connect("localhost",Port,[]),
+ gen_tcp:send(S,"An illegal info-string\r\n"),
+ gen_tcp:close(S),
+
+ %% wait for server to react:
+ timer:sleep(1000),
+
+ %% check if a badmatch was received:
+ {ok, Reports} = ssh_eqc_event_handler:get_reports(Pid),
+ case lists:any(fun({error_report,_,{_,supervisor_report,L}}) when is_list(L) ->
+ lists:member({reason,{badmatch,{error,closed}}}, L);
+ (_) ->
+ false
+ end, Reports) of
+ true ->
+ ct:fail("Bad error report on info_line from client");
+ false ->
+ ok
+ end.
+
+%%%--------------------------------------------------------------------
+%%% The server does not send the extension because
+%%% the client does not tell the server to send it
+no_ext_info_s1(Config) ->
+ %% Start the dameon
+ Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true},
+ {system_dir, system_dir(Config)}]),
+ {ok,AfterKexState} = connect_and_kex([{server,Server}|Config]),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
+ {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg}
+ ], AfterKexState),
+ ssh:stop_daemon(Pid).
+
+%%%--------------------------------------------------------------------
+%%% The server does not send the extension because
+%%% the server is not configured to send it
+no_ext_info_s2(Config) ->
+ %% Start the dameon
+ Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,false},
+ {system_dir, system_dir(Config)}]),
+ {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]},
+ {server,Server}
+ | Config]),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
+ {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg}
+ ], AfterKexState),
+ ssh:stop_daemon(Pid).
+
+%%%--------------------------------------------------------------------
+%%% The server sends the extension
+ext_info_s(Config) ->
+ %% Start the dameon
+ Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true},
+ {system_dir, system_dir(Config)}]),
+ {ok,AfterKexState} = connect_and_kex([{extra_options,[{recv_ext_info,true}]},
+ {server,Server}
+ | Config]),
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{match, #ssh_msg_ext_info{_='_'}, receive_msg}
+ ],
+ AfterKexState),
+ ssh:stop_daemon(Pid).
+
+%%%--------------------------------------------------------------------
+%%% The client sends the extension
+ext_info_c(Config) ->
+ {User,_Pwd} = server_user_password(Config),
+
+ %% Create a listening socket as server socket:
+ {ok,InitialState} = ssh_trpt_test_lib:exec(listen),
+ HostPort = ssh_trpt_test_lib:server_host_port(InitialState),
+
+ Parent = self(),
+ %% Start a process handling one connection on the server side:
+ Pid =
+ spawn_link(
+ fun() ->
+ Result =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_messages]},
+ {accept, [{system_dir, system_dir(Config)},
+ {user_dir, user_dir(Config)},
+ {recv_ext_info, true}
+ ]},
+ receive_hello,
+ {send, hello},
+
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+
+ {match, #ssh_msg_kexdh_init{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_reply},
+
+ {send, #ssh_msg_newkeys{}},
+ {match, #ssh_msg_newkeys{_='_'}, receive_msg},
+
+ {match, #ssh_msg_ext_info{_='_'}, receive_msg},
+
+ close_socket,
+ print_state
+ ],
+ InitialState),
+ Parent ! {result,self(),Result}
+ end),
+
+ %% connect to it with a regular Erlang SSH client
+ %% (expect error due to the close_socket in daemon):
+ {error,_} = std_connect(HostPort, Config,
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {tstflg, [{ext_info_client,true}]},
+ {send_ext_info, true}
+ ]
+ ),
+
+ %% Check that the daemon got expected result:
+ receive
+ {result, Pid, {ok,_}} -> ok;
+ {result, Pid, Error} -> ct:fail("Error: ~p",[Error])
+ end.
+
%%%================================================================
%%%==== Internal functions ========================================
%%%================================================================
@@ -592,6 +774,7 @@ stop_apps(_Config) ->
setup_dirs(Config) ->
DataDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:setup_dsa(DataDir, PrivDir),
ssh_test_lib:setup_rsa(DataDir, PrivDir),
Config.
@@ -677,10 +860,15 @@ connect_and_kex(Config, InitialState) ->
ssh_trpt_test_lib:exec(
[{connect,
server_host(Config),server_port(Config),
- [{preferred_algorithms,[{kex,['diffie-hellman-group1-sha1']}]},
- {silently_accept_hosts, true},
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {silently_accept_hosts, true},
+ {recv_ext_info, false},
{user_dir, user_dir(Config)},
- {user_interaction, false}]},
+ {user_interaction, false}
+ | proplists:get_value(extra_options,Config,[])
+ ]},
receive_hello,
{send, hello},
{send, ssh_msg_kexinit},
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test
index 2887bb4b60..87c4b4afc8 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test
@@ -1,3 +1,3 @@
-{2222, 5, 61}.
-{1111, 7, 91}.
+{1023, 5, 16#D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A770E2EC9F}.
+{3071, 5, 16#DFAA35D35531E0F524F0099877A482D2AC8D589F374394A262A8E81A8A4FB2F65FADBAB395E05D147B29D486DFAA41F41597A256DA82A8B6F76401AED53D0253F956CEC610D417E42E3B287F7938FC24D8821B40BFA218A956EB7401BED6C96C68C7FD64F8170A8A76B953DD2F05420118F6B144D8FE48060A2BCB85056B478EDEF96DBC70427053ECD2958C074169E9550DD877779A3CF17C5AC850598C7586BEEA9DCFE9DD2A5FB62DF5F33EA7BC00CDA31B9D2DD721F979EA85B6E63F0C4E30BDDCD3A335522F9004C4ED50B15DC537F55324DD4FA119FB3F101467C6D7E1699DE4B3E3C478A8679B8EB3FA5C9B826B44530FD3BE9AD3063B240B0C853EBDDBD68DD940332D98F148D5D9E1DC977D60A0D23D0CA1198637FEAE4E7FAAC173AF2B84313A666CFB4EE6972811921D0AD867CE57F3BBC8D6CB057E3B66757BB46C9F72662624D44E14528327E3A7100E81A12C43C4E236118318CD90C8AA185BBB0C764826DAEAEE8DD245C5B451B4944E6122CC522D1C335C2EEF9424273F1F}.
diff --git a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli
index f6995ba4c9..6d2b4bcb59 100644
--- a/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli
+++ b/lib/ssh/test/ssh_protocol_SUITE_data/dh_group_test.moduli
@@ -1,3 +1,2 @@
-20151021104105 2 6 100 2222 5 B7
-20151021104106 2 6 100 1111 5 4F
-
+20120821044046 2 6 100 1023 2 D9277DAA27DB131C03B108D41A76B4DA8ACEECCCAE73D2E48CEDAAA70B09EF9F04FB020DCF36C51B8E485B26FABE0337E24232BE4F4E693548310244937433FB1A5758195DC73B84ADEF8237472C46747D79DC0A2CF8A57CE8DBD8F466A20F8551E7B1B824B2E4987A8816D9BC0741C2798F3EBAD3ADEBCC78FCE6A7711F2C6B
+20120821050554 2 6 100 2047 5 DD2047CBDBB6F8E919BC63DE885B34D0FD6E3DB2887D8B46FE249886ACED6B46DFCD5553168185FD376122171CD8927E60120FA8D01F01D03E58281FEA9A1ABE97631C828E41815F34FDCDF787419FE13A3137649AA93D2584230DF5F24B5C00C88B7D7DE4367693428C730376F218A53E853B0851BAB7C53C15DA7839CBE1285DB63F6FA45C1BB59FE1C5BB918F0F8459D7EF60ACFF5C0FA0F3FCAD1C5F4CE4416D4F4B36B05CDCEBE4FB879E95847EFBC6449CD190248843BC7EDB145FBFC4EDBB1A3C959298F08F3BA2CFBE231BBE204BE6F906209D28BD4820AB3E7BE96C26AE8A809ADD8D1A5A0B008E9570FA4C4697E116B8119892C604293683A9635F
diff --git a/lib/ssh/test/ssh_relay.erl b/lib/ssh/test/ssh_relay.erl
index 28000fbb97..763130358b 100644
--- a/lib/ssh/test/ssh_relay.erl
+++ b/lib/ssh/test/ssh_relay.erl
@@ -131,7 +131,8 @@ init([ListenAddr, ListenPort, PeerAddr, PeerPort | _Options]) ->
S = #state{local_addr = ListenAddr,
local_port = ListenPort,
lpid = LPid,
- peer_addr = PeerAddr,
+ peer_addr = ssh_test_lib:ntoa(
+ ssh_test_lib:mangle_connect_address(PeerAddr)),
peer_port = PeerPort
},
{ok, S};
@@ -241,11 +242,11 @@ 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]),
+ io:format("Acceptor in ~p has finished: ~p~n", [?MODULE,Reason]),
{noreply, S};
handle_info(_Info, State) ->
- io:format("Unhandled info: ~p~n", [_Info]),
+ io:format("~p:~p Unhandled info: ~p~n", [?MODULE,?LINE,_Info]),
{noreply, State}.
%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_renegotiate_SUITE.erl b/lib/ssh/test/ssh_renegotiate_SUITE.erl
index b10ec3707f..74bbc291b2 100644
--- a/lib/ssh/test/ssh_renegotiate_SUITE.erl
+++ b/lib/ssh/test/ssh_renegotiate_SUITE.erl
@@ -92,11 +92,11 @@ rekey(Config) ->
ConnectionRef =
ssh_test_lib:std_connect(Config, Host, Port,
[{rekey_limit, 0}]),
- Kex1 = get_kex_init(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
receive
after ?REKEY_DATA_TMO ->
%%By this time rekeying would have been done
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid)
@@ -120,31 +120,31 @@ rekey_limit(Config) ->
{max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
- Kex1 = get_kex_init(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
timer:sleep(?REKEY_DATA_TMO),
- Kex1 = get_kex_init(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
Data = lists:duplicate(159000,1),
ok = ssh_sftp:write_file(SftpPid, DataFile, Data),
timer:sleep(?REKEY_DATA_TMO),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
timer:sleep(?REKEY_DATA_TMO),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
ok = ssh_sftp:write_file(SftpPid, DataFile, "hi\n"),
timer:sleep(?REKEY_DATA_TMO),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
timer:sleep(?REKEY_DATA_TMO),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
ssh_sftp:stop_channel(SftpPid),
ssh:close(ConnectionRef),
@@ -169,7 +169,7 @@ renegotiate1(Config) ->
ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
- Kex1 = get_kex_init(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
{ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
@@ -181,7 +181,7 @@ renegotiate1(Config) ->
timer:sleep(2000),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
@@ -208,7 +208,7 @@ renegotiate2(Config) ->
ConnectionRef = ssh_test_lib:std_connect(Config, Host, RPort, [{max_random_length_padding,0}]),
{ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef),
- Kex1 = get_kex_init(ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
{ok, Handle} = ssh_sftp:open(SftpPid, DataFile, [write]),
@@ -223,7 +223,7 @@ renegotiate2(Config) ->
timer:sleep(2000),
- Kex2 = get_kex_init(ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
false = (Kex2 == Kex1),
@@ -235,19 +235,3 @@ renegotiate2(Config) ->
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-%% 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_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl
index 19ad81e7da..680a8ef52e 100644
--- a/lib/ssh/test/ssh_sftp_SUITE.erl
+++ b/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -60,12 +60,16 @@ end_per_suite(_onfig) ->
groups() ->
[{not_unicode, [], [{group,erlang_server},
{group,openssh_server},
+ {group,big_recvpkt_size},
sftp_nonexistent_subsystem]},
{unicode, [], [{group,erlang_server},
{group,openssh_server},
sftp_nonexistent_subsystem]},
+ {big_recvpkt_size, [], [{group,erlang_server},
+ {group,openssh_server}]},
+
{erlang_server, [], [{group,write_read_tests},
version_option,
{group,remote_tar}]},
@@ -149,6 +153,9 @@ init_per_group(unicode, Config) ->
{skip, "Not unicode file encoding"}
end;
+init_per_group(big_recvpkt_size, Config) ->
+ [{pkt_sz,123456} | Config];
+
init_per_group(erlang_server, Config) ->
ct:comment("Begin ~p",[grps(Config)]),
PrivDir = proplists:get_value(priv_dir, Config),
@@ -257,7 +264,10 @@ init_per_testcase(Case, Config00) ->
Dog = ct:timetrap(2 * ?default_timeout),
User = proplists:get_value(user, Config0),
Passwd = proplists:get_value(passwd, Config0),
-
+ PktSzOpt = case proplists:get_value(pkt_sz, Config0) of
+ undefined -> [];
+ Sz -> [{packet_size,Sz}]
+ end,
Config =
case proplists:get_value(group,Config2) of
erlang_server ->
@@ -267,7 +277,9 @@ init_per_testcase(Case, Config00) ->
[{user, User},
{password, Passwd},
{user_interaction, false},
- {silently_accept_hosts, true}]
+ {silently_accept_hosts, true}
+ | PktSzOpt
+ ]
),
Sftp = {ChannelPid, Connection},
[{sftp, Sftp}, {watchdog, Dog} | Config2];
@@ -278,7 +290,9 @@ init_per_testcase(Case, Config00) ->
{ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host,
[{user_interaction, false},
- {silently_accept_hosts, true}]),
+ {silently_accept_hosts, true}
+ | PktSzOpt
+ ]),
Sftp = {ChannelPid, Connection},
[{sftp, Sftp}, {watchdog, Dog} | Config2]
end,
@@ -646,7 +660,7 @@ start_channel_sock(Config) ->
{Host,Port} = proplists:get_value(peer, Config),
%% Get a tcp socket
- {ok, Sock} = gen_tcp:connect(Host, Port, [{active,false}]),
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect(Host, Port, [{active,false}]),
%% and open one channel on one new Connection
{ok, ChPid1, Conn} = ssh_sftp:start_channel(Sock, Opts),
@@ -1024,7 +1038,7 @@ oldprep(Config) ->
prepare(Config0) ->
PrivDir = proplists:get_value(priv_dir, Config0),
- Dir = filename:join(PrivDir, random_chars(10)),
+ Dir = filename:join(PrivDir, ssh_test_lib:random_chars(10)),
file:make_dir(Dir),
Keys = [filename,
testfile,
@@ -1044,8 +1058,6 @@ prepare(Config0) ->
[{sftp_priv_dir,Dir} | Config2].
-random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)].
-
foldl_keydelete(Keys, L) ->
lists:foldl(fun(K,E) -> lists:keydelete(K,1,E) end,
L,
diff --git a/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_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_sftp_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_sftp_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_sftp_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_sftpd_SUITE.erl b/lib/ssh/test/ssh_sftpd_SUITE.erl
index 52a26110c4..763649a12f 100644
--- a/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -65,7 +65,12 @@ all() ->
ver3_open_flags,
relpath,
sshd_read_file,
- ver6_basic].
+ ver6_basic,
+ access_outside_root,
+ root_with_cwd,
+ relative_path,
+ open_file_dir_v5,
+ open_file_dir_v6].
groups() ->
[].
@@ -117,19 +122,43 @@ init_per_testcase(TestCase, Config) ->
ver6_basic ->
SubSystems = [ssh_sftpd:subsystem_spec([{sftpd_vsn, 6}])],
ssh:daemon(0, [{subsystems, SubSystems}|Options]);
+ access_outside_root ->
+ %% Build RootDir/access_outside_root/a/b and set Root and CWD
+ BaseDir = filename:join(PrivDir, access_outside_root),
+ RootDir = filename:join(BaseDir, a),
+ CWD = filename:join(RootDir, b),
+ %% Make the directory chain:
+ ok = filelib:ensure_dir(filename:join(CWD, tmp)),
+ SubSystems = [ssh_sftpd:subsystem_spec([{root, RootDir},
+ {cwd, CWD}])],
+ ssh:daemon(0, [{subsystems, SubSystems}|Options]);
+ root_with_cwd ->
+ RootDir = filename:join(PrivDir, root_with_cwd),
+ CWD = filename:join(RootDir, home),
+ SubSystems = [ssh_sftpd:subsystem_spec([{root, RootDir}, {cwd, CWD}])],
+ ssh:daemon(0, [{subsystems, SubSystems}|Options]);
+ relative_path ->
+ SubSystems = [ssh_sftpd:subsystem_spec([{cwd, PrivDir}])],
+ ssh:daemon(0, [{subsystems, SubSystems}|Options]);
+ open_file_dir_v5 ->
+ SubSystems = [ssh_sftpd:subsystem_spec([{cwd, PrivDir}])],
+ ssh:daemon(0, [{subsystems, SubSystems}|Options]);
+ open_file_dir_v6 ->
+ SubSystems = [ssh_sftpd:subsystem_spec([{cwd, PrivDir},
+ {sftpd_vsn, 6}])],
+ ssh:daemon(0, [{subsystems, SubSystems}|Options]);
_ ->
SubSystems = [ssh_sftpd:subsystem_spec([])],
ssh:daemon(0, [{subsystems, SubSystems}|Options])
end,
- {ok,Dinf} = ssh:daemon_info(Sftpd),
- Port = proplists:get_value(port, Dinf),
+
+ Port = ssh_test_lib:daemon_port(Sftpd),
Cm = ssh_test_lib:connect(Port,
[{user_dir, ClientUserDir},
{user, ?USER}, {password, ?PASSWD},
{user_interaction, false},
- {silently_accept_hosts, true},
- {pwdfun, fun(_,_) -> true end}]),
+ {silently_accept_hosts, true}]),
{ok, Channel} =
ssh_connection:session_channel(Cm, ?XFER_WINDOW_SIZE,
?XFER_PACKET_SIZE, ?TIMEOUT),
@@ -158,7 +187,7 @@ init_per_testcase(TestCase, Config) ->
[{sftp, {Cm, Channel}}, {sftpd, Sftpd }| Config].
end_per_testcase(_TestCase, Config) ->
- ssh_sftpd:stop(proplists:get_value(sftpd, Config)),
+ catch ssh:stop_daemon(proplists:get_value(sftpd, Config)),
{Cm, Channel} = proplists:get_value(sftp, Config),
ssh_connection:close(Cm, Channel),
ssh:close(Cm),
@@ -646,6 +675,133 @@ ver6_basic(Config) when is_list(Config) ->
open_file(PrivDir, Cm, Channel, ReqId,
?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
?SSH_FXF_OPEN_EXISTING).
+
+%%--------------------------------------------------------------------
+access_outside_root() ->
+ [{doc, "Try access files outside the tree below RootDir"}].
+access_outside_root(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ BaseDir = filename:join(PrivDir, access_outside_root),
+ %% A file outside the tree below RootDir which is BaseDir/a
+ %% Make the file BaseDir/bad :
+ BadFilePath = filename:join([BaseDir, bad]),
+ ok = file:write_file(BadFilePath, <<>>),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ %% Try to access a file parallell to the RootDir:
+ try_access("/../bad", Cm, Channel, 0),
+ %% Try to access the same file via the CWD which is /b relative to the RootDir:
+ try_access("../../bad", Cm, Channel, 1).
+
+
+try_access(Path, Cm, Channel, ReqId) ->
+ Return =
+ open_file(Path, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ ct:log("Try open ~p -> ~p",[Path,Return]),
+ case Return of
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), _Handle0/binary>>, _} ->
+ ct:fail("Could open a file outside the root tree!");
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(Code), Rest/binary>>, <<>>} ->
+ case Code of
+ ?SSH_FX_FILE_IS_A_DIRECTORY ->
+ ct:log("Got the expected SSH_FX_FILE_IS_A_DIRECTORY status",[]),
+ ok;
+ ?SSH_FX_FAILURE ->
+ ct:log("Got the expected SSH_FX_FAILURE status",[]),
+ ok;
+ _ ->
+ case Rest of
+ <<?UINT32(Len), Txt:Len/binary, _/binary>> ->
+ ct:fail("Got unexpected SSH_FX_code: ~p (~p)",[Code,Txt]);
+ _ ->
+ ct:fail("Got unexpected SSH_FX_code: ~p",[Code])
+ end
+ end;
+ _ ->
+ ct:fail("Completly unexpected return: ~p", [Return])
+ end.
+
+%%--------------------------------------------------------------------
+root_with_cwd() ->
+ [{doc, "Check if files are found, if the CWD and Root are specified"}].
+root_with_cwd(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ RootDir = filename:join(PrivDir, root_with_cwd),
+ CWD = filename:join(RootDir, home),
+ FileName = "root_with_cwd.txt",
+ FilePath = filename:join(CWD, FileName),
+ ok = filelib:ensure_dir(FilePath),
+ ok = file:write_file(FilePath ++ "0", <<>>),
+ ok = file:write_file(FilePath ++ "1", <<>>),
+ ok = file:write_file(FilePath ++ "2", <<>>),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ ReqId0 = 0,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId0), _Handle0/binary>>, _} =
+ open_file(FileName ++ "0", Cm, Channel, ReqId0,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ ReqId1 = 1,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId1), _Handle1/binary>>, _} =
+ open_file("./" ++ FileName ++ "1", Cm, Channel, ReqId1,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ ReqId2 = 2,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId2), _Handle2/binary>>, _} =
+ open_file("/home/" ++ FileName ++ "2", Cm, Channel, ReqId2,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING).
+
+%%--------------------------------------------------------------------
+relative_path() ->
+ [{doc, "Test paths relative to CWD when opening a file handle."}].
+relative_path(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ FileName = "test_relative_path.txt",
+ FilePath = filename:join(PrivDir, FileName),
+ ok = filelib:ensure_dir(FilePath),
+ ok = file:write_file(FilePath, <<>>),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ ReqId = 0,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), _Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING).
+
+%%--------------------------------------------------------------------
+open_file_dir_v5() ->
+ [{doc, "Test if open_file fails when opening existing directory."}].
+open_file_dir_v5(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ FileName = "open_file_dir_v5",
+ FilePath = filename:join(PrivDir, FileName),
+ ok = filelib:ensure_dir(FilePath),
+ ok = file:make_dir(FilePath),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ ReqId = 0,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_FAILURE), _/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING).
+
+%%--------------------------------------------------------------------
+open_file_dir_v6() ->
+ [{doc, "Test if open_file fails when opening existing directory."}].
+open_file_dir_v6(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ FileName = "open_file_dir_v6",
+ FilePath = filename:join(PrivDir, FileName),
+ ok = filelib:ensure_dir(FilePath),
+ ok = file:make_dir(FilePath),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ ReqId = 0,
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(?SSH_FX_FILE_IS_A_DIRECTORY), _/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
@@ -688,9 +844,7 @@ reply(Cm, Channel, RBuf) ->
30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
-
open_file(File, Cm, Channel, ReqId, Access, Flags) ->
-
Data = list_to_binary([?uint32(ReqId),
?binary(list_to_binary(File)),
?uint32(Access),
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
index 56a33d6349..417b5c4f16 100644
--- a/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ b/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -65,6 +65,7 @@ init_per_suite(Config) ->
{ok, FileInfo} = file:read_file_info(FileName),
ok = file:write_file_info(FileName,
FileInfo#file_info{mode = 8#400}),
+ ssh_test_lib:setup_rsa(DataDir, PrivDir),
ssh_test_lib:setup_dsa(DataDir, PrivDir),
Config
end).
@@ -73,6 +74,7 @@ end_per_suite(Config) ->
UserDir = filename:join(proplists:get_value(priv_dir, Config), nopubkey),
file:del_dir(UserDir),
SysDir = proplists:get_value(priv_dir, Config),
+ ssh_test_lib:clean_rsa(SysDir),
ssh_test_lib:clean_dsa(SysDir),
ok.
@@ -136,7 +138,7 @@ init_per_testcase(TestCase, Config) ->
[{port, Port}, {sftp, {ChannelPid, Connection}}, {sftpd, Sftpd} | NewConfig].
end_per_testcase(_TestCase, Config) ->
- catch ssh_sftpd:stop(proplists:get_value(sftpd, Config)),
+ catch ssh:stop_daemon(proplists:get_value(sftpd, Config)),
{Sftp, Connection} = proplists:get_value(sftp, Config),
catch ssh_sftp:stop_channel(Sftp),
catch ssh:close(Connection),
@@ -187,7 +189,6 @@ quit(Config) when is_list(Config) ->
timer:sleep(5000),
{ok, NewSftp, _Conn} = ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
- {pwdfun, fun(_,_) -> true end},
{user_dir, UserDir},
{user, ?USER}, {password, ?PASSWD}]),
diff --git a/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/id_rsa
new file mode 100644
index 0000000000..9d7e0dd5fb
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_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_sftpd_erlclient_SUITE_data/ssh_host_rsa_key b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_rsa_key
new file mode 100644
index 0000000000..79968bdd7d
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_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_sftpd_erlclient_SUITE_data/ssh_host_rsa_key.pub b/lib/ssh/test/ssh_sftpd_erlclient_SUITE_data/ssh_host_rsa_key.pub
new file mode 100644
index 0000000000..75d2025c71
--- /dev/null
+++ b/lib/ssh/test/ssh_sftpd_erlclient_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_sup_SUITE.erl b/lib/ssh/test/ssh_sup_SUITE.erl
index ff53e1c4c6..3920a1c592 100644
--- a/lib/ssh/test/ssh_sup_SUITE.erl
+++ b/lib/ssh/test/ssh_sup_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -41,7 +41,8 @@ suite() ->
{timetrap,{seconds,100}}].
all() ->
- [default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile].
+ [default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile,
+ killed_acceptor_restarts].
groups() ->
[].
@@ -100,6 +101,7 @@ default_tree(Config) when is_list(Config) ->
?wait_match([], supervisor:which_children(sshc_sup)),
?wait_match([], supervisor:which_children(sshd_sup)).
+%%-------------------------------------------------------------------------
sshc_subtree() ->
[{doc, "Make sure the sshc subtree is correct"}].
sshc_subtree(Config) when is_list(Config) ->
@@ -128,27 +130,31 @@ sshc_subtree(Config) when is_list(Config) ->
ssh:close(Pid2),
?wait_match([], supervisor:which_children(sshc_sup)).
+%%-------------------------------------------------------------------------
sshd_subtree() ->
[{doc, "Make sure the sshd subtree is correct"}].
sshd_subtree(Config) when is_list(Config) ->
HostIP = proplists:get_value(host_ip, Config),
Port = proplists:get_value(port, Config),
SystemDir = proplists:get_value(data_dir, Config),
- ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2},
- {user_passwords,
- [{?USER, ?PASSWD}]}]),
+ {ok,Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]}]),
- ?wait_match([{{server,ssh_system_sup, HostIP, Port, ?DEFAULT_PROFILE},
+ ct:log("Expect HostIP=~p, Port=~p, Daemon=~p",[HostIP,Port,Daemon]),
+ ?wait_match([{{server,ssh_system_sup, ListenIP, Port, ?DEFAULT_PROFILE},
Daemon, supervisor,
[ssh_system_sup]}],
supervisor:which_children(sshd_sup),
- Daemon),
+ [ListenIP,Daemon]),
+ true = ssh_test_lib:match_ip(HostIP, ListenIP),
check_sshd_system_tree(Daemon, Config),
ssh:stop_daemon(HostIP, Port),
ct:sleep(?WAIT_FOR_SHUTDOWN),
?wait_match([], 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) ->
@@ -157,34 +163,105 @@ sshd_subtree_profile(Config) when is_list(Config) ->
Profile = proplists:get_value(profile, Config),
SystemDir = proplists:get_value(data_dir, Config),
- {ok, _} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2},
- {user_passwords,
- [{?USER, ?PASSWD}]},
- {profile, Profile}]),
- ?wait_match([{{server,ssh_system_sup, HostIP,Port,Profile},
+ {ok, Daemon} = ssh:daemon(HostIP, Port, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]},
+ {profile, Profile}]),
+ ct:log("Expect HostIP=~p, Port=~p, Profile=~p, Daemon=~p",[HostIP,Port,Profile,Daemon]),
+ ?wait_match([{{server,ssh_system_sup, ListenIP,Port,Profile},
Daemon, supervisor,
[ssh_system_sup]}],
supervisor:which_children(sshd_sup),
- Daemon),
+ [ListenIP,Daemon]),
+ true = ssh_test_lib:match_ip(HostIP, ListenIP),
check_sshd_system_tree(Daemon, Config),
ssh:stop_daemon(HostIP, Port, Profile),
ct:sleep(?WAIT_FOR_SHUTDOWN),
?wait_match([], supervisor:which_children(sshd_sup)).
+%%-------------------------------------------------------------------------
+killed_acceptor_restarts(Config) ->
+ Profile = proplists:get_value(profile, Config),
+ SystemDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(userdir, Config),
+ {ok, DaemonPid} = ssh:daemon(0, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords, [{?USER, ?PASSWD}]},
+ {profile, Profile}]),
+
+ {ok, DaemonPid2} = ssh:daemon(0, [{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords, [{?USER, ?PASSWD}]},
+ {profile, Profile}]),
+
+ Port = ssh_test_lib:daemon_port(DaemonPid),
+ Port2 = ssh_test_lib:daemon_port(DaemonPid2),
+ true = (Port /= Port2),
+
+ ct:pal("~s",[lists:flatten(ssh_info:string())]),
+
+ {ok,[{AccPid,ListenAddr,Port}]} = acceptor_pid(DaemonPid),
+ {ok,[{AccPid2,ListenAddr,Port2}]} = acceptor_pid(DaemonPid2),
+
+ true = (AccPid /= AccPid2),
+
+ %% Connect first client and check it is alive:
+ {ok,C1} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
+ [{client_version,_}] = ssh:connection_info(C1,[client_version]),
+
+ %% Make acceptor restart:
+ exit(AccPid, kill),
+ %% Check it is a new acceptor:
+ {ok,[{AccPid1,ListenAddr,Port}]} = acceptor_pid(DaemonPid),
+ true = (AccPid /= AccPid1),
+ true = (AccPid2 /= AccPid1),
+
+ %% Connect second client and check it is alive:
+ {ok,C2} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
+ [{client_version,_}] = ssh:connection_info(C2,[client_version]),
+
+ ct:pal("~s",[lists:flatten(ssh_info:string())]),
+
+ %% Check first client is still alive:
+ [{client_version,_}] = ssh:connection_info(C1,[client_version]),
+
+ ok = ssh:stop_daemon(DaemonPid2),
+ timer:sleep(15000),
+ [{client_version,_}] = ssh:connection_info(C1,[client_version]),
+ [{client_version,_}] = ssh:connection_info(C2,[client_version]),
+
+ ok = ssh:stop_daemon(DaemonPid),
+ timer:sleep(15000),
+ {error,closed} = ssh:connection_info(C1,[client_version]),
+ {error,closed} = ssh:connection_info(C2,[client_version]).
+
+%%-------------------------------------------------------------------------
+%% Help functions
+%%-------------------------------------------------------------------------
check_sshd_system_tree(Daemon, Config) ->
Host = proplists:get_value(host, Config),
Port = proplists:get_value(port, Config),
UserDir = proplists:get_value(userdir, Config),
{ok, Client} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
- {user_interaction, false},
- {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
{{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
supervisor:which_children(Daemon),
- [SubSysSup,AccSup]),
+ [SubSysSup,AccSup]),
?wait_match([{{server,ssh_connection_sup, _,_},
ConnectionSup, supervisor,
@@ -208,4 +285,33 @@ check_sshd_system_tree(Daemon, Config) ->
?wait_match([{_, _,worker,[ssh_channel]}],
supervisor:which_children(ChannelSup)),
ssh:close(Client).
-
+
+acceptor_pid(DaemonPid) ->
+ Parent = self(),
+ Pid = spawn(fun() ->
+ Parent ! {self(), supsearch,
+ [{AccPid,ListenAddr,Port}
+
+ || {{server,ssh_system_sup,ListenAddr,Port,NS},
+ DPid,supervisor,
+ [ssh_system_sup]} <- supervisor:which_children(sshd_sup),
+ DPid == DaemonPid,
+
+ {{ssh_acceptor_sup,L1,P1,NS1},
+ AccSupPid,supervisor,
+ [ssh_acceptor_sup]} <- supervisor:which_children(DaemonPid),
+ L1 == ListenAddr,
+ P1 == Port,
+ NS1 == NS1,
+
+ {{ssh_acceptor_sup,L2,P2,NS2},
+ AccPid,worker,
+ [ssh_acceptor]} <- supervisor:which_children(AccSupPid),
+ L2 == ListenAddr,
+ P2 == Port,
+ NS2 == NS]}
+ end),
+ receive {Pid, supsearch, L} -> {ok,L}
+ after 2000 -> timeout
+ end.
+
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 6233680dce..7b273fecef 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-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -32,15 +32,19 @@
-define(TIMEOUT, 50000).
+%%%----------------------------------------------------------------
connect(Port, Options) when is_integer(Port) ->
connect(hostname(), Port, Options).
connect(any, Port, Options) ->
connect(hostname(), Port, Options);
connect(Host, Port, Options) ->
- {ok, ConnectionRef} = ssh:connect(Host, Port, Options),
+ R = ssh:connect(Host, Port, Options),
+ ct:log("~p:~p ssh:connect(~p, ~p, ~p)~n -> ~p",[?MODULE,?LINE,Host, Port, Options, R]),
+ {ok, ConnectionRef} = R,
ConnectionRef.
+%%%----------------------------------------------------------------
daemon(Options) ->
daemon(any, 0, Options).
@@ -53,23 +57,57 @@ daemon(Host, Options) ->
daemon(Host, Port, Options) ->
ct:log("~p:~p Calling ssh:daemon(~p, ~p, ~p)",[?MODULE,?LINE,Host,Port,Options]),
case ssh:daemon(Host, Port, Options) of
- {ok, Pid} when Host == any ->
- ct:log("ssh:daemon ok (1)",[]),
- {Pid, hostname(), daemon_port(Port,Pid)};
{ok, Pid} ->
- ct:log("ssh:daemon ok (2)",[]),
- {Pid, Host, daemon_port(Port,Pid)};
+ {ok,L} = ssh:daemon_info(Pid),
+ ListenPort = proplists:get_value(port, L),
+ ListenIP = proplists:get_value(ip, L),
+ {Pid, ListenIP, ListenPort};
Error ->
ct:log("ssh:daemon error ~p",[Error]),
Error
end.
+%%%----------------------------------------------------------------
+daemon_port(Pid) -> daemon_port(0, Pid).
+
+
daemon_port(0, Pid) -> {ok,Dinf} = ssh:daemon_info(Pid),
proplists:get_value(port, Dinf);
daemon_port(Port, _) -> Port.
-
+%%%----------------------------------------------------------------
+gen_tcp_connect(Host0, Port, Options) ->
+ Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(Host0)),
+ ct:log("~p:~p gen_tcp:connect(~p, ~p, ~p)~nHost0 = ~p",
+ [?MODULE,?LINE, Host, Port, Options, Host0]),
+ Result = gen_tcp:connect(Host, Port, Options),
+ ct:log("~p:~p Result = ~p", [?MODULE,?LINE, Result]),
+ Result.
+
+%%%----------------------------------------------------------------
+open_sshc(Host0, Port, OptStr) ->
+ open_sshc(Host0, Port, OptStr, "").
+
+open_sshc(Host0, Port, OptStr, ExecStr) ->
+ Cmd = open_sshc_cmd(Host0, Port, OptStr, ExecStr),
+ Result = os:cmd(Cmd),
+ ct:log("~p:~p Result = ~p", [?MODULE,?LINE, Result]),
+ Result.
+
+
+open_sshc_cmd(Host, Port, OptStr) ->
+ open_sshc_cmd(Host, Port, OptStr, "").
+open_sshc_cmd(Host0, Port, OptStr, ExecStr) ->
+ Host = ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(Host0)),
+ Cmd = lists:flatten(["ssh -p ", integer_to_list(Port),
+ " ", OptStr,
+ " ", Host,
+ " ", ExecStr]),
+ ct:log("~p:~p OpenSSH Cmd = ~p", [?MODULE,?LINE, Cmd]),
+ Cmd.
+
+%%%----------------------------------------------------------------
std_daemon(Config, ExtraOpts) ->
PrivDir = proplists:get_value(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -85,6 +123,7 @@ std_daemon1(Config, ExtraOpts) ->
{failfun, fun ssh_test_lib:failfun/2}
| ExtraOpts]).
+%%%----------------------------------------------------------------
std_connect(Config, Host, Port, ExtraOpts) ->
UserDir = proplists:get_value(priv_dir, Config),
_ConnectionRef =
@@ -95,6 +134,7 @@ std_connect(Config, Host, Port, ExtraOpts) ->
{user_interaction, false}
| ExtraOpts]).
+%%%----------------------------------------------------------------
std_simple_sftp(Host, Port, Config) ->
std_simple_sftp(Host, Port, Config, []).
@@ -109,43 +149,49 @@ std_simple_sftp(Host, Port, Config, Opts) ->
ok = ssh:close(ConnectionRef),
Data == ReadData.
+%%%----------------------------------------------------------------
std_simple_exec(Host, Port, Config) ->
std_simple_exec(Host, Port, Config, []).
std_simple_exec(Host, Port, Config, Opts) ->
+ ct:log("~p:~p std_simple_exec",[?MODULE,?LINE]),
ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts),
+ ct:log("~p:~p connected! ~p",[?MODULE,?LINE,ConnectionRef]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId, "23+21-2.", infinity),
- Data = {ssh_cm, ConnectionRef, {data, ChannelId, 0, <<"42\n">>}},
- case ssh_test_lib:receive_exec_result(Data) of
- expected ->
- ok;
- Other ->
- ct:fail(Other)
- end,
- ssh_test_lib:receive_exec_end(ConnectionRef, ChannelId),
- ssh:close(ConnectionRef).
-
-
-start_shell(Port, IOServer, UserDir) ->
- start_shell(Port, IOServer, UserDir, []).
-
-start_shell(Port, IOServer, UserDir, Options) ->
- spawn_link(?MODULE, init_shell, [Port, IOServer, [{user_dir, UserDir}|Options]]).
+ ct:log("~p:~p session_channel ok ~p",[?MODULE,?LINE,ChannelId]),
+ ExecResult = ssh_connection:exec(ConnectionRef, ChannelId, "23+21-2.", infinity),
+ ct:log("~p:~p exec ~p",[?MODULE,?LINE,ExecResult]),
+ case ExecResult of
+ success ->
+ Expected = {ssh_cm, ConnectionRef, {data,ChannelId,0,<<"42\n">>}},
+ case receive_exec_result(Expected) of
+ expected ->
+ ok;
+ Other ->
+ ct:fail(Other)
+ end,
+ receive_exec_end(ConnectionRef, ChannelId),
+ ssh:close(ConnectionRef);
+ _ ->
+ ct:fail(ExecResult)
+ end.
+%%%----------------------------------------------------------------
start_shell(Port, IOServer) ->
- spawn_link(?MODULE, init_shell, [Port, IOServer, []]).
+ start_shell(Port, IOServer, []).
-init_shell(Port, IOServer, UserDir) ->
- Host = hostname(),
- Options = [{user_interaction, false}, {silently_accept_hosts,
- true}] ++ UserDir,
- group_leader(IOServer, self()),
- loop_shell(Host, Port, Options).
+start_shell(Port, IOServer, ExtraOptions) ->
+ spawn_link(
+ fun() ->
+ Host = hostname(),
+ Options = [{user_interaction, false},
+ {silently_accept_hosts,true} | ExtraOptions],
+ group_leader(IOServer, self()),
+ ssh:shell(Host, Port, Options)
+ end).
-loop_shell(Host, Port, Options) ->
- ssh:shell(Host, Port, Options).
+%%%----------------------------------------------------------------
start_io_server() ->
spawn_link(?MODULE, init_io_server, [self()]).
@@ -204,10 +250,19 @@ reply(TestCase, Result) ->
%%ct:log("reply ~p sending ~p ! ~p",[self(), TestCase, Result]),
TestCase ! Result.
-
-
+%%%----------------------------------------------------------------
rcv_expected(Expect, SshPort, Timeout) ->
receive
+ {SshPort, Recvd} when is_function(Expect) ->
+ case Expect(Recvd) of
+ true ->
+ ct:log("Got expected ~p from ~p",[Recvd,SshPort]),
+ catch port_close(SshPort),
+ rcv_lingering(50);
+ false ->
+ ct:log("Got UNEXPECTED ~p~n",[Recvd]),
+ rcv_expected(Expect, SshPort, Timeout)
+ end;
{SshPort, Expect} ->
ct:log("Got expected ~p from ~p",[Expect,SshPort]),
catch port_close(SshPort),
@@ -445,8 +500,12 @@ setup_ecdsa_auth_keys(_Size, Dir, UserDir) ->
setup_auth_keys(Keys, Dir) ->
AuthKeys = public_key:ssh_encode(Keys, auth_keys),
AuthKeysFile = filename:join(Dir, "authorized_keys"),
- file:write_file(AuthKeysFile, AuthKeys).
+ ok = file:write_file(AuthKeysFile, AuthKeys),
+ AuthKeys.
+write_auth_keys(Keys, Dir) ->
+ AuthKeysFile = filename:join(Dir, "authorized_keys"),
+ file:write_file(AuthKeysFile, Keys).
del_dirs(Dir) ->
case file:list_dir(Dir) of
@@ -677,13 +736,16 @@ ssh_type() ->
ssh_type1() ->
try
+ ct:log("~p:~p os:find_executable(\"ssh\")",[?MODULE,?LINE]),
case os:find_executable("ssh") of
false ->
ct:log("~p:~p Executable \"ssh\" not found",[?MODULE,?LINE]),
not_found;
- _ ->
+ Path ->
+ ct:log("~p:~p Found \"ssh\" at ~p",[?MODULE,?LINE,Path]),
case os:cmd("ssh -V") of
- "OpenSSH" ++ _ ->
+ Version = "OpenSSH" ++ _ ->
+ ct:log("~p:~p Found OpenSSH ~p",[?MODULE,?LINE,Version]),
openSSH;
Str ->
ct:log("ssh client ~p is unknown",[Str]),
@@ -767,3 +829,157 @@ open_port(Arg1, ExtraOpts) ->
use_stdio,
overlapped_io, hide %only affects windows
| ExtraOpts]).
+
+%%%----------------------------------------------------------------
+%%% Sleeping
+
+%%% Milli sec
+sleep_millisec(Nms) -> receive after Nms -> ok end.
+
+%%% Micro sec
+sleep_microsec(Nus) ->
+ busy_wait(Nus, erlang:system_time(microsecond)).
+
+busy_wait(Nus, T0) ->
+ T = erlang:system_time(microsecond) - T0,
+ Tleft = Nus - T,
+ if
+ Tleft > 2000 ->
+ sleep_millisec((Tleft-1500) div 1000), % μs -> ms
+ busy_wait(Nus,T0);
+ Tleft > 1 ->
+ busy_wait(Nus, T0);
+ true ->
+ T
+ end.
+
+%%%----------------------------------------------------------------
+%% get_kex_init - helper function to get key_exchange_init_msg
+
+get_kex_init(Conn) ->
+ Ref = make_ref(),
+ {ok,TRef} = timer:send_after(15000, {reneg_timeout,Ref}),
+ get_kex_init(Conn, Ref, TRef).
+
+get_kex_init(Conn, Ref, TRef) ->
+ %% First, validate the key exchange is complete (StateName == connected)
+ {State, S} = sys:get_state(Conn),
+ case expected_state(State) of
+ true ->
+ timer:cancel(TRef),
+ %% 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;
+
+ false ->
+ ct:log("Not in 'connected' state: ~p",[State]),
+ receive
+ {reneg_timeout,Ref} ->
+ ct:log("S = ~p", [S]),
+ ct:fail(reneg_timeout)
+ after 0 ->
+ timer:sleep(100), % If renegotiation is complete we do not
+ % want to exit on the reneg_timeout
+ get_kex_init(Conn, Ref, TRef)
+ end
+ end.
+
+expected_state({ext_info,_,_}) -> true;
+expected_state({connected,_}) -> true;
+expected_state(_) -> false.
+
+%%%----------------------------------------------------------------
+%%% Return a string with N random characters
+%%%
+random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)].
+
+
+create_random_dir(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Name = filename:join(PrivDir, random_chars(15)),
+ case file:make_dir(Name) of
+ ok ->
+ Name;
+ {error,eexist} ->
+ %% The Name already denotes an existing file system object, try again.
+ %% The likelyhood of always generating an existing file name is low
+ create_random_dir(Config)
+ end.
+
+%%%----------------------------------------------------------------
+match_ip(A, B) ->
+ R = match_ip0(A,B) orelse match_ip0(B,A),
+ ct:log("match_ip(~p, ~p) -> ~p",[A, B, R]),
+ R.
+
+match_ip0(A, A) ->
+ true;
+match_ip0(any, _) ->
+ true;
+match_ip0(A, B) ->
+ case match_ip1(A, B) of
+ true ->
+ true;
+ false when is_list(A) ->
+ case inet:parse_address(A) of
+ {ok,IPa} -> match_ip0(IPa, B);
+ _ -> false
+ end;
+ false when is_list(B) ->
+ case inet:parse_address(B) of
+ {ok,IPb} -> match_ip0(A, IPb);
+ _ -> false
+ end;
+ false ->
+ false
+ end.
+
+match_ip1(any, _) -> true;
+match_ip1(loopback, {127,_,_,_}) -> true;
+match_ip1({0,0,0,0}, {127,_,_,_}) -> true;
+match_ip1(loopback, {0,0,0,0,0,0,0,1}) -> true;
+match_ip1({0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,1}) -> true;
+match_ip1(_, _) -> false.
+
+%%%----------------------------------------------------------------
+mangle_connect_address(A) ->
+ mangle_connect_address(A, []).
+
+mangle_connect_address(A, SockOpts) ->
+ mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
+
+loopback(true) -> {0,0,0,0,0,0,0,1};
+loopback(false) -> {127,0,0,1}.
+
+mangle_connect_address1( loopback, V6flg) -> loopback(V6flg);
+mangle_connect_address1( any, V6flg) -> loopback(V6flg);
+mangle_connect_address1({0,0,0,0}, _) -> loopback(false);
+mangle_connect_address1({0,0,0,0,0,0,0,0}, _) -> loopback(true);
+mangle_connect_address1( IP, _) when is_tuple(IP) -> IP;
+mangle_connect_address1(A, _) ->
+ case catch inet:parse_address(A) of
+ {ok, {0,0,0,0}} -> loopback(false);
+ {ok, {0,0,0,0,0,0,0,0}} -> loopback(true);
+ _ -> A
+ end.
+
+%%%----------------------------------------------------------------
+ntoa(A) ->
+ try inet:ntoa(A)
+ of
+ {error,_} when is_atom(A) -> atom_to_list(A);
+ {error,_} when is_list(A) -> A;
+ S when is_list(S) -> S
+ catch
+ _:_ when is_atom(A) -> atom_to_list(A);
+ _:_ when is_list(A) -> A
+ end.
+
diff --git a/lib/ssh/test/ssh_to_openssh_SUITE.erl b/lib/ssh/test/ssh_to_openssh_SUITE.erl
index a914938c41..4d6aa93d4e 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-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -29,13 +29,14 @@
-define(TIMEOUT, 50000).
-define(SSH_DEFAULT_PORT, 22).
+-define(REKEY_DATA_TMO, 65000).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() ->
- [{timetrap,{seconds,20}}].
+ [{timetrap,{seconds,60}}].
all() ->
case os:find_executable("ssh") of
@@ -55,10 +56,12 @@ groups() ->
erlang_client_openssh_server_publickey_rsa,
erlang_client_openssh_server_password,
erlang_client_openssh_server_kexs,
- erlang_client_openssh_server_nonexistent_subsystem
+ erlang_client_openssh_server_nonexistent_subsystem,
+ erlang_client_openssh_server_renegotiate
]},
{erlang_server, [], [erlang_server_openssh_client_public_key_dsa,
- erlang_server_openssh_client_public_key_rsa
+ erlang_server_openssh_client_public_key_rsa,
+ erlang_server_openssh_client_renegotiate
]}
].
@@ -104,6 +107,14 @@ init_per_testcase(erlang_server_openssh_client_public_key_rsa, Config) ->
chk_key(sshc, 'ssh-rsa', ".ssh/id_rsa", Config);
init_per_testcase(erlang_client_openssh_server_publickey_dsa, Config) ->
chk_key(sshd, 'ssh-dss', ".ssh/id_dsa", Config);
+init_per_testcase(erlang_client_openssh_server_publickey_rsa, Config) ->
+ chk_key(sshd, 'ssh-rsa', ".ssh/id_rsa", Config);
+
+init_per_testcase(erlang_server_openssh_client_renegotiate, Config) ->
+ case os:type() of
+ {unix,_} -> ssh:start(), Config;
+ Type -> {skip, io_lib:format("Unsupported test on ~p",[Type])}
+ end;
init_per_testcase(_TestCase, Config) ->
ssh:start(),
Config.
@@ -145,7 +156,7 @@ erlang_shell_client_openssh_server(Config) when is_list(Config) ->
IO = ssh_test_lib:start_io_server(),
Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO),
IO ! {input, self(), "echo Hej\n"},
- receive_hej(),
+ receive_data("Hej", undefined),
IO ! {input, self(), "exit\n"},
receive_logout(),
receive_normal_exit(Shell).
@@ -314,78 +325,162 @@ erlang_client_openssh_server_setenv(Config) when is_list(Config) ->
%% setenv not meaningfull on erlang ssh daemon!
%%--------------------------------------------------------------------
-erlang_client_openssh_server_publickey_rsa() ->
- [{doc, "Validate using rsa publickey."}].
-erlang_client_openssh_server_publickey_rsa(Config) when is_list(Config) ->
- {ok,[[Home]]} = init:get_argument(home),
- KeyFile = filename:join(Home, ".ssh/id_rsa"),
- case file:read_file(KeyFile) of
- {ok, Pem} ->
- case public_key:pem_decode(Pem) of
- [{_,_, not_encrypted}] ->
- ConnectionRef =
- ssh_test_lib:connect(?SSH_DEFAULT_PORT,
- [{public_key_alg, ssh_rsa},
- {user_interaction, false},
- silently_accept_hosts]),
- {ok, Channel} =
- ssh_connection:session_channel(ConnectionRef, infinity),
- ok = ssh_connection:close(ConnectionRef, Channel),
- ok = ssh:close(ConnectionRef);
- _ ->
- {skip, {error, "Has pass phrase can not be used by automated test case"}}
- end;
- _ ->
- {skip, "no ~/.ssh/id_rsa"}
- end.
-
+erlang_client_openssh_server_publickey_rsa(Config) ->
+ erlang_client_openssh_server_publickey_X(Config, 'ssh-rsa').
+
+erlang_client_openssh_server_publickey_dsa(Config) ->
+ erlang_client_openssh_server_publickey_X(Config, 'ssh-dss').
-%%--------------------------------------------------------------------
-erlang_client_openssh_server_publickey_dsa() ->
- [{doc, "Validate using dsa publickey."}].
-erlang_client_openssh_server_publickey_dsa(Config) when is_list(Config) ->
+
+erlang_client_openssh_server_publickey_X(Config, Alg) ->
ConnectionRef =
- ssh_test_lib:connect(?SSH_DEFAULT_PORT,
- [{public_key_alg, ssh_dsa},
- {user_interaction, false},
- silently_accept_hosts]),
+ ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [{pref_public_key_algs, [Alg]},
+ {user_interaction, false},
+ {auth_methods, "publickey"},
+ silently_accept_hosts]),
{ok, Channel} =
- ssh_connection:session_channel(ConnectionRef, infinity),
+ ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh_connection:close(ConnectionRef, Channel),
ok = ssh:close(ConnectionRef).
%%--------------------------------------------------------------------
erlang_server_openssh_client_public_key_dsa() ->
- [{timetrap, {seconds,(?TIMEOUT div 1000)+10}},
- {doc, "Validate using dsa publickey."}].
+ [{timetrap, {seconds,(?TIMEOUT div 1000)+10}}].
erlang_server_openssh_client_public_key_dsa(Config) when is_list(Config) ->
- erlang_server_openssh_client_public_key_X(Config, ssh_dsa).
+ erlang_server_openssh_client_public_key_X(Config, 'ssh-dss').
-erlang_server_openssh_client_public_key_rsa() ->
- [{timetrap, {seconds,(?TIMEOUT div 1000)+10}},
- {doc, "Validate using rsa publickey."}].
+erlang_server_openssh_client_public_key_rsa() ->
+ [{timetrap, {seconds,(?TIMEOUT div 1000)+10}}].
erlang_server_openssh_client_public_key_rsa(Config) when is_list(Config) ->
- erlang_server_openssh_client_public_key_X(Config, ssh_rsa).
+ erlang_server_openssh_client_public_key_X(Config, 'ssh-rsa').
-erlang_server_openssh_client_public_key_X(Config, PubKeyAlg) ->
+erlang_server_openssh_client_public_key_X(Config, Alg) ->
SystemDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
KnownHosts = filename:join(PrivDir, "known_hosts"),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {public_key_alg, PubKeyAlg},
+ {preferred_algorithms,[{public_key, [Alg]}]},
+ {auth_methods, "publickey"},
{failfun, fun ssh_test_lib:failfun/2}]),
-
ct:sleep(500),
- Cmd = "ssh -p " ++ integer_to_list(Port) ++
- " -o UserKnownHostsFile=" ++ KnownHosts ++
- " " ++ Host ++ " 1+1.",
+ Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o StrictHostKeyChecking=no"],
+ "1+1."),
OpenSsh = ssh_test_lib:open_port({spawn, Cmd}),
ssh_test_lib:rcv_expected({data,<<"2\n">>}, OpenSsh, ?TIMEOUT),
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+%% Test that the Erlang/OTP server can renegotiate with openSSH
+erlang_server_openssh_client_renegotiate(Config) ->
+ _PubKeyAlg = ssh_rsa,
+ SystemDir = proplists:get_value(data_dir, Config),
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KnownHosts = filename:join(PrivDir, "known_hosts"),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ ct:sleep(500),
+
+ RenegLimitK = 3,
+ DataFile = filename:join(PrivDir, "renegotiate_openssh_client.data"),
+ Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a),
+ ok = file:write_file(DataFile, Data),
+
+ Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
+ [" -o UserKnownHostsFile=", KnownHosts,
+ " -o StrictHostKeyChecking=no",
+ " -o RekeyLimit=",integer_to_list(RenegLimitK),"K"]),
+
+
+ OpenSsh = ssh_test_lib:open_port({spawn, Cmd++" < "++DataFile}),
+
+ Expect = fun({data,R}) ->
+ try
+ NonAlphaChars = [C || C<-lists:seq(1,255),
+ not lists:member(C,lists:seq($a,$z)),
+ not lists:member(C,lists:seq($A,$Z))
+ ],
+ Lines = string:tokens(binary_to_list(R), NonAlphaChars),
+ lists:any(fun(L) -> length(L)>1 andalso lists:prefix(L, Data) end,
+ Lines)
+ catch
+ _:_ -> false
+ end;
+
+ ({exit_status,E}) when E=/=0 ->
+ ct:log("exit_status ~p",[E]),
+ throw({skip,"exit status"});
+
+ (_) ->
+ false
+ end,
+
+ try
+ ssh_test_lib:rcv_expected(Expect, OpenSsh, ?TIMEOUT)
+ of
+ _ ->
+ %% Unfortunately we can't check that there has been a renegotiation, just trust OpenSSH.
+ ssh:stop_daemon(Pid)
+ catch
+ throw:{skip,R} -> {skip,R}
+ end.
+
+%%--------------------------------------------------------------------
+erlang_client_openssh_server_renegotiate(_Config) ->
+ process_flag(trap_exit, true),
+ IO = ssh_test_lib:start_io_server(),
+ Ref = make_ref(),
+ Parent = self(),
+
+ Shell =
+ spawn_link(
+ fun() ->
+ Host = ssh_test_lib:hostname(),
+ Options = [{user_interaction, false},
+ {silently_accept_hosts,true}],
+ group_leader(IO, self()),
+ {ok, ConnRef} = ssh:connect(Host, ?SSH_DEFAULT_PORT, Options),
+ ct:log("Parent = ~p, IO = ~p, Shell = ~p, ConnRef = ~p~n",[Parent, IO, self(), ConnRef]),
+ case ssh_connection:session_channel(ConnRef, infinity) of
+ {ok,ChannelId} ->
+ success = ssh_connection:ptty_alloc(ConnRef, ChannelId, []),
+ Args = [{channel_cb, ssh_shell},
+ {init_args,[ConnRef, ChannelId]},
+ {cm, ConnRef}, {channel_id, ChannelId}],
+ {ok, State} = ssh_channel:init([Args]),
+ Parent ! {ok, Ref, ConnRef},
+ ssh_channel:enter_loop(State);
+ Error ->
+ Parent ! {error, Ref, Error}
+ end,
+ receive
+ nothing -> ok
+ end
+ end),
+
+ receive
+ {error, Ref, Error} ->
+ ct:fail("Error=~p",[Error]);
+ {ok, Ref, ConnectionRef} ->
+ IO ! {input, self(), "echo Hej1\n"},
+ receive_data("Hej1", ConnectionRef),
+ Kex1 = ssh_test_lib:get_kex_init(ConnectionRef),
+ ssh_connection_handler:renegotiate(ConnectionRef),
+ IO ! {input, self(), "echo Hej2\n"},
+ receive_data("Hej2", ConnectionRef),
+ Kex2 = ssh_test_lib:get_kex_init(ConnectionRef),
+ IO ! {input, self(), "exit\n"},
+ receive_logout(),
+ receive_normal_exit(Shell),
+ true = (Kex1 =/= Kex2)
+ end.
+
+%%--------------------------------------------------------------------
erlang_client_openssh_server_password() ->
[{doc, "Test client password option"}].
erlang_client_openssh_server_password(Config) when is_list(Config) ->
@@ -440,27 +535,30 @@ erlang_client_openssh_server_nonexistent_subsystem(Config) when is_list(Config)
%%--------------------------------------------------------------------
%%% Internal functions -----------------------------------------------
%%--------------------------------------------------------------------
-receive_hej() ->
+receive_data(Data, Conn) ->
receive
- <<"Hej", _binary>> = Hej ->
- ct:log("Expected result: ~p~n", [Hej]);
- <<"Hej\n", _binary>> = Hej ->
- ct:log("Expected result: ~p~n", [Hej]);
- <<"Hej\r\n", _/binary>> = Hej ->
- ct:log("Expected result: ~p~n", [Hej]);
- Info ->
- Lines = binary:split(Info, [<<"\r\n">>], [global]),
- case lists:member(<<"Hej">>, Lines) of
+ Info when is_binary(Info) ->
+ Lines = string:tokens(binary_to_list(Info), "\r\n "),
+ case lists:member(Data, Lines) of
true ->
- ct:log("Expected result found in lines: ~p~n", [Lines]),
+ ct:log("Expected result ~p found in lines: ~p~n", [Data,Lines]),
ok;
false ->
ct:log("Extra info: ~p~n", [Info]),
- receive_hej()
- end
- after
- 30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end.
+ receive_data(Data, Conn)
+ end;
+ Other ->
+ ct:log("Unexpected: ~p",[Other]),
+ receive_data(Data, Conn)
+ after
+ 30000 ->
+ {State, _} = case Conn of
+ undefined -> {'??','??'};
+ _ -> sys:get_state(Conn)
+ end,
+ ct:log("timeout ~p:~p~nExpect ~p~nState = ~p",[?MODULE,?LINE,Data,State]),
+ ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
receive_logout() ->
receive
diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl
index e34071af99..8de550af15 100644
--- a/lib/ssh/test/ssh_trpt_test_lib.erl
+++ b/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2015. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2017. 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
@@ -85,15 +85,18 @@ exec(Op, S0=#s{}) ->
throw:Term ->
report_trace(throw, Term, S1),
- throw(Term);
+ throw({Term,Op});
error:Error ->
report_trace(error, Error, S1),
- error(Error);
+ error({Error,Op});
exit:Exit ->
report_trace(exit, Exit, S1),
- exit(Exit)
+ exit({Exit,Op});
+ Cls:Err ->
+ ct:pal("Class=~p, Error=~p", [Cls,Err]),
+ error({"fooooooO",Op})
end;
exec(Op, {ok,S=#s{}}) -> exec(Op, S);
exec(_, Error) -> Error.
@@ -111,20 +114,20 @@ op({accept,Opts}, S) when ?role(S) == server ->
{ok,Socket} = gen_tcp:accept(S#s.listen_socket, S#s.timeout),
{Host,_Port} = ok(inet:sockname(Socket)),
S#s{socket = Socket,
- ssh = init_ssh(server,Socket,[{host,host(Host)}|Opts]),
+ ssh = init_ssh(server, Socket, host(Host), Opts),
return_value = ok};
%%%---- Client ops
op({connect,Host,Port,Opts}, S) when ?role(S) == undefined ->
Socket = ok(gen_tcp:connect(host(Host), Port, mangle_opts([]))),
S#s{socket = Socket,
- ssh = init_ssh(client, Socket, [{host,host(Host)}|Opts]),
+ ssh = init_ssh(client, Socket, host(Host), Opts),
return_value = ok};
%%%---- ops for both client and server
op(close_socket, S) ->
- catch tcp_gen:close(S#s.socket),
- catch tcp_gen:close(S#s.listen_socket),
+ catch gen_tcp:close(S#s.socket),
+ catch gen_tcp:close(S#s.listen_socket),
S#s{socket = undefined,
listen_socket = undefined,
return_value = ok};
@@ -293,12 +296,14 @@ instantiate(X, _S) ->
%%%================================================================
%%%
-init_ssh(Role, Socket, Options0) ->
- Options = [{user_interaction, false},
- {vsn, {2,0}},
- {id_string, "ErlangTestLib"}
- | Options0],
- ssh_connection_handler:init_ssh_record(Role, Socket, Options).
+init_ssh(Role, Socket, Host, UserOptions0) ->
+ UserOptions = [{user_interaction, false},
+ {vsn, {2,0}},
+ {id_string, "ErlangTestLib"}
+ | UserOptions0],
+ Opts = ?PUT_INTERNAL_OPT({host,Host},
+ ssh_options:handle_options(Role, UserOptions)),
+ ssh_connection_handler:init_ssh_record(Role, Socket, Opts).
mangle_opts(Options) ->
SysOpts = [{reuseaddr, true},
@@ -309,8 +314,7 @@ mangle_opts(Options) ->
lists:keydelete(K,1,Opts)
end, Options, SysOpts).
-host({0,0,0,0}) -> "localhost";
-host(H) -> H.
+host(H) -> ssh_test_lib:ntoa(ssh_test_lib:mangle_connect_address(H)).
%%%----------------------------------------------------------------
send(S=#s{ssh=C}, hello) ->
@@ -393,6 +397,12 @@ send(S0, {special,Msg,PacketFun}) when is_tuple(Msg),
send_bytes(Packet, S#s{ssh = C, %%inc_send_seq_num(C),
return_value = Msg});
+send(S0, #ssh_msg_newkeys{} = Msg) ->
+ S = opt(print_messages, S0,
+ fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
+ {ok, Packet, C} = ssh_transport:new_keys_message(S#s.ssh),
+ send_bytes(Packet, S#s{ssh = C});
+
send(S0, Msg) when is_tuple(Msg) ->
S = opt(print_messages, S0,
fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
@@ -451,7 +461,10 @@ recv(S0 = #s{}) ->
};
#ssh_msg_kexdh_reply{} ->
{ok, _NewKeys, C} = ssh_transport:handle_kexdh_reply(PeerMsg, S#s.ssh),
- S#s{ssh=C#ssh{send_sequence=S#s.ssh#ssh.send_sequence}}; % Back the number
+ S#s{ssh = (S#s.ssh)#ssh{shared_secret = C#ssh.shared_secret,
+ exchanged_hash = C#ssh.exchanged_hash,
+ session_id = C#ssh.session_id}};
+ %%%S#s{ssh=C#ssh{send_sequence=S#s.ssh#ssh.send_sequence}}; % Back the number
#ssh_msg_newkeys{} ->
{ok, C} = ssh_transport:handle_new_keys(PeerMsg, S#s.ssh),
S#s{ssh=C};
diff --git a/lib/ssh/test/ssh_upgrade_SUITE.erl b/lib/ssh/test/ssh_upgrade_SUITE.erl
index b5b27c369a..7b9b109fa1 100644
--- a/lib/ssh/test/ssh_upgrade_SUITE.erl
+++ b/lib/ssh/test/ssh_upgrade_SUITE.erl
@@ -199,6 +199,4 @@ close(#state{server = Server,
connection = undefined}.
-random_contents() -> list_to_binary( random_chars(3) ).
-
-random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)].
+random_contents() -> list_to_binary( ssh_test_lib:random_chars(3) ).
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index b165928877..7208baca6e 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,5 +1,5 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.3
+SSH_VSN = 4.5
APP_VSN = "ssh-$(SSH_VSN)"