File feature-fix-update-ssh-stack.patch of Package erlang.42826
Index: otp-OTP-23.3.4.19/lib/ssh/doc/html/.gitignore
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/doc/html/.gitignore
@@ -0,0 +1 @@
+*
Index: otp-OTP-23.3.4.19/lib/ssh/doc/specs/.gitignore
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/doc/specs/.gitignore
@@ -0,0 +1 @@
+specs_*.xml
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/notes.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/notes.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2020</year>
+ <year>2004</year><year>2024</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -30,6 +30,1184 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 5.1.4.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>With this change user space buffers are used to limit
+ ssh hello message size instead of kernel buffers</p>
+ <p>
+ Own Id: OTP-19839 Aux Id: ERIERL-1273, PR-10350 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.12</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Option max_handles can be configured for sshd running
+ SFTP. The positive integer value limits amount of file
+ handles opened for a connection (by default 4096 is
+ used).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-19701 Aux Id: CVE-2025-48041, PR-10157 </p>
+ </item>
+ <item>
+ <p>Avoid decoding KEX messages providing too many
+ algorithms. This change does not introduce new limitation
+ but assures it is enforced earlier in processing chain.
+ Adjustments in error logging during handshake.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-19741 Aux Id: CVE-2025-48040, PR-10162 </p>
+ </item>
+ <item>
+ <p>A new 'max_path' option is now available in the sshd
+ configuration, allowing administrators to set the maximum
+ allowable path length. By default, this value is set to
+ 4096 characters.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-19742 Aux Id: CVE-2025-48039, PR-10155 </p>
+ </item>
+ <item>
+ <p>Reject file handles exceeding size specified in RFCs
+ (256 bytes).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-19748 Aux Id: CVE-2025-48038, PR-10156 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.11</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fix file handle id generation.</p>
+ <p>
+ Own Id: OTP-19691 Aux Id: PR-10003 </p>
+ </item>
+ <item>
+ <p>Fixes a badmatch error, when SFTP operation cannot be
+ processed due to channel closed in parallel.</p>
+ <p>
+ Own Id: OTP-19707 Aux Id: GH-9655, PR-10035, PR-10036 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Various channel closing robustness improvements. Avoid
+ crashes when channel handling process closes channel and
+ immediately exits. Avoid breaking the protocol by sending
+ duplicated channel-close messages. Cleanup channels which
+ timeout during closing procedure.</p>
+ <p>
+ Own Id: OTP-19634 Aux Id: GH-9102, PR-9103 </p>
+ </item>
+ <item>
+ <p>Improved interoperability with clients acting as
+ Paramiko.</p>
+ <p>
+ Own Id: OTP-19637 Aux Id: GH-6463, PR-9838 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fix KEX strict implementation according to
+ draft-miller-sshm-strict-kex-01 document.</p>
+ <p>
+ Own Id: OTP-19625 Aux Id: CVE-2025-46712 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Reception of wrong Unicode does not cause unnecessary
+ processing. US-ASCII fields are not decoded as
+ Unicode.</p>
+ <p>
+ Own Id: OTP-19582 Aux Id: PR-9679 </p>
+ </item>
+ <item>
+ <p>SSH daemon disconnects upon receiving connection
+ protocol message for unauthenticated used.</p>
+ <p>Thanks to Fabian Bäumer, Marcel Maehren, Marcus
+ Brinkmann, Nurullah Erinola, Jörg Schwenk (Ruhr
+ University Bochum).</p>
+ <p>
+ Own Id: OTP-19595 Aux Id: CVE-2025-32433 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Reception of malicious KEX init message does not
+ result with ssh daemon excessive memory usage.</p>
+ <p>
+ Own Id: OTP-19543 Aux Id: CVE-2025-30211 </p>
+ </item>
+ <item>
+ <p>Call to ssh:daemon_replace_options does not crash when
+ argument is not a valid daemon ref.</p>
+ <p>
+ Own Id: OTP-19559 Aux Id: GH-9554, PR-9545 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>SFTP packets exceeding max packet size are not
+ processed and dropped.</p>
+ <p>
+ Own Id: OTP-19466 Aux Id: ERIERL-1173, CVE-2025-26618 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>With this change, type specs for
+ ssh:connection_info/1,2 functions are fixed so they
+ include {error, term()} return value.</p>
+ <p>
+ Own Id: OTP-19388 Aux Id: ERIERL-1165, PR-9161 </p>
+ </item>
+ <item>
+ <p>With this change, ssh client accepts a banner sent
+ during processing keyboard interactive user
+ authentication.</p>
+ <p>
+ Own Id: OTP-19392 Aux Id: PR-9139, GH-9065 </p>
+ </item>
+ <item>
+ <p> With this change, large sftp transfers does not hang.
+ Redundant window adjustment are not requested. </p>
+ <p>
+ Own Id: OTP-19435 Aux Id: PR-9309 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>With this change, ssh connection does not crash upon
+ receiving exit-signal message for an already terminated
+ channel.</p>
+ <p>
+ Own Id: OTP-19326 Aux Id: PR-8995, GH-8929 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, a race condition is removed from ssh
+ client connection setup procedure.</p>
+ <p>
+ Own Id: OTP-19124 Aux Id: GH-7550, PR-8766 </p>
+ </item>
+ <item>
+ <p>
+ With this change, ssh:connect is not affected by presence
+ of EXIT message in queue.</p>
+ <p>
+ Own Id: OTP-19246 Aux Id: GH-8223, PR-8854 </p>
+ </item>
+ <item>
+ <p>
+ With this change, ssh appends {active, false} option
+ after socket options received from user - so that false
+ value is always used.</p>
+ <p>
+ Own Id: OTP-19247 Aux Id: PR-8226 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssh daemon started with TCP port number
+ argument will re-try to obtain listen socket before
+ returning error to user.</p>
+ <p>
+ Own Id: OTP-19170 Aux Id: GH-7746 </p>
+ </item>
+ <item>
+ <p>
+ With this change, robustness is improved by monitoring
+ connection handler process before casting socket control
+ notification.</p>
+ <p>
+ Own Id: OTP-19173 Aux Id: PR-8310 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssh client will automatically adjust
+ transfer window size for commands executed remotely over
+ SSH.</p>
+ <p>
+ Own Id: OTP-19057 Aux Id: PR-8345, GH-7483 </p>
+ </item>
+ <item>
+ <p>
+ With this change, race condition between connection
+ closing and automatic window adjustment is fixed.</p>
+ <p>
+ Own Id: OTP-19109 Aux Id: PR-8345 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, owner and group file attributes
+ decoding is fixed and results with value of integer type.</p>
+ <p>
+ Own Id: OTP-19013 Aux Id: GH-7897, PR-8220 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, acceptor_sup is not started for ssh
+ client as it is not needed in that role.</p>
+ <p>
+ Own Id: OTP-18974 </p>
+ </item>
+ <item>
+ <p>
+ With this change, more secure algorithms are preferred by
+ ssh and documentation is updated to reflect that.</p>
+ <p>
+ Own Id: OTP-18986 </p>
+ </item>
+ <item>
+ <p>
+ With this change, KEX strict terminal message is emitted
+ with debug verbosity.</p>
+ <p>
+ Own Id: OTP-19002 Aux Id: ERIERL-1041 </p>
+ </item>
+ <item>
+ <p>
+ Fix reading of password for ssh client when in
+ <c>user_interactive</c> mode.</p>
+ <p>
+ Own Id: OTP-19007 Aux Id: ERIERL-1049 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, Curve25519 and Curve448 KEX methods
+ become most preferred (related to RFC8731).</p>
+ <p>
+ Own Id: OTP-18964 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change (being response to CVE-2023-48795), ssh
+ can negotiate "strict KEX" OpenSSH extension with peers
+ supporting it; also 'chacha20-poly1305@openssh.com'
+ algorithm becomes a less preferred cipher.</p>
+ <p>
+ If strict KEX availability cannot be ensured on both
+ connection sides, affected encryption modes(CHACHA and
+ CBC) can be disabled with standard ssh configuration.
+ This will provide protection against vulnerability, but
+ at a cost of affecting interoperability. See <seeguide
+ marker="configure_algos">Configuring algorithms in
+ SSH</seeguide>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-18897</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Replaced unintentional Erlang Public License 1.1 headers
+ in some files with the intended Apache License 2.0
+ header.</p>
+ <p>
+ Own Id: OTP-18815 Aux Id: PR-7780 </p>
+ </item>
+ <item>
+ <p>
+ Avoid outputting ansi escape sequences to dumb ssh
+ clients.</p>
+ <p>
+ Own Id: OTP-18861 Aux Id: PR-7627 </p>
+ </item>
+ <item>
+ <p>
+ With this change, connection handler does not execute
+ socket operations until it becomes socket owner.
+ Previously errors could occur if connection handler tried
+ to work with socket whose owner exited.</p>
+ <p>
+ Own Id: OTP-18869 Aux Id: PR-7849,GH-7571 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ With this change, reverse search works with ssh shell and
+ non dumb terminals.</p>
+ <p>
+ Own Id: OTP-18730 Aux Id: PR-7499 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Added multiline editing support to ssh clients connected
+ through OTP ssh daemon.</p>
+ <p>
+ Own Id: OTP-18653 Aux Id: PR-7242 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 5.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The ssh_cli has been updated to work with the changes
+ introduced in the new Erlang shell implementation.</p>
+ <p>
+ Own Id: OTP-18231 Aux Id: OTP-17932 PR-6144 </p>
+ </item>
+ <item>
+ <p>
+ Typing <c>Ctrl+L</c> in a shell now clears the screen and
+ redraws the current line instead of only redrawing the
+ current line. To only redraw the current line, you must
+ now type <c>Alt+L</c>. This brings the behaviour of
+ <c>Ctrl+L</c> closer to how bash and other shells work.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-18285 Aux Id: PR-6262 </p>
+ </item>
+ <item>
+ <p>
+ Deprecates <c>dbg:stop_clear/0</c> because it is simply a
+ function alias to <c>dbg:stop/0</c></p>
+ <p>
+ Own Id: OTP-18478 Aux Id: GH-6903 </p>
+ </item>
+ <item>
+ <p> The implementation has been fixed to use
+ <c>proc_lib:init_fail/2,3</c> where appropriate, instead
+ of <c>proc_lib:init_ack/1,2</c>. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-18490 Aux Id: OTP-18471, GH-6339, PR-6843 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, Curve25519 and Curve448 KEX methods
+ become most preferred (related to RFC8731).</p>
+ <p>
+ Own Id: OTP-18964 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, connection handler does not execute
+ socket operations until it becomes socket owner.
+ Previously errors could occur if connection handler tried
+ to work with socket whose owner exited.</p>
+ <p>
+ Own Id: OTP-18869 Aux Id: PR-7849,GH-7571 </p>
+ </item>
+ <item>
+ <p>
+ With this change (being response to CVE-2023-48795), ssh
+ can negotiate "strict KEX" OpenSSH extension with peers
+ supporting it; also 'chacha20-poly1305@openssh.com'
+ algorithm becomes a less preferred cipher.</p>
+ <p>
+ If strict KEX availability cannot be ensured on both
+ connection sides, affected encryption modes(CHACHA and
+ CBC) can be disabled with standard ssh configuration.
+ This will provide protection against vulnerability, but
+ at a cost of affecting interoperability. See <seeguide
+ marker="configure_algos">Configuring algorithms in
+ SSH</seeguide>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-18897</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, PKCS8 formatted private key file is
+ properly decoded and SSH daemon with such key can be
+ started.</p>
+ <p>
+ Own Id: OTP-18446 Aux Id: GH-6475 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Replace size/1 with either tuple_size/1 or byte_size/1</p>
+ <p>
+ The <c>size/1</c> BIF is not optimized by the JIT, and
+ its use can result in worse types for Dialyzer.</p>
+ <p>
+ When one knows that the value being tested must be a
+ tuple, <c>tuple_size/1</c> should always be preferred.</p>
+ <p>
+ When one knows that the value being tested must be a
+ binary, <c>byte_size/1</c> should be preferred. However,
+ <c>byte_size/1</c> also accepts a bitstring (rounding up
+ size to a whole number of bytes), so one must make sure
+ that the call to <c>byte_size/</c> is preceded by a call
+ to <c>is_binary/1</c> to ensure that bitstrings are
+ rejected. Note that the compiler removes redundant calls
+ to <c>is_binary/1</c>, so if one is not sure whether
+ previous code had made sure that the argument is a
+ binary, it does not harm to add an <c>is_binary/1</c>
+ test immediately before the call to <c>byte_size/1</c>.</p>
+ <p>
+ Own Id: OTP-18432 Aux Id:
+ GH-6672,PR-6793,PR-6784,PR-6787,PR-6785,PR-6682,PR-6800,PR-6797,PR-6798,PR-6799,PR-6796,PR-6813,PR-6671,PR-6673,PR-6684,PR-6694,GH-6677,PR-6696,PR-6670,PR-6674 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssh application does not crash when
+ formatting some of info reports for unsuccessful
+ connections.</p>
+ <p>
+ Own Id: OTP-18386 Aux Id: PR-6611 </p>
+ </item>
+ <item>
+ <p>
+ With this change, ssh does not log extensively long
+ messages.</p>
+ <p>
+ Own Id: OTP-18417 Aux Id: DAFH-1349,ERIERL-888,IA18357 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ graceful shutdown of ssh_conection_handler when
+ connection is closed by peer</p>
+ <p>
+ Own Id: OTP-18326 Aux Id: ERIERL-865 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.15</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Handling rare race condition at channel close.</p>
+ <p>
+ Own Id: OTP-18220 Aux Id: ERIERL-666, ERIERL-661 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ New ssh option <c>no_auth_needed</c> to skip the ssh
+ authentication. Use with caution!</p>
+ <p>
+ Own Id: OTP-18134 Aux Id: GH-6021 </p>
+ </item>
+ <item>
+ <p>
+ This change fixes dialyzer warnings generated for
+ inets/httpd examples (includes needed adjustment of spec
+ for ssh_sftp module).</p>
+ <p>
+ Own Id: OTP-18178 Aux Id: ERIERL-833, ERIERL-834,
+ ERIERL-835 </p>
+ </item>
+ <item>
+ <p>
+ The new function <c>ssh:daemon_replace_options/2</c>
+ makes it possible to change the <c>Options</c> in a
+ running SSH server.</p>
+ <p>
+ Established connections are not affected, only those
+ created after the call to this new function.</p>
+ <p>
+ Own Id: OTP-18196</p>
+ </item>
+ <item>
+ <p>
+ Add a timeout as option <c>max_initial_idle_time</c>. It
+ closes a connection that does not allocate a channel
+ within the timeout time.</p>
+ <p>
+ For more information about timeouts, see the <seeguide
+ marker="hardening#timeouts">Timeouts section </seeguide>
+ in the User's Guide <seeguide
+ marker="hardening">Hardening</seeguide> chapter.</p>
+ <p>
+ Own Id: OTP-18207 Aux Id: PR-6231 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.14.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Binaries can be limited in logs with the parameter
+ <c>max_log_item_len</c>. The default value is 500 bytes.</p>
+ <p>
+ Own Id: OTP-18094</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.14</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The representation of Edward curves (ed25519 and ed448)
+ inside ssh had a temporary representation (ed_pri and
+ ed_pub).</p>
+ <p>
+ That is now changed to the public_key form. See the
+ manual for more information.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-17920</p>
+ </item>
+ <item>
+ <p>
+ Former internal function
+ <c>ssh_file:extract_public_key/1</c> documented publicly.</p>
+ <p>
+ Internally it was previously in ssh_transport.</p>
+ <p>
+ Own Id: OTP-18079 Aux Id: GH-5767 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, Curve25519 and Curve448 KEX methods
+ become most preferred (related to RFC8731).</p>
+ <p>
+ Own Id: OTP-18964 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, connection handler does not execute
+ socket operations until it becomes socket owner.
+ Previously errors could occur if connection handler tried
+ to work with socket whose owner exited.</p>
+ <p>
+ Own Id: OTP-18869 Aux Id: PR-7849,GH-7571 </p>
+ </item>
+ <item>
+ <p>
+ With this change (being response to CVE-2023-48795), ssh
+ can negotiate "strict KEX" OpenSSH extension with peers
+ supporting it; also 'chacha20-poly1305@openssh.com'
+ algorithm becomes a less preferred cipher.</p>
+ <p>
+ If strict KEX availability cannot be ensured on both
+ connection sides, affected encryption modes(CHACHA and
+ CBC) can be disabled with standard ssh configuration.
+ This will provide protection against vulnerability, but
+ at a cost of affecting interoperability. See <seeguide
+ marker="configure_algos">Configuring algorithms in
+ SSH</seeguide>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-18897</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, error logging related crashes in
+ ssh_connection_handler module are fixed.</p>
+ <p>
+ Own Id: OTP-18620 Aux Id: OTP-18386,PR-6611 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ With this change, ssh application does not crash when
+ formatting some of info reports for unsuccessful
+ connections.</p>
+ <p>
+ Own Id: OTP-18386 Aux Id: PR-6611 </p>
+ </item>
+ <item>
+ <p>
+ With this change, ssh does not log extensively long
+ messages.</p>
+ <p>
+ Own Id: OTP-18417 Aux Id: DAFH-1349,ERIERL-888,IA18357 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Binaries can be limited in logs with the parameter
+ <c>max_log_item_len</c>. The default value is 500 bytes.</p>
+ <p>
+ Own Id: OTP-18094</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix makefile dependency bugs.</p>
+ <p>
+ Own Id: OTP-17847 Aux Id: PR-5574 GH-5548 </p>
+ </item>
+ <item>
+ <p>
+ Fixed faulty OpenSSH decoding of Ed25519/Ed448 keys in
+ the OpenSSH format <c>openssh_key_v1</c>.</p>
+ <p>
+ Own Id: OTP-17868 Aux Id: PR-5520 </p>
+ </item>
+ <item>
+ <p>
+ Correction of ssh_file typing, specially for the
+ experimental openssh-key-v1 encoding.</p>
+ <p>
+ Own Id: OTP-17912 Aux Id: GH-5680 </p>
+ </item>
+ <item>
+ <p>
+ Improper tag for private ED keys when encoding with
+ ssh:encode/2.</p>
+ <p>
+ The tuple had <c>ed_priv</c> as first element, but should
+ have had <c>ed_pri</c>. This is now corrected.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-17928 Aux Id: PR-5679 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add support for Ed25519/Ed448 SSH host keys in the RFC
+ 4716 format ("<c>-----BEGIN EC PRIVATE KEY-----</c>")
+ generated by for example openssl or via Erlang functions
+ (i.e. <c>public_key:generate_key({namedCurve,
+ ed25519})</c>).</p>
+ <p>
+ Ed25519 SSH host keys generated by <c>ssh-keygen</c> was,
+ and are still, supported.</p>
+ <p>
+ Own Id: OTP-17857 Aux Id: PR-5532 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ssh sever <c>parallel_login</c> option was missing in
+ OTP-24</p>
+ <p>
+ Own Id: OTP-17850 Aux Id: ERIERL-764 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.13</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The value of the <c>connect_timeout</c> option is now
+ used as default value for the negotiation timeout.</p>
+ <p>
+ Own Id: OTP-17707 Aux Id: ERIERL-706 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add better error handling in connect/2,3,4. Detect
+ incorrect arguments and return an informative error tuple
+ instead of throwing a function_clause or similar.</p>
+ <p>
+ Own Id: OTP-17515 Aux Id: ERIERL-648 </p>
+ </item>
+ <item>
+ <p>
+ Make ssh algorithm selection better handle dynamic
+ changes changes in crypto fips mode.</p>
+ <p>
+ Own Id: OTP-17795</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed a race condition in the acceptor loop: if a client
+ disconnected immediately after the tcp connect, the
+ server could cease handling connection on that
+ address:port.</p>
+ <p>
+ Own Id: OTP-17764 Aux Id: ERIERL-726 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed that a slow start (>30s) of a client subsystem
+ could cause a log entry with the password.</p>
+ <p>
+ Own Id: OTP-17390 Aux Id: ERIERL-648 </p>
+ </item>
+ <item>
+ <p>
+ Fixed an error when running as an sftp server and a
+ client requests a directory contents listing. </p>
+ <p>
+ The fix is to handle the error code <c>{error,
+ eacces}</c> as <c>{error, enoent}</c> in the
+ <c>ssh_sftpd:get_attrs/5</c> internal function; that is,
+ just skip it.</p>
+ <p>
+ Own Id: OTP-17586 Aux Id: GH-5014 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The "Key exchange failed" Info Report is now more
+ informative.</p>
+ <p>
+ Own Id: OTP-17450 Aux Id: ERIERL-655 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Filter out sensitive data (passwords etc) from progress
+ reports and supervisor reports.</p>
+ <p>
+ Own Id: OTP-17468 Aux Id: ERIERL-656 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Avoid an extra blank line in the ssh known_hosts file</p>
+ <p>
+ Own Id: OTP-17427</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12.1</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add missing <c>known_hosts</c> and <c>authorized_keys</c>
+ file types to <c>ssh_file:decode/2</c> and
+ <c>ssh_file:encode/2</c>.</p>
+ <p>
+ Own Id: OTP-17397</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.12</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Missing runtime dependencies has been added to this
+ application.</p>
+ <p>
+ Own Id: OTP-17243 Aux Id: PR-4557 </p>
+ </item>
+ <item>
+ <p>
+ The send window handling is changed to not initialize a
+ too large window on some occasions.</p>
+ <p>
+ Own Id: OTP-17353</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Removed usage of <c>erlang:is_port/1</c> from the SSH
+ implementation.</p>
+ <p>
+ Own Id: OTP-16750</p>
+ </item>
+ <item>
+ <p>
+ Internal connection setup refactoring.</p>
+ <p>
+ Own Id: OTP-17051</p>
+ </item>
+ <item>
+ <p>
+ Refactor SSH fsm into a (hopefully) more comprehensible
+ set of gen_statem callback-files.</p>
+ <p>
+ Own Id: OTP-17140</p>
+ </item>
+ <item>
+ <p>
+ The RSA SHA1 sign/verify variants are disabled by
+ default. That is, ssh-rsa is disabled by default as well
+ as the SHA1 sign/verify with RSA keys from id_rsa and
+ ssh_host_rsa_key. All SHA2 sign/verify are enabled by
+ default.</p>
+ <p>
+ The reason is that SHA1 is now considered easy to break.</p>
+ <p>
+ To enable RSA with SHA1, for example for a very old and
+ unsafe peer, see <seeguide
+ marker="configure_algos#example-9">Example 9</seeguide>
+ in the User's Guide chapter <seeguide
+ marker="configure_algos">Configuring algorithms in
+ SSH</seeguide>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-17259 Aux Id: OTP-16511, ERIERL-619 </p>
+ </item>
+ <item>
+ <p>
+ Adapt ssh supervisors to the new 'significant' and
+ 'auto_shutdown' flags in supervisor.</p>
+ <p>
+ Own Id: OTP-17322 Aux Id: PR-4638, EEP-56, OTP-17334 </p>
+ </item>
+ <item>
+ <p>
+ The functions public_key:ssh_encode/2,
+ public_key:ssh_decode/2,
+ public_key:ssh_hostkey_fingerprint/1 and
+ public_key:ssh_hostkey_fingerprint/2 are deprecated.</p>
+ <p>
+ Replacement functions are available in SSH, see the
+ <seeguide
+ marker="system/general_info:deprecations#otp-24">Deprecations</seeguide>
+ chapter in the Erlang/OTP documentation.</p>
+ <p>
+ Own Id: OTP-17352</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.11.1.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -283,6 +1461,22 @@
</section>
+<section><title>Ssh 4.10.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Filter out sensitive data (passwords etc) from progress
+ reports and supervisor reports.</p>
+ <p>
+ Own Id: OTP-17468 Aux Id: ERIERL-656 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.10.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -631,6 +1825,39 @@
</section>
+<section><title>Ssh 4.9.1.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The value of the <c>connect_timeout</c> option is now
+ used as default value for the negotiation timeout.</p>
+ <p>
+ Own Id: OTP-17707 Aux Id: ERIERL-706 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Ssh 4.9.1.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The idle_time timer was not cancelled when a channel was
+ opened within the timeout time on an empty connection
+ that have had channels previously.</p>
+ <p>
+ Own Id: OTP-17279</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.9.1.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -782,7 +2009,7 @@
input/output, the I/O was erroneously handled by the
*server's* group leader, so the I/O turned up in the the
server's Erlang shell (if any). The user at the client
- side did therefor not see that I/O.</p>
+ side did therefore not see that I/O.</p>
<p>
This is corrected now, so the client - for example the
ssh OS shell command - handles the I/O. The user could
@@ -968,6 +2195,23 @@
</section>
+<section><title>Ssh 4.7.6.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The idle_time timer was not cancelled when a channel was
+ opened within the timeout time on an empty connection
+ that have had channels previously.</p>
+ <p>
+ Own Id: OTP-17279</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -1310,7 +2554,7 @@
</item>
<item>
<p>
- The type specifications in SSH are completly reworked and
+ The type specifications in SSH are completely reworked and
the following types are renamed:</p>
<p>
<c>ssh:ssh_connection_ref()</c> is changed to
@@ -1575,7 +2819,7 @@
</item>
<item>
<p>
- Fix rare spurios shutdowns of ssh servers when receiveing
+ Fix rare spurious shutdowns of ssh servers when receiving
<c>{'EXIT',_,normal}</c> messages.</p>
<p>
Own Id: OTP-15018</p>
@@ -2026,7 +3270,7 @@
<list>
<item>
<p>
- Fix rare spurios shutdowns of ssh servers when receiveing
+ Fix rare spurious shutdowns of ssh servers when receiving
<c>{'EXIT',_,normal}</c> messages.</p>
<p>
Own Id: OTP-15018</p>
@@ -2258,7 +3502,7 @@
<item>
<p>
If a client illegaly sends an info-line and then
- immediatly closes the TCP-connection, a badmatch
+ immediately closes the TCP-connection, a badmatch
exception was raised.</p>
<p>
Own Id: OTP-13966</p>
@@ -2471,7 +3715,7 @@
<list>
<item>
<p>
- Fix rare spurios shutdowns of ssh servers when receiveing
+ Fix rare spurious shutdowns of ssh servers when receiving
<c>{'EXIT',_,normal}</c> messages.</p>
<p>
Own Id: OTP-15018</p>
@@ -3093,7 +4337,7 @@
<p>
The possible values are: <c>{id_string,string()}</c> and
<c>{id_string,random}</c>. The latter will make ssh
- generate a random nonsence id-string for each new
+ generate a random nonsense id-string for each new
connection.</p>
<p>
Own Id: OTP-12659</p>
@@ -3219,7 +4463,7 @@
<p>
The possible values are: <c>{id_string,string()}</c> and
<c>{id_string,random}</c>. The latter will make ssh
- generate a random nonsence id-string for each new
+ generate a random nonsense id-string for each new
connection.</p>
<p>
Own Id: OTP-12659</p>
@@ -3882,7 +5126,7 @@
<list>
<item>
<p>
- ssh:daemon will get feeded with an argument even if it is
+ ssh:daemon will get fed with an argument even if it is
not a valid expression.</p>
<p>
Own Id: OTP-10975</p>
@@ -4230,7 +5474,7 @@
<list>
<item>
<p>
- All keys in authorized_keys are considerd, wrongly only
+ All keys in authorized_keys are considered, wrongly only
the first one was before.</p>
<p>
Own Id: OTP-7235</p>
@@ -4604,7 +5848,7 @@
<list>
<item>
<p>
- Now clear all processes when a connnection is terminated.</p>
+ Now clear all processes when a connection is terminated.</p>
<p>
Own Id: OTP-8121 Aux Id:</p>
</item>
@@ -4702,13 +5946,13 @@
<list>
<item>
<p>
- ssh_sftp:start_channel/3 did not handle timout correctly.</p>
+ ssh_sftp:start_channel/3 did not handle timeout correctly.</p>
<p>
Own Id: OTP-8159 Aux Id: seq11386</p>
</item>
<item>
<p>
- If a progress message was not recieved after invoking ssh:connect/3
+ If a progress message was not received after invoking ssh:connect/3
the call could hang for ever. A timeout option has also been added.</p>
<p>
Own Id: OTP-8160 Aux Id: seq11386</p>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/SSH_app.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/SSH_app.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/SSH_app.xml
@@ -4,7 +4,7 @@
<appref>
<header>
<copyright>
- <year>2012</year><year>2020</year>
+ <year>2012</year><year>2024</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -172,16 +172,16 @@
<tag>Key exchange algorithms</tag>
<item>
<list type="bulleted">
- <item>ecdh-sha2-nistp384</item>
+ <item>curve25519-sha256</item>
+ <item>curve25519-sha256@libssh.org</item>
+ <item>curve448-sha512</item>
<item>ecdh-sha2-nistp521</item>
+ <item>ecdh-sha2-nistp384</item>
<item>ecdh-sha2-nistp256</item>
<item>diffie-hellman-group-exchange-sha256</item>
<item>diffie-hellman-group16-sha512</item>
<item>diffie-hellman-group18-sha512</item>
<item>diffie-hellman-group14-sha256</item>
- <item>curve25519-sha256</item>
- <item>curve25519-sha256@libssh.org</item>
- <item>curve448-sha512</item>
</list>
<p>The following unsecure <c>SHA1</c> algorithms are now disabled by default:</p>
<list>
@@ -199,18 +199,18 @@
<tag>Public key algorithms</tag>
<item>
<list type="bulleted">
- <item>ecdsa-sha2-nistp384</item>
- <item>ecdsa-sha2-nistp521</item>
- <item>ecdsa-sha2-nistp256</item>
- <item>ssh-ed25519</item>
+ <item>ssh-ed25519</item>
<item>ssh-ed448</item>
+ <item>ecdsa-sha2-nistp521</item>
+ <item>ecdsa-sha2-nistp384</item>
+ <item>ecdsa-sha2-nistp256</item>
+ <item>rsa-sha2-512</item>
<item>rsa-sha2-256</item>
- <item>rsa-sha2-512</item>
- <item>ssh-rsa <i>(SHA1 sign/verify are supported but disabled by default from OTP-24)</i></item>
</list>
- <p>The following unsecure <c>SHA1</c> algorithm is supported but disabled by default:</p>
+ <p>The following unsecure <c>SHA1</c> algorithms are supported but disabled by default:</p>
<list>
<item>(ssh-dss)</item>
+ <item>(ssh-rsa)</item>
</list>
<p>See
Disabled public key algorithms can be enabled with the
@@ -227,11 +227,11 @@
<tag>MAC algorithms</tag>
<item>
<list type="bulleted">
- <item>hmac-sha2-256-etm@openssh.com</item>
<item>hmac-sha2-512-etm@openssh.com</item>
- <item>hmac-sha1-etm@openssh.com</item>
- <item>hmac-sha2-256</item>
+ <item>hmac-sha2-256-etm@openssh.com</item>
<item>hmac-sha2-512</item>
+ <item>hmac-sha2-256</item>
+ <item>hmac-sha1-etm@openssh.com</item>
<item>hmac-sha1</item>
</list>
<p>The following unsecure <c>SHA1</c> algorithm is disabled by default:</p>
@@ -282,7 +282,7 @@
</section>
<section>
<title>Unicode support</title>
- <p>Unicode filenames are supported if the emulator and the underlaying OS support it. See section DESCRIPTION in the
+ <p>Unicode filenames are supported if the emulator and the underlying OS support it. See section DESCRIPTION in the
<seeerl marker="kernel:file">file</seeerl> manual page in Kernel for information about this subject.
</p>
<p>The shell and the cli both support unicode.
@@ -318,6 +318,7 @@
<item>6.6. Public Key Algorithms
<list type="bulleted">
<item>ssh-dss</item>
+ <item>ssh-rsa</item>
</list>
</item>
</list>
@@ -450,7 +451,7 @@
</item>
<item>
- <url href="https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves">Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448 (work in progress)</url>
+ <url href="https://tools.ietf.org/html/rfc8731">Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448</url>
<p/>
</item>
Index: otp-OTP-23.3.4.19/lib/ssh/doc/src/ssh_sftpd.xml
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/doc/src/ssh_sftpd.xml
+++ otp-OTP-23.3.4.19/lib/ssh/doc/src/ssh_sftpd.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2005</year><year>2020</year>
+ <year>2005</year><year>2025</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -65,6 +65,18 @@
If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c>
request is limited to at most the given value.</p>
</item>
+ <tag><c>max_handles</c></tag>
+ <item>
+ <p>The default value is <c>1000</c>. Positive integer value represents the maximum number of file handles allowed for a connection.</p>
+ </item>
+ <tag><c>max_path</c></tag>
+ <item>
+ <p>The default value is <c>4096</c>. Positive integer
+ value represents the maximum path length which cannot be
+ exceeded in data provided by the SFTP client. (Note:
+ limitations might be also enforced by underlying operating
+ system)</p>
+ </item>
<tag><c>root</c></tag>
<item>
<p>Sets the SFTP root directory. Then the user cannot see any files
Index: otp-OTP-23.3.4.19/lib/ssh/src/.gitignore
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/.gitignore
@@ -0,0 +1 @@
+deps
Index: otp-OTP-23.3.4.19/lib/ssh/src/Makefile
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/Makefile
+++ otp-OTP-23.3.4.19/lib/ssh/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2004-2020. All Rights Reserved.
+# Copyright Ericsson AB 2004-2024. 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.
@@ -66,11 +66,13 @@ MODULES= \
ssh_cli \
ssh_connection \
ssh_connection_handler \
- ssh_connection_sup \
- ssh_controller \
ssh_file \
+ ssh_fsm_kexinit \
+ ssh_fsm_userauth_client \
+ ssh_fsm_userauth_server \
ssh_info \
ssh_io \
+ ssh_lib \
ssh_message \
ssh_no_io \
ssh_options \
@@ -78,17 +80,14 @@ MODULES= \
ssh_sftpd \
ssh_sftpd_file\
ssh_shell \
- ssh_subsystem_sup \
- ssh_sup \
+ ssh_connection_sup \
ssh_system_sup \
ssh_tcpip_forward_srv \
ssh_tcpip_forward_client \
ssh_tcpip_forward_acceptor_sup \
ssh_tcpip_forward_acceptor \
ssh_transport \
- ssh_xfer \
- sshc_sup \
- sshd_sup
+ ssh_xfer
HRL_FILES =
@@ -113,7 +112,25 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
-INTERNAL_HRL_FILES = ssh_agent.hrl ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_xfer.hrl
+INTERNAL_HRL_FILES = \
+ ssh.hrl \
+ ssh_agent.hrl \
+ ssh_auth.hrl \
+ ssh_connect.hrl \
+ ssh_fsm.hrl \
+ ssh_transport.hrl \
+ ssh_xfer.hrl
+
+DEPDIR=$(ERL_TOP)/lib/ssh/src/deps
+DEP_FILE=$(DEPDIR)/ssh.d
+$(shell mkdir -p $(dir $(DEP_FILE)) >/dev/null)
+
+ifeq ($(TARGET), win32)
+ # Native path without C: ignore driveletter case
+ ERL_TOP_NATIVE = $(shell w32_path.sh -m $(ERL_TOP) | sed "s@[a-zA-Z]:@:@")
+else
+ ERL_TOP_NATIVE = $(ERL_TOP)
+endif
# ----------------------------------------------------
# FLAGS
@@ -132,11 +149,22 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/ke
$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES_2)
$(BEHAVIOUR_TARGET_FILES_2): $(BEHAVIOUR_TARGET_FILES_1)
-debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
+$(DEP_FILE): $(ERL_FILES)
+ @echo SED $(TARGET) $(ERL_TOP_NATIVE)
+ $(gen_verbose)erlc -M $(ERL_FILES) \
+ | perl -pe "s@ [a-zA-Z]?$(ERL_TOP_NATIVE)/(?:bootstrap/)?lib/([^/]+)@ ../../\1@g" 2> /dev/null \
+ | sed "s/\.$(EMULATOR)/\.$$\(EMULATOR\)/" \
+ | sed 's@^ssh_@$$(EBIN)/ssh_@' \
+ | sed 's@^sshc_@$$(EBIN)/sshc_@' \
+ | sed 's@^sshd_@$$(EBIN)/sshd_@' \
+ > $(DEP_FILE)
+
+$(TYPES): $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(DEP_FILE)
clean:
rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES)
rm -f errs core *~
+ rm -rf $(DEPDIR)
$(APP_TARGET): $(APP_SRC) ../vsn.mk
$(vsn_verbose)sed -e 's;%VSN%;$(VSN);' $< > $@
@@ -163,81 +191,7 @@ release_spec: opt
release_docs_spec:
-
-deps:
- erlc -M $(ERL_FILES) \
- | sed 's@$(ERL_TOP)/lib@../..@g' \
- | sed 's/\.$(EMULATOR)/\.$$\(EMULATOR\)/' \
- | sed 's@^ssh_@$$(EBIN)/ssh_@'
-
-ssh.$(EMULATOR): ssh.erl ssh.hrl ssh_connect.hrl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ../../kernel/include/file.hrl
-$(EBIN)/ssh_sup.$(EMULATOR): ssh_sup.erl
-sshc_sup.$(EMULATOR): sshc_sup.erl
-sshd_sup.$(EMULATOR): sshd_sup.erl ssh.hrl
-$(EBIN)/ssh_connection_sup.$(EMULATOR): ssh_connection_sup.erl
-$(EBIN)/ssh_connection.$(EMULATOR): ssh_connection.erl ssh.hrl ssh_connect.hrl \
- ssh_transport.hrl
-$(EBIN)/ssh_connection_handler.$(EMULATOR): ssh_connection_handler.erl ssh.hrl \
- ssh_transport.hrl ssh_auth.hrl ssh_connect.hrl
-$(EBIN)/ssh_shell.$(EMULATOR): ssh_shell.erl ssh_connect.hrl
-$(EBIN)/ssh_system_sup.$(EMULATOR): ssh_system_sup.erl ssh.hrl
-$(EBIN)/ssh_subsystem_sup.$(EMULATOR): ssh_subsystem_sup.erl
-$(EBIN)/ssh_channel_sup.$(EMULATOR): ssh_channel_sup.erl ssh.hrl
-$(EBIN)/ssh_acceptor_sup.$(EMULATOR): ssh_acceptor_sup.erl ssh.hrl
-$(EBIN)/ssh_acceptor.$(EMULATOR): ssh_acceptor.erl ssh.hrl
-$(EBIN)/ssh_agent.$(EMULATOR): ssh_agent.erl ssh.hrl ssh_agent.hrl
-$(EBIN)/ssh_app.$(EMULATOR): ssh_app.erl
-$(EBIN)/ssh_auth.$(EMULATOR): ssh_auth.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ssh.hrl ssh_auth.hrl ssh_transport.hrl
-$(EBIN)/ssh_bits.$(EMULATOR): ssh_bits.erl ssh.hrl
-$(EBIN)/ssh_cli.$(EMULATOR): ssh_cli.erl ssh.hrl ssh_connect.hrl
-$(EBIN)/ssh_file.$(EMULATOR): ssh_file.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ../../kernel/include/file.hrl ssh.hrl
-$(EBIN)/ssh_io.$(EMULATOR): ssh_io.erl ssh.hrl
-$(EBIN)/ssh_info.$(EMULATOR): ssh_info.erl
-$(EBIN)/ssh_message.$(EMULATOR): ssh_message.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ssh.hrl ssh_connect.hrl ssh_auth.hrl ssh_transport.hrl
-$(EBIN)/ssh_no_io.$(EMULATOR): ssh_no_io.erl ssh_transport.hrl
-$(EBIN)/ssh_sftp.$(EMULATOR): ssh_sftp.erl \
- ../../kernel/include/file.hrl ssh.hrl \
- ssh_xfer.hrl
-$(EBIN)/ssh_sftpd.$(EMULATOR): ssh_sftpd.erl \
- ../../kernel/include/file.hrl ssh.hrl \
- ssh_xfer.hrl
-$(EBIN)/ssh_sftpd_file.$(EMULATOR): ssh_sftpd_file.erl
-$(EBIN)/ssh_transport.$(EMULATOR): ssh_transport.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ../../kernel/include/inet.hrl \
- ssh_transport.hrl ssh.hrl
-$(EBIN)/ssh_xfer.$(EMULATOR): ssh_xfer.erl ssh.hrl ssh_xfer.hrl
-$(EBIN)/ssh_sftpd_file_api.$(EMULATOR): ssh_sftpd_file_api.erl
-$(EBIN)/ssh_client_channel.$(EMULATOR): ssh_client_channel.erl ssh_connect.hrl
-$(EBIN)/ssh_channel.$(EMULATOR): ssh_channel.erl ssh_connect.hrl
-$(EBIN)/ssh_daemon_channel.$(EMULATOR): ssh_daemon_channel.erl
-$(EBIN)/ssh_server_channel.$(EMULATOR): ssh_server_channel.erl
-$(EBIN)/ssh_client_key_api.$(EMULATOR): ssh_client_key_api.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ssh.hrl
-$(EBIN)/ssh_server_key_api.$(EMULATOR): ssh_server_key_api.erl \
- ../../public_key/include/public_key.hrl \
- ../../public_key/include/OTP-PUB-KEY.hrl \
- ../../public_key/include/PKCS-FRAME.hrl \
- ssh.hrl
-
+# ----------------------------------------------------
+# Dependencies
+# ----------------------------------------------------
+-include $(DEP_FILE)
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_acceptor.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_acceptor.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_acceptor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,82 +25,29 @@
-include("ssh.hrl").
%% Internal application API
--export([start_link/4,
+-export([start_link/3,
number_of_connections/1,
- listen/2,
- handle_established_connection/4]).
+ listen/2]).
%% spawn export
--export([acceptor_init/5, acceptor_loop/6]).
+-export([acceptor_init/4, acceptor_loop/6]).
-behaviour(ssh_dbg).
--export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2, ssh_dbg_format/3]).
-define(SLEEP_TIME, 200).
%%====================================================================
%% Internal application API
%%====================================================================
-start_link(Port, Address, Options, AcceptTimeout) ->
- Args = [self(), Port, Address, Options, AcceptTimeout],
- proc_lib:start_link(?MODULE, acceptor_init, Args).
-
-%%%----------------------------------------------------------------
-number_of_connections(SysSup) ->
- length([S || S <- supervisor:which_children(SysSup),
- has_worker(SysSup,S)]).
-
-
-has_worker(SysSup, {R,SubSysSup,supervisor,[ssh_subsystem_sup]}) when is_reference(R),
- is_pid(SubSysSup) ->
- try
- {{server, ssh_connection_sup, _, _}, Pid, supervisor, [ssh_connection_sup]} =
- lists:keyfind([ssh_connection_sup], 4, supervisor:which_children(SubSysSup)),
- {Pid, supervisor:which_children(Pid)}
- of
- {ConnSup,[]} ->
- %% Strange. Since the connection supervisor exists, there should have been
- %% a connection here.
- %% It might be that the connection_handler worker has "just died", maybe
- %% due to a exit(_,kill). It might also be so that the worker is starting.
- %% Spawn a killer that redo the test and kills it if the problem persists.
- %% TODO: Fix this better in the supervisor tree....
- spawn(fun() ->
- timer:sleep(10),
- try supervisor:which_children(ConnSup)
- of
- [] ->
- %% we are on the server-side:
- ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
- [_] ->
- %% is ok now
- ok;
- _ ->
- %% What??
- error
- catch _:_ ->
- %% What??
- error
- end
- end),
- false;
- {_ConnSup,[_]}->
- true;
- _ ->
- %% What??
- false
- catch _:_ ->
- %% What??
- false
- end;
-
-has_worker(_,_) ->
- false.
+%% Supposed to be called in a child-spec of the ssh_acceptor_sup
+start_link(SystemSup, Address, Options) ->
+ proc_lib:start_link(?MODULE, acceptor_init, [self(),SystemSup,Address,Options]).
%%%----------------------------------------------------------------
listen(Port, Options) ->
{_, Callback, _} = ?GET_OPT(transport, Options),
- SockOpts = [{active, false}, {reuseaddr,true} | ?GET_OPT(socket_options, Options)],
+ SockOpts = ?GET_OPT(socket_options, Options) ++ [{active, false}, {reuseaddr,true}],
case Callback:listen(Port, SockOpts) of
{error, nxdomain} ->
Callback:listen(Port, lists:delete(inet6, SockOpts));
@@ -112,40 +59,53 @@ listen(Port, Options) ->
Other
end.
-%%%----------------------------------------------------------------
-handle_established_connection(Address, Port, Options, Socket) ->
+accept(ListenSocket, AcceptTimeout, Options) ->
{_, Callback, _} = ?GET_OPT(transport, Options),
- handle_connection(Callback, Address, Port, Options, Socket).
+ Callback:accept(ListenSocket, AcceptTimeout).
+close(Socket, Options) ->
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ Callback:close(Socket).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-acceptor_init(Parent, Port, Address, Opts, AcceptTimeout) ->
- try
- ?GET_INTERNAL_OPT(lsocket, Opts)
- of
+acceptor_init(Parent, SystemSup,
+ #address{address=Address, port=Port, profile=_Profile},
+ Opts) ->
+ AcceptTimeout = ?GET_INTERNAL_OPT(timeout, Opts, ?DEFAULT_TIMEOUT),
+ case ?GET_INTERNAL_OPT(lsocket, Opts, undefined) of
{LSock, SockOwner} ->
+ %% A listening socket (or fd option) was provided in the ssh:daemon call
case inet:sockname(LSock) of
- {ok,{_,Port}} -> % A usable, open LSock
+ {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
- %% Allow gen_tcp:listen to fail 4 times if eaddrinuse:
- {ok,NewLSock} = try_listen(Port, Opts, 4),
+ acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup);
+ {error,_Error} ->
+ %% Not open, a restart
+ %% Allow gen_tcp:listen to fail 4 times if eaddrinuse (It is a bug fix):
+ case try_listen(Port, Opts, 4) of
+ {ok,NewLSock} ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ Opts1 = ?DELETE_INTERNAL_OPT(lsocket, Opts),
+ acceptor_loop(Port, Address, Opts1, NewLSock, AcceptTimeout, SystemSup);
+ {error,Error} ->
+ proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
+ end
+ end;
+ undefined ->
+ %% No listening socket (nor fd option) was provided; open a listening socket:
+ case try_listen(Port, Opts, 4) of
+ {ok,LSock} ->
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)
+ acceptor_loop(Port, Address, Opts, LSock, AcceptTimeout, SystemSup);
+ {error,Error} ->
+ proc_lib:init_fail(Parent, {error,Error}, {exit, normal})
end
- catch
- _:_ ->
- {error,use_existing_socket_failed}
end.
-
try_listen(Port, Opts, NtriesLeft) ->
try_listen(Port, Opts, 1, NtriesLeft).
@@ -158,7 +118,6 @@ try_listen(Port, Opts, N, Nmax) ->
Other
end.
-
request_ownership(LSock, SockOwner) ->
SockOwner ! {request_control,LSock,self()},
receive
@@ -166,117 +125,217 @@ request_ownership(LSock, SockOwner) ->
end.
%%%----------------------------------------------------------------
-acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
- case (catch Callback:accept(ListenSocket, AcceptTimeout)) of
- {ok, Socket} ->
- handle_connection(Callback, Address, Port, Opts, Socket),
- ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
- ListenSocket, AcceptTimeout);
- {error, Reason} ->
- handle_error(Reason),
- ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
- ListenSocket, AcceptTimeout);
- {'EXIT', Reason} ->
- handle_error(Reason),
- ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
- ListenSocket, AcceptTimeout)
- end.
+acceptor_loop(Port, Address, Opts, ListenSocket, AcceptTimeout, SystemSup) ->
+ try
+ case accept(ListenSocket, AcceptTimeout, Opts) of
+ {ok,Socket} ->
+ PeerName = inet:peername(Socket),
+ MaxSessions = ?GET_OPT(max_sessions, Opts),
+ NumSessions = number_of_connections(SystemSup),
+ ParallelLogin = ?GET_OPT(parallel_login, Opts),
+ case handle_connection(Address, Port, PeerName, Opts, Socket,
+ MaxSessions, NumSessions, ParallelLogin) of
+ {error,Error} ->
+ catch close(Socket, Opts),
+ handle_error(Error, Address, Port, PeerName);
+ _ ->
+ ok
+ end;
+ {error,Error} ->
+ handle_error(Error, Address, Port, undefined)
+ end
+ catch
+ Class:Err:Stack ->
+ handle_error({error, {unhandled,Class,Err,Stack}}, Address, Port, undefined)
+ end,
+ ?MODULE:acceptor_loop(Port, Address, Opts, ListenSocket, AcceptTimeout, SystemSup).
%%%----------------------------------------------------------------
-handle_connection(Callback, Address, Port, Options, Socket) ->
- Profile = ?GET_OPT(profile, Options),
- SystemSup = ssh_system_sup:system_supervisor(Address, Port, Profile),
-
- MaxSessions = ?GET_OPT(max_sessions, Options),
- case number_of_connections(SystemSup) < MaxSessions of
- true ->
- {ok, SubSysSup} =
- ssh_system_sup:start_subsystem(SystemSup, server, Address, Port, Profile, Options),
- ConnectionSup = ssh_subsystem_sup:connection_supervisor(SubSysSup),
- NegTimeout = ?GET_OPT(negotiation_timeout, Options),
- ssh_connection_handler:start_connection(server, Socket,
- ?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);
- true -> Address
- end,
- Str = try io_lib:format('~s:~p',[IPstr,Port])
- catch _:_ -> "port "++integer_to_list(Port)
- end,
- error_logger:info_report("Ssh login attempt to "++Str++" denied due to option "
- "max_sessions limits to "++ io_lib:write(MaxSessions) ++
- " sessions."
- ),
- {error,max_sessions}
- end.
+handle_connection(_Address, _Port, _Peer, _Options, _Socket,
+ MaxSessions, NumSessions, _ParallelLogin)
+ when NumSessions >= MaxSessions->
+ {error,{max_sessions,MaxSessions}};
+handle_connection(_Address, _Port, {error,Error}, _Options, _Socket,
+ _MaxSessions, _NumSessions, _ParallelLogin) ->
+ {error,Error};
+handle_connection(Address, Port, _Peer, Options, Socket,
+ _MaxSessions, _NumSessions, ParallelLogin)
+ when ParallelLogin == false ->
+ handle_connection(Address, Port, Options, Socket);
+handle_connection(Address, Port, _Peer, Options, Socket,
+ _MaxSessions, _NumSessions, ParallelLogin)
+ when ParallelLogin == true ->
+ Ref = make_ref(),
+ Pid = spawn_link(
+ fun() ->
+ process_flag(trap_exit, true),
+ receive
+ {start,Ref} ->
+ handle_connection(Address, Port, Options, Socket)
+ after 10000 ->
+ {error, timeout2}
+ end
+ end),
+ catch gen_tcp:controlling_process(Socket, Pid),
+ Pid ! {start,Ref},
+ ok.
+
+handle_connection(Address, Port, Options0, Socket) ->
+ Options = ?PUT_INTERNAL_OPT([{user_pid, self()}
+ ], Options0),
+ ssh_system_sup:start_connection(server,
+ #address{address = Address,
+ port = Port,
+ profile = ?GET_OPT(profile,Options)
+ },
+ Socket,
+ Options).
%%%----------------------------------------------------------------
-handle_error(timeout) ->
- ok;
+handle_error(Reason, ToAddress, ToPort, {ok, {FromIP,FromPort}}) ->
+ handle_error(Reason, ToAddress, ToPort, FromIP, FromPort);
+
+handle_error(Reason, ToAddress, ToPort, _) ->
+ handle_error(Reason, ToAddress, ToPort, undefined, undefined).
-handle_error(enfile) ->
- %% Out of sockets...
- timer:sleep(?SLEEP_TIME);
-
-handle_error(emfile) ->
- %% Too many open files -> Out of sockets...
- timer:sleep(?SLEEP_TIME);
-
-handle_error(closed) ->
- error_logger:info_report("The ssh accept socket was closed by "
- "a third party. "
- "This will not have an impact on ssh "
- "that will open a new accept socket and "
- "go on as nothing happened. It does however "
- "indicate that some other software is behaving "
- "badly."),
- exit(normal);
-
-handle_error(Reason) ->
- String = lists:flatten(io_lib:format("Accept error: ~p", [Reason])),
- error_logger:error_report(String),
- exit({accept_failed, String}).
+
+handle_error(Reason, ToAddress, ToPort, FromAddress, FromPort) ->
+ case Reason of
+ {max_sessions, MaxSessions} ->
+ MsgFun =
+ fun(debug) ->
+ lists:concat(["Ssh login attempt to ",
+ ssh_lib:format_address_port(ToAddress,ToPort),
+ " from ",
+ ssh_lib:format_address_port(FromAddress,FromPort),
+ " denied due to option max_sessions limits to ",
+ MaxSessions, " sessions."]);
+ (_) ->
+ ["Ssh login attempt denied max_session limits"]
+ end,
+ error_logger:info_report(?SELECT_MSG(MsgFun));
+ Limit when Limit==enfile ; Limit==emfile ->
+ %% Out of sockets...
+ MsgFun =
+ fun(debug) ->
+ [atom_to_list(Limit),": out of accept sockets on ",
+ ssh_lib:format_address_port(ToAddress, ToPort),
+ " - retrying"];
+ (_) ->
+ ["Out of accept sockets on - retrying"]
+ end,
+ error_logger:info_report(?SELECT_MSG(MsgFun)),
+ timer:sleep(?SLEEP_TIME);
+ closed ->
+ MsgFun =
+ fun(debug) ->
+ ["The ssh accept socket on ", ssh_lib:format_address_port(ToAddress,ToPort),
+ "was closed by a third party."];
+ (_) ->
+ ["The ssh accept socket on was closed by a third party"]
+ end,
+ error_logger:info_report(?SELECT_MSG(MsgFun));
+ timeout ->
+ ok;
+ Error when is_list(Error) ->
+ ok;
+ Error when FromAddress=/=undefined,
+ FromPort=/=undefined ->
+ MsgFun =
+ fun(debug) ->
+ ["Accept failed on ",ssh_lib:format_address_port(ToAddress,ToPort),
+ " for connect from ",ssh_lib:format_address_port(FromAddress,FromPort),
+ io_lib:format(": ~p", [Error])];
+ (_) ->
+ [io_lib:format("Accept failed on for connection: ~p", [Error])]
+ end,
+ error_logger:info_report(?SELECT_MSG(MsgFun));
+ Error ->
+ MsgFun =
+ fun(debug) ->
+ ["Accept failed on ",ssh_lib:format_address_port(ToAddress,ToPort),
+ io_lib:format(": ~p", [Error])];
+ (_) ->
+ [io_lib:format("Accept failed on for connection: ~p", [Error])]
+ end,
+ error_logger:info_report(?SELECT_MSG(MsgFun))
+ end.
+
+%%%----------------------------------------------------------------
+number_of_connections(SysSupPid) ->
+ lists:foldl(fun({_Ref,_Pid,supervisor,[ssh_connection_sup]}, N) -> N+1;
+ (_, N) -> N
+ end, 0, supervisor:which_children(SysSupPid)).
%%%################################################################
%%%#
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [connections].
+ssh_dbg_trace_points() -> [connections, tcp].
+ssh_dbg_flags(tcp) -> [c];
ssh_dbg_flags(connections) -> [c].
-ssh_dbg_on(connections) -> dbg:tp(?MODULE, acceptor_init, 5, x),
- dbg:tpl(?MODULE, handle_connection, 5, x).
-
-ssh_dbg_off(connections) -> dbg:ctp(?MODULE, acceptor_init, 5),
- dbg:ctp(?MODULE, handle_connection, 5).
+ssh_dbg_on(tcp) -> dbg:tp(?MODULE, listen, 2, x),
+ dbg:tpl(?MODULE, accept, 3, x),
+ dbg:tpl(?MODULE, close, 2, x);
+
+ssh_dbg_on(connections) -> dbg:tp(?MODULE, acceptor_init, 4, x),
+ dbg:tpl(?MODULE, handle_connection, 4, x).
+
+ssh_dbg_off(tcp) -> dbg:ctpg(?MODULE, listen, 2),
+ dbg:ctpl(?MODULE, accept, 3),
+ dbg:ctpl(?MODULE, close, 2);
+
+ssh_dbg_off(connections) -> dbg:ctp(?MODULE, acceptor_init, 4),
+ dbg:ctp(?MODULE, handle_connection, 4).
+
+ssh_dbg_format(tcp, {call, {?MODULE,listen, [Port,_Opts]}}, Stack) ->
+ {skip, [{port,Port}|Stack]};
+ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, {ok,Sock}}, [{port,Port}|Stack]) ->
+ {["TCP listener started\n",
+ io_lib:format("Port: ~p~n"
+ "ListeningSocket: ~p~n", [Port,Sock])
+ ],
+ Stack};
+ssh_dbg_format(tcp, {return_from, {?MODULE,listen,2}, Result}, [{port,Port}|Stack]) ->
+ {["TCP listener start ERROR\n",
+ io_lib:format("Port: ~p~n"
+ "Return: ~p~n", [Port,Result])
+ ],
+ Stack};
+
+ssh_dbg_format(tcp, {call, {?MODULE,accept, [ListenSocket, _AcceptTimeout, _Options]}}, Stack) ->
+ {skip, [{lsock,ListenSocket}|Stack]};
+ssh_dbg_format(tcp, {return_from, {?MODULE,accept,3}, {ok,Sock}}, [{lsock,ListenSocket}|Stack]) ->
+ {["TCP accept\n",
+ io_lib:format("ListenSock: ~p~n"
+ "New Socket: ~p~n", [ListenSocket,Sock])
+ ], Stack};
+ssh_dbg_format(tcp, {return_from, {?MODULE,accept,3}, {error,timeout}}, [{lsock,_ListenSocket}|Stack]) ->
+ {skip, Stack};
+ssh_dbg_format(tcp, {return_from, {?MODULE,accept,3}, Return}, [{lsock,ListenSocket}|Stack]) ->
+ {["TCP accept returned\n",
+ io_lib:format("ListenSock: ~p~n"
+ "Return: ~p~n", [ListenSocket,Return])
+ ], Stack}.
+
+ssh_dbg_format(tcp, {call, {?MODULE,close, [Socket, _Options]}}) ->
+ ["TCP close listen socket\n",
+ io_lib:format("Socket: ~p~n", [Socket])];
+ssh_dbg_format(tcp, {return_from, {?MODULE,close,2}, _Return}) ->
+ skip;
-ssh_dbg_format(connections, {call, {?MODULE,acceptor_init,
- [_Parent, Port, Address, _Opts, _AcceptTimeout]}}) ->
- [io_lib:format("Starting LISTENER on ~s:~p\n", [ntoa(Address),Port])
+ssh_dbg_format(connections, {call, {?MODULE,acceptor_init, [_Parent, _SysSup, Address, _Opts]}}) ->
+ [io_lib:format("Starting LISTENER on ~s\n", [ssh_lib:format_address(Address)])
];
-ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,5}, _Ret}) ->
+ssh_dbg_format(connections, {return_from, {?MODULE,acceptor_init,4}, _Ret}) ->
skip;
-ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_,_,_,_,_]}}) ->
+ssh_dbg_format(connections, {call, {?MODULE,handle_connection,[_Address,_Port,_Options,_Sock]}}) ->
skip;
-ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,5}, {error,Error}}) ->
+ssh_dbg_format(connections, {return_from, {?MODULE,handle_connection,4}, {error,Error}}) ->
["Starting connection to server failed:\n",
io_lib:format("Error = ~p", [Error])
].
-
-
-
-ntoa(A) ->
- try inet:ntoa(A)
- catch
- _:_ when is_list(A) -> A;
- _:_ -> io_lib:format('~p',[A])
- end.
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_app.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_app.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_app.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2024. 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,16 +20,132 @@
%%
-%% Purpose : Application master for SSH.
+%%%=========================================================================
+%%% Purpose : Application master and top supervisors for SSH.
+%%%
+%%% -----> ssh_sup -----+-----> sshc_sup --+--> "connection sup" (etc)
+%%% | |
+%%% | +--> "connection sup" (etc)
+%%% | :
+%%% | +--> "connection sup" (etc)
+%%% |
+%%% +-----> sshc_sup --+--> "system sup" (etc)
+%%% |
+%%% +--> "system sup" (etc)
+%%% :
+%%% +--> "system sup" (etc)
-module(ssh_app).
-behaviour(application).
+-behaviour(supervisor).
+%% 'application' export:
-export([start/2, stop/1]).
+%% 'supervisor' export:
+-export([init/1]).
+
+
+%%%=========================================================================
+%%% Application callback
+%%%=========================================================================
start(_Type, _State) ->
- supervisor:start_link({local, ssh_sup}, ssh_sup, []).
+ supervisor:start_link({local,ssh_sup}, ?MODULE, [ssh_sup]).
stop(_State) ->
ok.
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([ssh_sup]) ->
+ add_logger_filter(),
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [#{id => SupName,
+ start => {supervisor, start_link,
+ [{local,SupName}, ?MODULE, [sshX_sup]]},
+ type => supervisor}
+ || SupName <- [sshd_sup, sshc_sup]
+ ],
+ {ok, {SupFlags,ChildSpecs}};
+
+init([sshX_sup]) ->
+ SupFlags = #{strategy => one_for_one,
+ intensity => 10,
+ period => 3600
+ },
+ ChildSpecs = [],
+ {ok, {SupFlags,ChildSpecs}}.
+
+
+%%%================================================================
+add_logger_filter() ->
+ DefAct = application:get_env(ssh, default_filter, rm),
+ DefF = start_link,
+ ModulesActions =
+ lists:map(fun(M) when is_atom(M) ->
+ {M,{DefF,DefAct}};
+
+ ({M,Act}) when is_atom(M),
+ (Act == rm orelse
+ Act == filter) ->
+ {M,{DefF,Act}};
+
+ ({M,F}) when is_atom(M), is_atom(F) ->
+ {M,{F,DefAct}};
+
+ ({M,F,Act}) when is_atom(M), is_atom(F),
+ (Act == rm orelse
+ Act == filter) ->
+ {M,{F,Act}}
+ end, application:get_env(ssh, filter_modules, [])),
+ logger:add_primary_filter(ssh_filter, {fun ssh_filter/2, ModulesActions}).
+
+
+ssh_filter(Ev = #{msg := {report, R=#{report := Rep}}},
+ ModulesActions = [_|_]) when is_list(Rep) ->
+ %% io:format("Ev = ~p~n", [Ev]),
+ try
+ Ev#{msg := {report, R#{report := remove_sensitive(Rep, ModulesActions)}}}
+ catch
+ throw:{ssh_filter_return,Ret} ->
+ %% io:format("ssh_filter returns ~p~n", [Ret]),
+ Ret;
+ _C:_E ->
+ %% io:format("*** ~p ~p~n", [_C,_E]),
+ stop
+ end;
+ssh_filter(OtherEv, _) ->
+ %% io:format("OtherEv = ~p~n", [OtherEv]),
+ OtherEv.
+
+
+remove_sensitive(L, ModActs) when is_list(L) -> rs(L, ModActs);
+remove_sensitive(_, _) -> throw({ssh_filter_return,ignore}).
+
+
+rs([{K,V0}|T], ModActs) when is_list(V0) ->
+ case proplists:get_value(mfargs, V0) of
+ {M,F,A} ->
+ MFA1 = filter(proplists:get_value(M,ModActs), {M,F,A}),
+ V = lists:keyreplace(mfargs, 1, V0, {mfargs,MFA1}),
+ [{K,V} | T];
+ _ ->
+ [{K,V0} | rs(T,ModActs)]
+ end;
+
+rs([H|T], ModActs) ->
+ [H | rs(T,ModActs)];
+
+rs(Other, _) ->
+ Other.
+
+
+filter({F,Act}, {M,F,A}) -> {M, F, ssh_options:no_sensitive(Act,A)};
+filter(stop, _) -> throw({ssh_filter_return,stop});
+filter(_, _) -> throw({ssh_filter_return,ignore}).
+
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh.app.src
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh.app.src
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh.app.src
@@ -19,16 +19,16 @@
ssh_channel,
ssh_connection,
ssh_connection_handler,
- ssh_connection_sup,
- ssh_controller,
+ ssh_fsm_kexinit,
+ ssh_fsm_userauth_client,
+ ssh_fsm_userauth_server,
ssh_daemon_channel,
ssh_dbg,
+ ssh_lib,
ssh_shell,
- sshc_sup,
- sshd_sup,
- ssh_file,
ssh_io,
ssh_info,
+ ssh_file,
ssh_no_io,
ssh_server_channel,
ssh_server_key_api,
@@ -36,8 +36,7 @@
ssh_sftpd,
ssh_sftpd_file,
ssh_sftpd_file_api,
- ssh_subsystem_sup,
- ssh_sup,
+ ssh_connection_sup,
ssh_tcpip_forward_client,
ssh_tcpip_forward_srv,
ssh_tcpip_forward_acceptor_sup,
@@ -49,19 +48,20 @@
{applications, [kernel, stdlib, crypto, public_key]},
{env, [{filter_modules, [
ssh_acceptor_sup,
- ssh_acceptor,filter,
+ ssh_acceptor,
ssh_channel_sup,
+ ssh_connection_handler,
ssh_connection_sup,
- ssh_subsystem_sup,
ssh_system_sup
]},
{default_filter, rm} %% rm | filter
]},
{mod, {ssh_app, []}},
{runtime_dependencies, [
- "crypto-4.6.4",
- "erts-9.0",
- "kernel-5.3",
+ "crypto-5.0",
+ "erts-14.0",
+ "kernel-9.0",
"public_key-1.6.1",
- "stdlib-3.4.1"
+ "stdlib-5.0","stdlib-5.0",
+ "runtime_tools-1.15.1"
]}]}.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_connect.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_connect.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_connect.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2024. 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,7 +25,6 @@
-define(DEFAULT_PACKET_SIZE, 65536).
-define(DEFAULT_WINDOW_SIZE, 10*?DEFAULT_PACKET_SIZE).
--define(DEFAULT_TIMEOUT, 5000).
-define(MAX_PROTO_VERSION, 255). % Max length of the hello string
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -267,8 +266,8 @@
channel_id_seed,
cli_spec,
options,
+ suggest_window_size,
+ suggest_packet_size,
exec,
- system_supervisor,
- sub_system_supervisor,
connection_supervisor
}).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_connection.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,8 @@
-module(ssh_connection).
+-include_lib("kernel/include/logger.hrl").
+
-include("ssh.hrl").
-include("ssh_connect.hrl").
-include("ssh_transport.hrl").
@@ -177,7 +179,7 @@
Command :: string()
} .
-%%% This function is soley to convince all
+%%% This function is solely to convince all
%%% checks that the type event() exists...
-export([dummy/1]).
-spec dummy(event()) -> false.
@@ -199,13 +201,13 @@ dummy(_) -> false.
Result :: {ok, ssh:channel_id()} | {error, reason()} .
session_channel(ConnectionHandler, Timeout) ->
- session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+ session_channel(ConnectionHandler, undefined, undefined, Timeout).
-spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when
ConnectionRef :: ssh:connection_ref(),
- InitialWindowSize :: pos_integer(),
- MaxPacketSize :: pos_integer(),
+ InitialWindowSize :: pos_integer() | undefined,
+ MaxPacketSize :: pos_integer() | undefined,
Timeout :: timeout(),
Result :: {ok, ssh:channel_id()} | {error, reason()} .
@@ -219,7 +221,7 @@ session_channel(ConnectionHandler, Initi
%% Description: Opens a channel for the given type.
%% --------------------------------------------------------------------
open_channel(ConnectionHandler, Type, ChanData, Timeout) ->
- open_channel(ConnectionHandler, Type, ChanData, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+ open_channel(ConnectionHandler, Type, ChanData, undefined, undefined, Timeout).
open_channel(ConnectionHandler, Type, ChanData, InitialWindowSize, MaxPacketSize, Timeout) ->
case ssh_connection_handler:open_channel(ConnectionHandler, Type, ChanData,
@@ -404,7 +406,7 @@ ptty_alloc(ConnectionHandler, Channel, O
).
%%--------------------------------------------------------------------
-%% Not yet officialy supported! The following functions are part of the
+%% Not yet officially supported! The following functions are part of the
%% initial contributed ssh application. They are untested. Do we want them?
%% Should they be documented and tested?
%%--------------------------------------------------------------------
@@ -468,23 +470,50 @@ channel_data(ChannelId, DataType, Data0,
%%% Replies {Reply, UpdatedConnection}
%%%
+handle_msg(#ssh_msg_disconnect{code = Code, description = Description}, Connection, _, _SSH) ->
+ {disconnect, {Code, Description}, handle_stop(Connection)};
+
+handle_msg(Msg, Connection, server, Ssh = #ssh{authenticated = false}) ->
+ %% See RFC4252 6.
+ %% Message numbers of 80 and higher are reserved for protocols running
+ %% after this authentication protocol, so receiving one of them before
+ %% authentication is complete is an error, to which the server MUST
+ %% respond by disconnecting, preferably with a proper disconnect message
+ %% sent to ease troubleshooting.
+ MsgFun = fun(M) ->
+ io_lib:format("Connection terminated. Unexpected message for unauthenticated user."
+ " Message: ~w", [M],
+ [{chars_limit, ssh_lib:max_log_len(Ssh)}])
+ end,
+ ?LOG_DEBUG(MsgFun, [Msg]),
+ {disconnect, {?SSH_DISCONNECT_PROTOCOL_ERROR, "Connection refused"}, handle_stop(Connection)};
+
handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
sender_channel = RemoteId,
initial_window_size = WindowSz,
maximum_packet_size = PacketSz},
#connection{channel_cache = Cache} = Connection0, _, _SSH) ->
- #channel{remote_id = undefined} = Channel =
+ #channel{remote_id = undefined, user = U} = Channel =
ssh_client_channel:cache_lookup(Cache, ChannelId),
- ssh_client_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_msg(Channel, Connection0, {open, ChannelId});
+ if U /= undefined ->
+ ssh_client_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_msg(Channel, Connection0, {open, ChannelId});
+ true ->
+ %% There is no user process so nobody cares about the channel
+ %% close it and remove from the cache, reply from the peer will be
+ %% ignored
+ CloseMsg = channel_close_msg(RemoteId),
+ ssh_client_channel:cache_delete(Cache, ChannelId),
+ {[{connection_reply, CloseMsg}], Connection0}
+ end;
handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId,
reason = Reason,
@@ -533,6 +562,10 @@ handle_msg(#ssh_msg_channel_close{recipi
{Replies, Connection};
undefined ->
+ %% This may happen among other reasons
+ %% - we sent 'channel-close' %% and the peer failed to respond in time
+ %% - we tried to open a channel but the handler died prematurely
+ %% and the channel entry was removed from the cache
{[], Connection0}
end;
@@ -548,21 +581,23 @@ handle_msg(#ssh_msg_channel_extended_dat
channel_data_reply_msg(ChannelId, Connection, DataType, Data);
handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
- bytes_to_add = Add},
+ bytes_to_add = Add},
#connection{channel_cache = Cache} = Connection, _, _SSH) ->
- #channel{send_window_size = Size, remote_id = RemoteId} =
- Channel0 = ssh_client_channel:cache_lookup(Cache, ChannelId),
-
- {SendList, Channel} = %% TODO: Datatype 0 ?
- update_send_window(Channel0#channel{send_window_size = Size + Add},
- 0, undefined, Connection),
-
- Replies = lists:map(fun({Type, Data}) ->
- {connection_reply, channel_data_msg(RemoteId, Type, Data)}
- end, SendList),
- FlowCtrlMsgs = flow_control(Channel, Cache),
- {Replies ++ FlowCtrlMsgs, Connection};
-
+ case ssh_client_channel:cache_lookup(Cache, ChannelId) of
+ Channel0 = #channel{send_window_size = Size,
+ remote_id = RemoteId} ->
+ {SendList, Channel} = %% TODO: Datatype 0 ?
+ update_send_window(Channel0#channel{send_window_size = Size + Add},
+ 0, undefined, Connection),
+ Replies = lists:map(fun({Type, Data}) ->
+ {connection_reply,
+ channel_data_msg(RemoteId, Type, Data)}
+ end, SendList),
+ FlowCtrlMsgs = flow_control(Channel, Cache),
+ {Replies ++ FlowCtrlMsgs, Connection};
+ undefined ->
+ {[], Connection}
+ end;
handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
sender_channel = RemoteId,
initial_window_size = WindowSz,
@@ -603,8 +638,10 @@ handle_msg(#ssh_msg_channel_open{channel
},
#connection{channel_cache = Cache,
channel_id_seed = ChId,
+ suggest_window_size = WinSz,
+ suggest_packet_size = PktSz,
options = Options,
- sub_system_supervisor = SubSysSup
+ connection_supervisor = ConnectionSup
} = C,
client, _SSH) ->
{ReplyMsg, NextChId} =
@@ -612,7 +649,7 @@ handle_msg(#ssh_msg_channel_open{channel
{ok, {ConnectToHost,ConnectToPort}} ->
case gen_tcp:connect(ConnectToHost, ConnectToPort, [{active,false}, binary]) of
{ok,Sock} ->
- {ok,Pid} = ssh_subsystem_sup:start_channel(client, SubSysSup, self(),
+ {ok,Pid} = ssh_connection_sup:start_channel(client, ConnectionSup, self(),
ssh_tcpip_forward_client, ChId,
[Sock], undefined, Options),
ssh_client_channel:cache_update(Cache,
@@ -621,17 +658,15 @@ handle_msg(#ssh_msg_channel_open{channel
local_id = ChId,
remote_id = RemoteId,
user = Pid,
- recv_window_size = ?DEFAULT_WINDOW_SIZE,
- recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ recv_window_size = WinSz,
+ recv_packet_size = PktSz,
send_window_size = WindowSize,
send_packet_size = PacketSize,
send_buf = queue:new()
}),
gen_tcp:controlling_process(Sock, Pid),
inet:setopts(Sock, [{active,once}]),
- {channel_open_confirmation_msg(RemoteId, ChId,
- ?DEFAULT_WINDOW_SIZE,
- ?DEFAULT_PACKET_SIZE),
+ {channel_open_confirmation_msg(RemoteId, ChId, WinSz, PktSz),
ChId + 1};
{error,Error} ->
@@ -661,8 +696,10 @@ handle_msg(#ssh_msg_channel_open{channel
},
#connection{channel_cache = Cache,
channel_id_seed = ChId,
+ suggest_window_size = WinSz,
+ suggest_packet_size = PktSz,
options = Options,
- sub_system_supervisor = SubSysSup
+ connection_supervisor = ConnectionSup
} = C,
server, _SSH) ->
{ReplyMsg, NextChId} =
@@ -678,7 +715,7 @@ handle_msg(#ssh_msg_channel_open{channel
case gen_tcp:connect(binary_to_list(HostToConnect), PortToConnect,
[{active,false}, binary]) of
{ok,Sock} ->
- {ok,Pid} = ssh_subsystem_sup:start_channel(server, SubSysSup, self(),
+ {ok,Pid} = ssh_connection_sup:start_channel(server, ConnectionSup, self(),
ssh_tcpip_forward_srv, ChId,
[Sock], undefined, Options),
ssh_client_channel:cache_update(Cache,
@@ -687,8 +724,8 @@ handle_msg(#ssh_msg_channel_open{channel
local_id = ChId,
remote_id = RemoteId,
user = Pid,
- recv_window_size = ?DEFAULT_WINDOW_SIZE,
- recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ recv_window_size = WinSz,
+ recv_packet_size = PktSz,
send_window_size = WindowSize,
send_packet_size = PacketSize,
send_buf = queue:new()
@@ -696,9 +733,7 @@ handle_msg(#ssh_msg_channel_open{channel
gen_tcp:controlling_process(Sock, Pid),
inet:setopts(Sock, [{active,once}]),
- {channel_open_confirmation_msg(RemoteId, ChId,
- ?DEFAULT_WINDOW_SIZE,
- ?DEFAULT_PACKET_SIZE),
+ {channel_open_confirmation_msg(RemoteId, ChId, WinSz, PktSz),
ChId + 1};
{error,Error} ->
@@ -739,21 +774,35 @@ handle_msg(#ssh_msg_channel_request{reci
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "exit-signal",
want_reply = false,
- data = Data},
+ data = Data},
#connection{channel_cache = Cache} = Connection0, _, _SSH) ->
<<?DEC_BIN(SigName, _SigLen),
- ?BOOLEAN(_Core),
+ ?BOOLEAN(_Core),
?DEC_BIN(Err, _ErrLen),
?DEC_BIN(Lang, _LangLen)>> = Data,
- Channel = ssh_client_channel:cache_lookup(Cache, ChannelId),
- RemoteId = Channel#channel.remote_id,
- {Reply, Connection} = reply_msg(Channel, Connection0,
- {exit_signal, ChannelId,
- binary_to_list(SigName),
- binary_to_list(Err),
- binary_to_list(Lang)}),
- CloseMsg = channel_close_msg(RemoteId),
- {[{connection_reply, CloseMsg}|Reply], Connection};
+ case ssh_client_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = RemoteId, sent_close = SentClose} = Channel ->
+ {Reply, Connection} = reply_msg(Channel, Connection0,
+ {exit_signal, ChannelId,
+ binary_to_list(SigName),
+ binary_to_list(Err),
+ binary_to_list(Lang)}),
+ %% Send 'channel-close' only if it has not been sent yet
+ %% by e.g. our side also closing the channel or going down
+ %% and(!) update the cache
+ %% so that the 'channel-close' is not sent twice
+ if not SentClose ->
+ CloseMsg = channel_close_msg(RemoteId),
+ ssh_client_channel:cache_update(Cache,
+ Channel#channel{sent_close = true}),
+ {[{connection_reply, CloseMsg}|Reply], Connection};
+ true ->
+ {Reply, Connection}
+ end;
+ _ ->
+ %% Channel already closed by peer
+ {[], Connection0}
+ end;
handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
request_type = "xon-xoff",
@@ -811,26 +860,20 @@ handle_msg(#ssh_msg_channel_request{reci
request_type = "pty-req",
want_reply = WantReply,
data = Data},
- Connection, server, SSH) ->
+ Connection, server, _SSH) ->
<<?DEC_BIN(BTermName,_TermLen),
?UINT32(Width),?UINT32(Height),
?UINT32(PixWidth), ?UINT32(PixHeight),
Modes/binary>> = Data,
TermName = binary_to_list(BTermName),
PtyOpts0 = decode_pty_opts(Modes),
- PtyOpts = case SSH#ssh.c_version of
- "SSH-2.0-PuTTY"++_ ->
- %% If - peer client is PuTTY
- %% - it asked for pty
+ PtyOpts = case proplists:get_value(onlcr, PtyOpts0, undefined) of
+ undefined ->
+ %% If - peer client asked for pty
%% - did not tell if LF->CRLF expansion is wanted
%% then
%% - do LF->CRLF expansion
- case proplists:get_value(onlcr, PtyOpts0, undefined) of
- undefined ->
- [{onlcr,1} | PtyOpts0];
- _ ->
- PtyOpts0
- end;
+ [{onlcr,1} | PtyOpts0];
_ ->
PtyOpts0
end,
@@ -918,9 +961,8 @@ handle_msg(#ssh_msg_global_request{name
{[{connection_reply, request_failure_msg()}], Connection};
true ->
- Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
- SubSysSup = proplists:get_value(subsystem_sup, Sups),
- FwdSup = ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup),
+ ConnectionSup = ?GET_INTERNAL_OPT(connection_sup, Opts),
+ FwdSup = ssh_connection_sup:tcpip_fwd_supervisor(ConnectionSup),
ConnPid = self(),
case ssh_tcpip_forward_acceptor:supervised_start(FwdSup,
{ListenAddrStr, ListenPort},
@@ -973,12 +1015,7 @@ handle_msg(#ssh_msg_request_success{data
#connection{requests = [{_, From, Fun} | Rest]} = Connection0, _, _SSH) ->
Connection = Fun({success,Data}, Connection0),
{[{channel_request_reply, From, {success, Data}}],
- Connection#connection{requests = Rest}};
-
-handle_msg(#ssh_msg_disconnect{code = Code,
- description = Description},
- Connection, _, _SSH) ->
- {disconnect, {Code, Description}, handle_stop(Connection)}.
+ Connection#connection{requests = Rest}}.
%%%----------------------------------------------------------------
@@ -1102,16 +1139,19 @@ encode_ip(Addr) when is_list(Addr) ->
%%% of "session" typ is handled
%%%
setup_session(#connection{channel_cache = Cache,
- channel_id_seed = NewChannelID
+ channel_id_seed = NewChannelID,
+ suggest_window_size = WinSz,
+ suggest_packet_size = PktSz
} = C,
- RemoteId, Type, WindowSize, PacketSize) ->
+ RemoteId, Type, WindowSize, PacketSize) when is_integer(WinSz),
+ is_integer(PktSz) ->
NextChannelID = NewChannelID + 1,
Channel =
#channel{type = Type,
sys = "ssh",
local_id = NewChannelID,
- recv_window_size = ?DEFAULT_WINDOW_SIZE,
- recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ recv_window_size = WinSz,
+ recv_packet_size = PktSz,
send_window_size = WindowSize,
send_packet_size = PacketSize,
send_buf = queue:new(),
@@ -1119,9 +1159,9 @@ setup_session(#connection{channel_cache
},
ssh_client_channel:cache_update(Cache, Channel),
OpenConfMsg = channel_open_confirmation_msg(RemoteId, NewChannelID,
- ?DEFAULT_WINDOW_SIZE,
- ?DEFAULT_PACKET_SIZE),
- Reply = {connection_reply, OpenConfMsg},
+ WinSz,
+ PktSz),
+ Reply = {connection_reply, OpenConfMsg},
{[Reply], C#connection{channel_id_seed = NextChannelID}}.
@@ -1131,22 +1171,22 @@ setup_session(#connection{channel_cache
start_cli(#connection{options = Options,
cli_spec = CliSpec,
exec = Exec,
- sub_system_supervisor = SubSysSup}, ChannelId) ->
+ connection_supervisor = ConnectionSup}, ChannelId) ->
case CliSpec of
no_cli ->
{error, cli_disabled};
{CbModule, Args} ->
- ssh_subsystem_sup:start_channel(server, SubSysSup, self(), CbModule, ChannelId, Args, Exec, Options)
+ ssh_connection_sup:start_channel(server, ConnectionSup, self(), CbModule, ChannelId, Args, Exec, Options)
end.
start_subsystem(BinName, #connection{options = Options,
- sub_system_supervisor = SubSysSup},
+ connection_supervisor = ConnectionSup},
#channel{local_id = ChannelId}, _ReplyMsg) ->
Name = binary_to_list(BinName),
case check_subsystem(Name, Options) of
{Callback, Opts} when is_atom(Callback), Callback =/= none ->
- ssh_subsystem_sup:start_channel(server, SubSysSup, self(), Callback, ChannelId, Opts, undefined, Options);
+ ssh_connection_sup:start_channel(server, ConnectionSup, self(), Callback, ChannelId, Opts, undefined, Options);
{none, _} ->
{error, bad_subsystem};
{_, _} ->
@@ -1204,7 +1244,7 @@ get_window(#channel{send_buf = Buffer,
} = Channel, Acc0) ->
case queue:out(Buffer) of
{{value, {_, Data} = Msg}, NewBuffer} ->
- case handle_send_window(Msg, size(Data), PacketSize, WindowSize0, Acc0) of
+ case handle_send_window(Msg, byte_size(Data), PacketSize, WindowSize0, Acc0) of
{WindowSize, Acc, {_, <<>>}} ->
{lists:reverse(Acc), Channel#channel{send_window_size = WindowSize,
send_buf = NewBuffer}};
@@ -1524,7 +1564,7 @@ handle_cli_msg(C0, ChId, Reply0) ->
channel_data_reply_msg(ChannelId, Connection, DataType, Data) ->
case ssh_client_channel:cache_lookup(Connection#connection.channel_cache, ChannelId) of
#channel{recv_window_size = Size} = Channel ->
- WantedSize = Size - size(Data),
+ WantedSize = Size - byte_size(Data),
ssh_client_channel:cache_update(Connection#connection.channel_cache,
Channel#channel{recv_window_size = WantedSize}),
reply_msg(Channel, Connection, {data, ChannelId, DataType, Data});
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_handler.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_connection_handler.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_handler.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,27 +34,34 @@
-include("ssh_transport.hrl").
-include("ssh_auth.hrl").
-include("ssh_connect.hrl").
+-include("ssh_fsm.hrl").
%%====================================================================
%%% Exports
%%====================================================================
%%% Start and stop
--export([start_link/3,
+-export([start_link/3, start_link/4,
+ takeover/4,
stop/1
]).
%%% Internal application API
--export([start_connection/4,
- available_hkey_algorithms/2,
+-export([available_hkey_algorithms/2,
open_channel/6,
start_channel/5,
+ handshake/2,
handle_direct_tcpip/6,
request/6, request/7,
- reply_request/3,
+ reply_request/3,
global_request/5,
+ handle_ssh_msg_ext_info/2,
send/5,
+ send_bytes/2,
+ send_msg/2,
send_eof/2,
+ send_disconnect/6,
+ send_disconnect/7,
store/3,
retrieve/2,
info/1, info/2,
@@ -67,16 +74,12 @@
prohibited_sock_option/1
]).
--type connection_ref() :: ssh:connection_ref().
--type channel_id() :: ssh:channel_id().
-
%%% Behaviour callbacks
-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
- init_ssh_record/3, % Export of this internal function
+-export([init_ssh_record/3, % Export of this internal function
% intended for low-level protocol test suites
renegotiate/1, alg/1 % Export intended for test cases
]).
@@ -87,31 +90,54 @@
ssh_dbg_format/2, ssh_dbg_format/3]).
--define(send_disconnect(Code, DetailedText, StateName, State),
- send_disconnect(Code, DetailedText, ?MODULE, ?LINE, StateName, State)).
-
--define(send_disconnect(Code, Reason, DetailedText, StateName, State),
- send_disconnect(Code, Reason, DetailedText, ?MODULE, ?LINE, StateName, State)).
-
-define(call_disconnectfun_and_log_cond(LogMsg, DetailedText, StateName, D),
call_disconnectfun_and_log_cond(LogMsg, DetailedText, ?MODULE, ?LINE, StateName, D)).
%%====================================================================
%% Start / stop
%%====================================================================
-%%--------------------------------------------------------------------
--spec start_link(role(),
- gen_tcp:socket(),
- internal_options()
- ) -> {ok, pid()}.
-%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
start_link(Role, Socket, Options) ->
- {ok, proc_lib:spawn_opt(?MODULE,
- init_connection_handler,
- [Role, Socket, Options],
- [link, {message_queue_data,off_heap}]
- )}.
+ start_link(Role, undefined, Socket, Options).
+
+start_link(Role, Id, Socket, Options) ->
+ case gen_statem:start_link(?MODULE,
+ [Role, Socket, Options],
+ [{spawn_opt, [{message_queue_data,off_heap}]}]) of
+
+ {ok, Pid} when Id =/= undefined ->
+ %% Announce the ConnectionRef to the system supervisor so it could
+ %% 1) initiate the socket handover, and
+ %% 2) be returned to whoever called for example ssh:connect; the Pid
+ %% returned from this function is "consumed" by the connection
+ %% supervisor.
+ ?GET_INTERNAL_OPT(user_pid,Options) ! {new_connection_ref, Id, Pid},
+ {ok, Pid};
+ Others ->
+ Others
+ end.
+
+takeover(ConnPid, Role, Socket, Options) ->
+ case Role of
+ client ->
+ group_leader(group_leader(), ConnPid);
+ _ ->
+ ok
+ end,
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ case Callback:controlling_process(Socket, ConnPid) of
+ ok ->
+ Ref = erlang:monitor(process, ConnPid),
+ gen_statem:cast(ConnPid, socket_control),
+ NegTimeout = ?GET_INTERNAL_OPT(negotiation_timeout,
+ Options,
+ ?GET_OPT(negotiation_timeout, Options)
+ ),
+ handshake(ConnPid, Role, Ref, NegTimeout);
+ {error, Reason} ->
+ {error, Reason}
+ end.
%%--------------------------------------------------------------------
-spec stop(connection_ref()
@@ -130,46 +156,6 @@ stop(ConnectionHandler)->
%%====================================================================
%%--------------------------------------------------------------------
--spec start_connection(role(),
- gen_tcp:socket(),
- internal_options(),
- timeout()
- ) -> {ok, connection_ref()} | {error, term()}.
-%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-start_connection(Role, Socket, Options, Timeout) ->
- try
- case Role of
- client ->
- ChildPid = start_the_connection_child(self(), Role, Socket, Options),
- handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout);
- server ->
- case ?GET_OPT(parallel_login, Options) of
- true ->
- HandshakerPid =
- spawn_link(fun() ->
- process_flag(trap_exit, true),
- receive
- {do_handshake, Pid} ->
- handshake(Pid, erlang:monitor(process,Pid), Timeout)
- after Timeout ->
- {error, timeout2}
- end
- end),
- ChildPid = start_the_connection_child(HandshakerPid, Role, Socket, Options),
- HandshakerPid ! {do_handshake, ChildPid};
- false ->
- ChildPid = start_the_connection_child(self(), Role, Socket, Options),
- handshake(ChildPid, erlang:monitor(process,ChildPid), Timeout)
- end
- end
- catch
- exit:{noproc, _} ->
- {error, ssh_not_started};
- _:Error ->
- {error, Error}
- end.
-
-%%--------------------------------------------------------------------
%%% Some other module has decided to disconnect.
-spec disconnect(Code::integer(), Details::iodata(),
@@ -188,18 +174,18 @@ disconnect(Code, DetailedText, Module, L
-spec open_channel(connection_ref(),
string(),
iodata(),
- pos_integer(),
- pos_integer(),
+ pos_integer() | undefined,
+ pos_integer() | undefined,
timeout()
) -> {open, channel_id()} | {error, term()}.
-
+
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-open_channel(ConnectionHandler,
+open_channel(ConnectionHandler,
ChannelType, ChannelSpecificData, InitialWindowSize, MaxPacketSize,
Timeout) ->
call(ConnectionHandler,
- {open,
- self(),
+ {open,
+ self(),
ChannelType, InitialWindowSize, MaxPacketSize, ChannelSpecificData,
Timeout}).
@@ -210,8 +196,8 @@ open_channel(ConnectionHandler,
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
start_channel(ConnectionHandler, CallbackModule, ChannelId, Args, Exec) ->
- {ok, {SubSysSup,Role,Opts}} = call(ConnectionHandler, get_misc),
- ssh_subsystem_sup:start_channel(Role, SubSysSup,
+ {ok, {ConnectionSup,Role,Opts}} = call(ConnectionHandler, get_misc),
+ ssh_connection_sup:start_channel(Role, ConnectionSup,
ConnectionHandler, CallbackModule, ChannelId,
Args, Exec, Opts).
@@ -252,7 +238,7 @@ request(ConnectionHandler, ChannelId, Ty
success | failure,
channel_id()
) -> ok.
-
+
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
reply_request(ConnectionHandler, Status, ChannelId) ->
cast(ConnectionHandler, {reply_request, Status, ChannelId}).
@@ -262,7 +248,7 @@ global_request(ConnectionHandler, Type,
call(ConnectionHandler, {global_request, Type, Data, Timeout});
global_request(ConnectionHandler, Type, false, Data, _) ->
cast(ConnectionHandler, {global_request, Type, Data}).
-
+
%%--------------------------------------------------------------------
-spec send(connection_ref(),
channel_id(),
@@ -355,10 +341,10 @@ close(ConnectionHandler, ChannelId) ->
%%--------------------------------------------------------------------
store(ConnectionHandler, Key, Value) ->
cast(ConnectionHandler, {store,Key,Value}).
-
+
retrieve(#connection{options=Opts}, Key) ->
try ?GET_INTERNAL_OPT(Key, Opts) of
- Value ->
+ Value ->
{ok,Value}
catch
error:{badkey,Key} ->
@@ -366,7 +352,7 @@ retrieve(#connection{options=Opts}, Key)
end;
retrieve(ConnectionHandler, Key) ->
call(ConnectionHandler, {retrieve,Key}).
-
+
%%--------------------------------------------------------------------
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
set_sock_opts(ConnectionRef, SocketOptions) ->
@@ -412,133 +398,37 @@ alg(ConnectionHandler) ->
call(ConnectionHandler, get_alg).
%%====================================================================
-%% Internal process state
-%%====================================================================
--record(data, {
- starter :: pid()
- | undefined,
- auth_user :: string()
- | undefined,
- connection_state :: #connection{}
- | undefined,
- latest_channel_id = 0 :: non_neg_integer()
- | undefined,
- transport_protocol :: atom()
- | undefined, % ex: tcp
- transport_cb :: atom()
- | undefined, % ex: gen_tcp
- transport_close_tag :: atom()
- | undefined, % ex: tcp_closed
- ssh_params :: #ssh{}
- | undefined,
- socket :: gen_tcp:socket()
- | undefined,
- decrypted_data_buffer = <<>> :: binary()
- | undefined,
- encrypted_data_buffer = <<>> :: binary()
- | undefined,
- aead_data = <<>> :: binary()
- | undefined,
- undecrypted_packet_length :: undefined | non_neg_integer(),
- key_exchange_init_msg :: #ssh_msg_kexinit{}
- | undefined,
- last_size_rekey = 0 :: non_neg_integer(),
- event_queue = [] :: list(),
- inet_initial_recbuf_size :: pos_integer()
- | undefined
- }).
-
-%%====================================================================
%% Intitialisation
%%====================================================================
-%%--------------------------------------------------------------------
--spec init_connection_handler(role(),
- gen_tcp:socket(),
- internal_options()
- ) -> no_return().
-%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-init_connection_handler(Role, Socket, Opts) ->
- case init([Role, Socket, Opts]) of
- {ok, StartState, D} when Role == server ->
- process_flag(trap_exit, true),
- gen_statem:enter_loop(?MODULE,
- [], %%[{debug,[trace,log,statistics,debug]} ], %% []
- StartState,
- D);
-
- {ok, StartState, D0=#data{connection_state=C}} when Role == client ->
- process_flag(trap_exit, true),
- Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
- D = D0#data{connection_state =
- C#connection{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)
- }},
- gen_statem:enter_loop(?MODULE,
- [], %%[{debug,[trace,log,statistics,debug]} ], %% []
- StartState,
- D);
-
- {stop, Error} ->
- D = try
- %% Only servers have supervisorts defined in Opts
- Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
- #connection{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)
- }
- of
- C ->
- #data{connection_state=C}
- catch
- _:_ ->
- #data{connection_state=#connection{}}
- end,
- gen_statem:enter_loop(?MODULE,
- [],
- {init_error,Error},
- D#data{socket=Socket})
- end.
-
-
+init([Role, Socket, Opts]) when Role==client ; Role==server ->
+ process_flag(trap_exit, true),
+ %% ssh_params will be updated after receiving socket_control event
+ %% in wait_for_socket state;
+ D = #data{socket = Socket, ssh_params = #ssh{role = Role, opts = Opts}},
+ {ok, {wait_for_socket, Role}, D}.
-init([Role,Socket,Opts]) ->
- case inet:peername(Socket) of
- {ok, PeerAddr} ->
- {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts),
- C = #connection{channel_cache = ssh_client_channel:cache_create(),
- channel_id_seed = 0,
- 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 ->
- D0;
- server ->
- Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
- 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}
+%%%----------------------------------------------------------------
+%%% Connection start and initialization helpers
+init_connection_record(Role, Socket, Opts) ->
+ {WinSz, PktSz} = init_inet_buffers_window(Socket),
+ C = #connection{channel_cache = ssh_client_channel:cache_create(),
+ channel_id_seed = 0,
+ suggest_window_size = WinSz,
+ suggest_packet_size = PktSz,
+ requests = [],
+ options = Opts,
+ connection_supervisor = ?GET_INTERNAL_OPT(connection_sup, Opts)
+ },
+ case Role of
+ server ->
+ C#connection{cli_spec =
+ ?GET_OPT(ssh_cli, Opts, {ssh_cli,[?GET_OPT(shell, Opts)]}),
+ exec =
+ ?GET_OPT(exec, Opts)};
+ client ->
+ C
end.
-
-
init_ssh_record(Role, Socket, Opts) ->
%% Export of this internal function is
%% intended for low-level protocol test suites
@@ -596,7 +486,46 @@ init_ssh_record(Role, Socket, PeerAddr,
}
end.
+handshake(ConnPid, server, Ref, Timeout) ->
+ receive
+ {ConnPid, ssh_connected} ->
+ erlang:demonitor(Ref, [flush]),
+ {ok, ConnPid};
+ {ConnPid, {not_connected, Reason}} ->
+ erlang:demonitor(Ref, [flush]),
+ {error, Reason};
+ {'DOWN', Ref, process, ConnPid, {shutdown, Reason}} ->
+ {error, Reason};
+ {'DOWN', Ref, process, ConnPid, Reason} ->
+ {error, Reason};
+ {'EXIT',_,Reason} ->
+ stop(ConnPid),
+ {error, {exit,Reason}}
+ after Timeout ->
+ erlang:demonitor(Ref, [flush]),
+ ssh_connection_handler:stop(ConnPid),
+ {error, timeout}
+ end;
+handshake(ConnPid, client, Ref, Timeout) ->
+ receive
+ {ConnPid, ssh_connected} ->
+ erlang:demonitor(Ref, [flush]),
+ {ok, ConnPid};
+ {ConnPid, {not_connected, Reason}} ->
+ erlang:demonitor(Ref, [flush]),
+ {error, Reason};
+ {'DOWN', Ref, process, ConnPid, {shutdown, Reason}} ->
+ {error, Reason};
+ {'DOWN', Ref, process, ConnPid, Reason} ->
+ {error, Reason}
+ after Timeout ->
+ erlang:demonitor(Ref, [flush]),
+ ssh_connection_handler:stop(ConnPid),
+ {error, timeout}
+ end.
+handshake(Msg, #data{starter = User}) ->
+ User ! {self(), Msg}.
%%====================================================================
%% gen_statem callbacks
@@ -625,16 +554,12 @@ init_ssh_record(Role, Socket, PeerAddr,
%% 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 == renegotiate;
renegotiation(_) -> false.
--define(CONNECTED(StateName),
+-define(CONNECTED(StateName),
(element(1,StateName) == connected orelse
element(1,StateName) == ext_info ) ).
@@ -643,51 +568,59 @@ renegotiation(_) -> false.
state_name(),
#data{}
) -> gen_statem:event_handler_result(state_name()) .
-
+
-define(CONNECTION_MSG(Msg),
[{next_event, internal, prepare_next_packet},
{next_event,internal,{conn_msg,Msg}}]).
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
callback_mode() ->
[handle_event_function,
state_enter].
+%%% ######## {hello, client|server} ####
+%% The very first event that is sent when ssh_connection_handler
+%% becomes owner process for Socket
+handle_event(cast, socket_control, {wait_for_socket, Role},
+ #data{socket = Socket, ssh_params = #ssh{opts = Opts}}) ->
+ case inet:peername(Socket) of
+ {ok, PeerAddr} ->
+ try
+ {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts),
+ D = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts),
+ socket = Socket,
+ transport_protocol = Protocol,
+ transport_cb = Callback,
+ transport_close_tag = CloseTag,
+ ssh_params = init_ssh_record(Role, Socket, PeerAddr, Opts),
+ connection_state = init_connection_record(Role, Socket, Opts)
+ },
+ NextEvent = {next_event, internal, socket_ready},
+ {next_state, {hello,Role}, D, NextEvent}
+ catch
+ _:{error,Error} -> {stop, {error,Error}};
+ error:Error -> {stop, {error,Error}}
+ end;
-handle_event(_, _Event, {init_error,Error}=StateName, D) ->
- case Error of
- enotconn ->
- %% Handles the abnormal sequence:
- %% SYN->
- %% <-SYNACK
- %% ACK->
- %% RST->
- ?call_disconnectfun_and_log_cond("Protocol Error",
- "TCP connenction to server was prematurely closed by the client",
- StateName, D),
- {stop, {shutdown,"TCP connenction to server was prematurely closed by the client"}};
-
- OtherError ->
- {stop, {shutdown,{init,OtherError}}}
+ {error,Error} ->
+ {stop, {shutdown,Error}}
end;
-%%% ######## {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,_}=StateName, #data{ssh_params = Ssh0} = D) ->
+handle_event(internal, socket_ready, {hello,_}=StateName, #data{ssh_params = Ssh0} = D) ->
VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh0)),
send_bytes(VsnMsg, D),
- case inet:getopts(Socket=D#data.socket, [recbuf]) of
- {ok, [{recbuf,Size}]} ->
+ case inet:getopts(Socket=D#data.socket, [buffer]) of
+ {ok, [{buffer,Size}]} ->
%% Set the socket to the hello text line handling mode:
inet:setopts(Socket, [{packet, line},
{active, once},
% Expecting the version string which might
% be max ?MAX_PROTO_VERSION bytes:
- {recbuf, ?MAX_PROTO_VERSION},
+ {buffer, ?MAX_PROTO_VERSION},
+ {packet_size, ?MAX_PROTO_VERSION},
{nodelay,true}]),
Time = ?GET_OPT(hello_timeout, Ssh0#ssh.opts, infinity),
- {keep_state, D#data{inet_initial_recbuf_size=Size}, [{state_timeout,Time,no_hello_received}] };
+ {keep_state, D#data{inet_initial_buffer_size=Size}, [{state_timeout,Time,no_hello_received}] };
Other ->
?call_disconnectfun_and_log_cond("Option return",
@@ -696,39 +629,40 @@ handle_event(_, socket_control, {hello,_
{stop, {shutdown,{unexpected_getopts_return, Other}}}
end;
-handle_event(_, {info_line,Line}, {hello,Role}=StateName, 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
- send_bytes("Protocol mismatch.", D),
- Msg = io_lib:format("Protocol mismatch in version exchange. Client sent info lines.~n~s",
- [ssh_dbg:hex_dump(Line, 64)]),
- ?call_disconnectfun_and_log_cond("Protocol mismatch.", Msg, StateName, D),
- {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}}
- end;
+handle_event(internal, {info_line,_Line}, {hello,client}, D) ->
+ %% 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;
+
+handle_event(internal, {info_line,Line}, {hello,server}=StateName, D) ->
+ %% But the client may NOT send them to the server. Openssh answers with cleartext,
+ %% and so do we
+ send_bytes("Protocol mismatch.", D),
+ Msg = io_lib:format("Protocol mismatch in version exchange. Client sent info lines.~n~s",
+ [ssh_dbg:hex_dump(Line, 64)]),
+ ?call_disconnectfun_and_log_cond("Protocol mismatch.", Msg, StateName, D),
+ {stop, {shutdown,"Protocol mismatch in version exchange. Client sent info lines."}};
-handle_event(_, {version_exchange,Version}, {hello,Role}, D0) ->
+handle_event(internal, {version_exchange,Version}, {hello,Role}, D0) ->
{NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version),
case handle_version(NumVsn, StrVsn, D0#data.ssh_params) of
{ok, Ssh1} ->
- %% Since the hello part is finnished correctly, we set the
- %% socket to the packet handling mode (including recbuf size):
+ %% Since the hello part is finished correctly, we set the
+ %% socket to the packet handling mode (including buffer size):
inet:setopts(D0#data.socket, [{packet,0},
{mode,binary},
{active, once},
- {recbuf, D0#data.inet_initial_recbuf_size}]),
+ {buffer, D0#data.inet_initial_buffer_size},
+ {packet_size, 0}]),
{KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1),
send_bytes(SshPacket, D0),
- {next_state, {kexinit,Role,init}, D0#data{ssh_params = Ssh,
- key_exchange_init_msg = KeyInitMsg}};
+ D = D0#data{ssh_params = Ssh,
+ key_exchange_init_msg = KeyInitMsg},
+ {next_state, {kexinit,Role,init}, D, {change_callback_module, ssh_fsm_kexinit}};
+
not_supported ->
- {Shutdown, D} =
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
io_lib:format("Offending version is ~p",[string:chomp(Version)]),
{hello,Role},
@@ -736,403 +670,85 @@ handle_event(_, {version_exchange,Versio
{stop, Shutdown, D}
end;
-handle_event(_, no_hello_received, {hello,_Role}=StateName, D0) ->
+%%% timeout after tcp:connect but then nothing arrives
+handle_event(state_timeout, no_hello_received, {hello,_Role}=StateName, D0 = #data{ssh_params = Ssh0}) ->
+ MsgFun =
+ fun (debug) ->
+ Time = ?GET_OPT(hello_timeout, Ssh0#ssh.opts),
+ lists:concat(["No HELLO received within ",ssh_lib:format_time_ms(Time)]);
+ (_) ->
+ ["No HELLO received within hello_timeout"]
+ end,
{Shutdown, D} =
- ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, "No HELLO recieved", StateName, D0),
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, ?SELECT_MSG(MsgFun), StateName, D0),
{stop, Shutdown, D};
-
-%%% ######## {kexinit, client|server, init|renegotiate} ####
-
-handle_event(_, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
- D = #data{key_exchange_init_msg = OwnKex}) ->
- 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 ->
- send_bytes(NextKexMsg, D),
- Ssh2;
- {ok, Ssh2} when Role==server ->
- Ssh2
- end,
- {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}};
-
-
-%%% ######## {key_exchange, client|server, init|renegotiate} ####
-
-%%%---- 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),
- 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, 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, 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, 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),
- 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),
- 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, 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}};
-
-
-%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} ####
-
-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),
- 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}};
-
-
-%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} ####
-
-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),
- 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,client,init}, D0) ->
- {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params),
- %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
- %% send_bytes(ExtInfo, D0),
- {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
- D = send_msg(MsgReq, D0#data{ssh_params = Ssh}),
- {next_state, {ext_info,client,init}, D};
-
-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{} = 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};
-
-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}, D0) ->
+%%% ######## {service_request, client|server} #### StateName ==
+%% {userauth,server} guard added due to interoperability with clients
+%% sending extra ssh_msg_service_request (e.g. Paramiko for Python,
+%% see GH-6463)
+handle_event(internal, Msg = #ssh_msg_service_request{name=ServiceName}, StateName, D0)
+ when StateName == {service_request,server}; StateName == {userauth,server} ->
case ServiceName of
"ssh-userauth" ->
Ssh0 = #ssh{session_id=SessionId} = D0#data.ssh_params,
{ok, {Reply, Ssh}} = ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0),
D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {next_state, {userauth,server}, D};
+ {next_state, {userauth,server}, D, {change_callback_module,ssh_fsm_userauth_server}};
_ ->
- {Shutdown, D} =
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
io_lib:format("Unknown service: ~p",[ServiceName]),
StateName, D0),
{stop, Shutdown, D}
end;
-handle_event(_, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
+handle_event(internal, #ssh_msg_service_accept{name = "ssh-userauth"}, {service_request,client},
#data{ssh_params = #ssh{service="ssh-userauth"} = Ssh0} = D0) ->
{Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
D = send_msg(Msg, D0#data{ssh_params = Ssh,
auth_user = Ssh#ssh.user
}),
- {next_state, {userauth,client}, D};
-
-
-%%% ######## {userauth, client|server} ####
-
-%%---- userauth request to server
-handle_event(_,
- Msg = #ssh_msg_userauth_request{service = ServiceName, method = Method},
- StateName = {userauth,server},
- D0 = #data{ssh_params=Ssh0}) ->
-
- case {ServiceName, Ssh0#ssh.service, Method} of
- {"ssh-connection", "ssh-connection", "none"} ->
- %% 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),
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {keep_state, D};
-
- {"ssh-connection", "ssh-connection", Method} ->
- %% Userauth request with a method like "password" or so
- case lists:member(Method, Ssh0#ssh.userauth_methods) of
- true ->
- %% Yepp! we support this method
- case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
- {authorized, User, {Reply, Ssh1}} ->
- D = #data{ssh_params=Ssh} =
- send_msg(Reply, D0#data{ssh_params = Ssh1}),
- D#data.starter ! ssh_connected,
- connected_fun(User, Method, D),
- {next_state, {connected,server},
- D#data{auth_user=User,
- %% Note: authenticated=true MUST NOT be sent
- %% before send_msg!
- ssh_params = Ssh#ssh{authenticated = true}}};
- {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
- retry_fun(User, Reason, D0),
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {next_state, {userauth_keyboard_interactive,server}, D};
- {not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Reason, D0),
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {keep_state, D}
- end;
- false ->
- %% No we do not support this method (=/= none)
- %% At least one non-erlang client does like this. Retry as the next event
- {keep_state_and_data,
- [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}]
- }
- end;
-
- %% {"ssh-connection", Expected, Method} when Expected =/= ServiceName -> Do what?
- %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what?
-
- {ServiceName, _, _} when ServiceName =/= "ssh-connection" ->
- {Shutdown, D} =
- ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
- io_lib:format("Unknown service: ~p",[ServiceName]),
- StateName, D0),
- {stop, Shutdown, D}
- 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}) ->
- ssh_auth:ssh_msg_userauth_result(success),
- D#data.starter ! ssh_connected,
- {next_state, {connected,client}, D#data{ssh_params=Ssh#ssh{authenticated = true}}};
-
-
-%%---- userauth failure response to client
-handle_event(_, #ssh_msg_userauth_failure{}, {userauth,client}=StateName,
- #data{ssh_params = #ssh{userauth_methods = []}} = D0) ->
- {Shutdown, D} =
- ?send_disconnect(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
- io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
- StateName, D0),
- {stop, Shutdown, D};
-handle_event(_, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client},
- D0 = #data{ssh_params = Ssh0}) ->
- %% The prefered authentication method failed try next method
- Ssh1 = case Ssh0#ssh.userauth_methods of
- none ->
- %% Server tells us which authentication methods that are allowed
- Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")};
- _ ->
- %% We already know...
- Ssh0
- end,
- case ssh_auth:userauth_request_msg(Ssh1) of
- {send_disconnect, Code, Ssh} ->
- {Shutdown, D} =
- ?send_disconnect(Code,
- io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
- StateName, D0#data{ssh_params = Ssh}),
- {stop, Shutdown, D};
- {"keyboard-interactive", {Msg, Ssh}} ->
- D = send_msg(Msg, D0#data{ssh_params = Ssh}),
- {next_state, {userauth_keyboard_interactive,client}, D};
- {_Method, {Msg, Ssh}} ->
- D = send_msg(Msg, D0#data{ssh_params = Ssh}),
- {keep_state, D}
- end;
-
-%%---- banner to client
-handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) ->
- case D#data.ssh_params#ssh.userauth_quiet_mode of
- false -> io:format("~s", [Msg]);
- true -> ok
- end,
- keep_state_and_data;
+ {next_state, {userauth,client}, D, {change_callback_module,ssh_fsm_userauth_client}};
-%%% ######## {userauth_keyboard_interactive, client|server}
-
-handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
- #data{ssh_params = Ssh0} = D0) ->
- case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
- {ok, {Reply, Ssh}} ->
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {next_state, {userauth_keyboard_interactive_info_response,client}, D};
- not_ok ->
- {next_state, {userauth,client}, D0, [postpone]}
- end;
-
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) ->
- case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of
- {authorized, User, {Reply, Ssh1}} ->
- D = #data{ssh_params=Ssh} =
- send_msg(Reply, D0#data{ssh_params = Ssh1}),
- D#data.starter ! ssh_connected,
- connected_fun(User, "keyboard-interactive", D),
- {next_state, {connected,server}, D#data{auth_user = User,
- %% Note: authenticated=true MUST NOT be sent
- %% before send_msg!
- ssh_params = Ssh#ssh{authenticated = true}}};
- {not_authorized, {User, Reason}, {Reply, Ssh}} ->
- retry_fun(User, Reason, D0),
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {next_state, {userauth,server}, D};
-
- {authorized_but_one_more, _User, {Reply, Ssh}} ->
- D = send_msg(Reply, D0#data{ssh_params = Ssh}),
- {next_state, {userauth_keyboard_interactive_extra,server}, D}
- end;
-
-handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) ->
- {authorized, User, {Reply, Ssh1}} =
- ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params),
- D = #data{ssh_params=Ssh} =
- send_msg(Reply, D0#data{ssh_params = Ssh1}),
- D#data.starter ! ssh_connected,
- connected_fun(User, "keyboard-interactive", D),
- {next_state, {connected,server}, D#data{auth_user = User,
- %% Note: authenticated=true MUST NOT be sent
- %% before send_msg!
- 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, [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(_, #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(_, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
- {next_state, {userauth,client}, D, [postpone]};
-
-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} ####
-
%% Skip ext_info messages in connected state (for example from OpenSSH >= 7.7)
-handle_event(_, #ssh_msg_ext_info{}, {connected,_Role}, D) ->
+handle_event(internal, #ssh_msg_ext_info{}, {connected,_Role}, D) ->
{keep_state, D};
-handle_event(_, {#ssh_msg_kexinit{},_}, {connected,Role}, D0) ->
+handle_event(internal, {#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, [postpone]};
+ {next_state, {kexinit,Role,renegotiate}, D, [postpone, {change_callback_module,ssh_fsm_kexinit}]};
-handle_event(_, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
+handle_event(internal, #ssh_msg_disconnect{description=Desc} = Msg, StateName, D0) ->
{disconnect, _, RepliesCon} =
- ssh_connection:handle_msg(Msg, D0#data.connection_state, role(StateName), D0#data.ssh_params),
+ ssh_connection:handle_msg(Msg, D0#data.connection_state, ?role(StateName), D0#data.ssh_params),
{Actions,D} = send_replies(RepliesCon, D0),
disconnect_fun("Received disconnect: "++Desc, D),
{stop_and_reply, {shutdown,Desc}, Actions, D};
-handle_event(_, #ssh_msg_ignore{}, _, _) ->
+handle_event(internal, #ssh_msg_ignore{}, _StateName, _) ->
keep_state_and_data;
-handle_event(_, #ssh_msg_unimplemented{}, _, _) ->
+handle_event(internal, #ssh_msg_unimplemented{}, _StateName, _) ->
keep_state_and_data;
-handle_event(_, #ssh_msg_debug{} = Msg, _, D) ->
+%% Quick fix of failing test case (ssh_options_SUITE:ssh_msg_debug_fun_option_{client|server}/1)
+handle_event(cast, #ssh_msg_debug{} = Msg, State, D) ->
+ handle_event(internal, Msg, State, D);
+
+handle_event(internal, #ssh_msg_debug{} = Msg, _StateName, D) ->
debug_fun(Msg, D),
keep_state_and_data;
-handle_event(internal, {conn_msg,Msg}, StateName, #data{starter = User,
- connection_state = Connection0,
+handle_event(internal, {conn_msg,Msg}, StateName, #data{connection_state = Connection0,
event_queue = Qev0} = D0) ->
- Role = role(StateName),
+ Role = ?role(StateName),
Rengotation = renegotiation(StateName),
try ssh_connection:handle_msg(Msg, Connection0, Role, D0#data.ssh_params) of
{disconnect, Reason0, RepliesConn} ->
@@ -1140,7 +756,7 @@ handle_event(internal, {conn_msg,Msg}, S
case {Reason0,Role} of
{{_, Reason}, client} when ((StateName =/= {connected,client})
and (not Rengotation)) ->
- User ! {self(), not_connected, Reason};
+ handshake({not_connected,Reason}, D);
_ ->
ok
end,
@@ -1178,7 +794,7 @@ handle_event(internal, {conn_msg,Msg}, S
handle_event(enter, OldState, {connected,_}=NewState, D) ->
%% Entering the state where re-negotiation is possible
init_renegotiate_timers(OldState, NewState, D);
-
+
handle_event(enter, OldState, {ext_info,_,renegotiate}=NewState, D) ->
%% Could be hanging in exit_info state if nothing else arrives
init_renegotiate_timers(OldState, NewState, D);
@@ -1247,7 +863,7 @@ handle_event(cast, {adjust_window,Channe
handle_event(cast, {reply_request,Resp,ChannelId}, StateName, D) when ?CONNECTED(StateName) ->
case ssh_client_channel:cache_lookup(cache(D), ChannelId) of
#channel{remote_id = RemoteId} when Resp== success ; Resp==failure ->
- Msg =
+ Msg =
case Resp of
success -> ssh_connection:channel_success_msg(RemoteId);
failure -> ssh_connection:channel_failure_msg(RemoteId)
@@ -1257,7 +873,9 @@ handle_event(cast, {reply_request,Resp,C
#channel{} ->
Details = io_lib:format("Unhandled reply in state ~p:~n~p", [StateName,Resp]),
- ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D);
+ {_Shutdown, D1} =
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D),
+ {keep_state, D1};
undefined ->
keep_state_and_data
@@ -1285,7 +903,7 @@ handle_event({call,From}, get_print_info
inet:peername(D#data.socket)
}
of
- {{ok,Local}, {ok,Remote}} ->
+ {{ok,Local}, {ok,Remote}} ->
{{Local,Remote},io_lib:format("statename=~p",[StateName])};
_ ->
{{"-",0},"-"}
@@ -1315,7 +933,7 @@ handle_event({call,From}, {info, all}, _
end,
[], cache(D)),
{keep_state_and_data, [{reply, From, {ok,Result}}]};
-
+
handle_event({call,From}, {info, ChannelPid}, _, D) ->
Result = ssh_client_channel:cache_foldl(
fun(Channel, Acc) when Channel#channel.user == ChannelPid ->
@@ -1346,24 +964,24 @@ handle_event({call,From}, stop, _StateNa
handle_event({call,_}, _, StateName, _) when not ?CONNECTED(StateName) ->
{keep_state_and_data, [postpone]};
-handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0)
+handle_event({call,From}, {request, ChannelPid, ChannelId, Type, Data, Timeout}, StateName, D0)
when ?CONNECTED(StateName) ->
case handle_request(ChannelPid, ChannelId, Type, Data, true, From, D0) of
{error,Error} ->
{keep_state, D0, {reply,From,{error,Error}}};
D ->
- %% Note reply to channel will happen later when reply is recived from peer on the socket
+ %% Note reply to channel will happen later when reply is received from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, D, cond_set_idle_timer(D)}
end;
-handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0)
+handle_event({call,From}, {request, ChannelId, Type, Data, Timeout}, StateName, D0)
when ?CONNECTED(StateName) ->
case handle_request(ChannelId, Type, Data, true, From, D0) of
{error,Error} ->
{keep_state, D0, {reply,From,{error,Error}}};
D ->
- %% Note reply to channel will happen later when reply is recived from peer on the socket
+ %% Note reply to channel will happen later when reply is received from peer on the socket
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, D, cond_set_idle_timer(D)}
end;
@@ -1396,14 +1014,14 @@ handle_event({call,From}, {global_reques
start_channel_request_timer(Id, From, Timeout),
{keep_state, D, cond_set_idle_timer(D)};
-handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)
+handle_event({call,From}, {data, ChannelId, Type, Data, Timeout}, StateName, D0)
when ?CONNECTED(StateName) ->
{Repls,D} = send_replies(ssh_connection:channel_data(ChannelId, Type, Data, D0#data.connection_state, From),
D0),
start_channel_request_timer(ChannelId, From, Timeout), % FIXME: No message exchange so why?
{keep_state, D, Repls};
-handle_event({call,From}, {eof, ChannelId}, StateName, D0)
+handle_event({call,From}, {eof, ChannelId}, StateName, D0)
when ?CONNECTED(StateName) ->
case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of
#channel{remote_id = Id, sent_close = false} ->
@@ -1415,35 +1033,40 @@ handle_event({call,From}, {eof, ChannelI
handle_event({call,From}, get_misc, StateName,
#data{connection_state = #connection{options = Opts}} = D) when ?CONNECTED(StateName) ->
- Sups = ?GET_INTERNAL_OPT(supervisors, Opts),
- SubSysSup = proplists:get_value(subsystem_sup, Sups),
- Reply = {ok, {SubSysSup, role(StateName), Opts}},
+ ConnectionSup = ?GET_INTERNAL_OPT(connection_sup, Opts),
+ Reply = {ok, {ConnectionSup, ?role(StateName), Opts}},
{keep_state, D, [{reply,From,Reply}]};
handle_event({call,From},
{open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data, Timeout},
StateName,
- D0) when ?CONNECTED(StateName) ->
+ D0 = #data{connection_state = C}) when ?CONNECTED(StateName) ->
erlang:monitor(process, ChannelPid),
{ChannelId, D1} = new_channel_id(D0),
- D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId,
- InitialWindowSize,
- MaxPacketSize, Data),
+ WinSz = case InitialWindowSize of
+ undefined -> C#connection.suggest_window_size;
+ _ -> InitialWindowSize
+ end,
+ PktSz = case MaxPacketSize of
+ undefined -> C#connection.suggest_packet_size;
+ _ -> MaxPacketSize
+ end,
+ D2 = send_msg(ssh_connection:channel_open_msg(Type, ChannelId, WinSz, PktSz, Data),
D1),
- ssh_client_channel:cache_update(cache(D2),
+ ssh_client_channel:cache_update(cache(D2),
#channel{type = Type,
sys = "none",
user = ChannelPid,
local_id = ChannelId,
- recv_window_size = InitialWindowSize,
- recv_packet_size = MaxPacketSize,
+ recv_window_size = WinSz,
+ recv_packet_size = PktSz,
send_buf = queue:new()
}),
D = add_request(true, ChannelId, From, D2),
start_channel_request_timer(ChannelId, From, Timeout),
{keep_state, D, cond_set_idle_timer(D)};
-handle_event({call,From}, {send_window, ChannelId}, StateName, D)
+handle_event({call,From}, {send_window, ChannelId}, StateName, D)
when ?CONNECTED(StateName) ->
Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of
#channel{send_window_size = WinSize,
@@ -1454,7 +1077,7 @@ handle_event({call,From}, {send_window,
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {recv_window, ChannelId}, StateName, D)
+handle_event({call,From}, {recv_window, ChannelId}, StateName, D)
when ?CONNECTED(StateName) ->
Reply = case ssh_client_channel:cache_lookup(cache(D), ChannelId) of
#channel{recv_window_size = WinSize,
@@ -1465,14 +1088,24 @@ handle_event({call,From}, {recv_window,
end,
{keep_state_and_data, [{reply,From,Reply}]};
-handle_event({call,From}, {close, ChannelId}, StateName, D0)
+handle_event({call,From}, {close, ChannelId}, StateName, D0)
when ?CONNECTED(StateName) ->
+ %% Send 'channel-close' only if it has not been sent yet
+ %% e.g. when 'exit-signal' was received from the peer
+ %% and(!) we update the cache so that we remember what we've done
case ssh_client_channel:cache_lookup(cache(D0), ChannelId) of
- #channel{remote_id = Id} = Channel ->
+ #channel{remote_id = Id, sent_close = false} = Channel ->
D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
- ssh_client_channel:cache_update(cache(D1), Channel#channel{sent_close = true}),
- {keep_state, D1, [cond_set_idle_timer(D1), {reply,From,ok}]};
- undefined ->
+ ssh_client_channel:cache_update(cache(D1),
+ Channel#channel{sent_close = true}),
+ {keep_state, D1, [cond_set_idle_timer(D1),
+ channel_close_timer(D1, Id),
+ {reply,From,ok}]};
+ _ ->
+ %% Here we match a channel which has already sent 'channel-close'
+ %% AND possible cases of 'broken cache' i.e. when a channel
+ %% disappeared from the cache, but has not been properly shut down
+ %% The latter would be a bug, but hard to chase
{keep_state_and_data, [{reply,From,ok}]}
end;
@@ -1492,15 +1125,17 @@ handle_event({call,From}, {retrieve,Key}
handle_event(info, {Proto, Sock, Info}, {hello,_}, #data{socket = Sock,
transport_protocol = Proto}) ->
case Info of
- "SSH-" ++ _ ->
+ "SSH-" ++ _ ->
{keep_state_and_data, [{next_event, internal, {version_exchange,Info}}]};
_ ->
{keep_state_and_data, [{next_event, internal, {info_line,Info}}]}
end;
-handle_event(info, {Proto, Sock, NewData}, StateName, D0 = #data{socket = Sock,
- transport_protocol = Proto}) ->
+handle_event(info, {Proto, Sock, NewData}, StateName,
+ D0 = #data{socket = Sock,
+ transport_protocol = Proto,
+ ssh_params = SshParams}) ->
try ssh_transport:handle_packet_part(
D0#data.decrypted_data_buffer,
<<(D0#data.encrypted_data_buffer)/binary, NewData/binary>>,
@@ -1510,11 +1145,14 @@ handle_event(info, {Proto, Sock, NewData
of
{packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
D1 = D0#data{ssh_params =
- Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
- decrypted_data_buffer = <<>>,
- undecrypted_packet_length = undefined,
- aead_data = <<>>,
- encrypted_data_buffer = EncryptedDataRest},
+ Ssh1#ssh{recv_sequence =
+ ssh_transport:next_seqnum(StateName,
+ Ssh1#ssh.recv_sequence,
+ SshParams)},
+ decrypted_data_buffer = <<>>,
+ undecrypted_packet_length = undefined,
+ aead_data = <<>>,
+ encrypted_data_buffer = EncryptedDataRest},
try
ssh_message:decode(set_kex_overload_prefix(DecryptedBytes,D1))
of
@@ -1526,7 +1164,10 @@ handle_event(info, {Proto, Sock, NewData
#ssh_msg_global_request{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
#ssh_msg_request_success{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
#ssh_msg_request_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
- #ssh_msg_channel_open{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
+ #ssh_msg_channel_open{} = Msg -> {keep_state, D1,
+ [{{timeout, max_initial_idle_time}, cancel} |
+ ?CONNECTION_MSG(Msg)
+ ]};
#ssh_msg_channel_open_confirmation{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
#ssh_msg_channel_open_failure{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
#ssh_msg_channel_window_adjust{} = Msg -> {keep_state, D1, ?CONNECTION_MSG(Msg)};
@@ -1543,11 +1184,21 @@ handle_event(info, {Proto, Sock, NewData
{next_event, internal, Msg}
]}
catch
- C:E:ST ->
- {Shutdown, D} =
+ Class:Reason0:Stacktrace ->
+ Reason = ssh_lib:trim_reason(Reason0),
+ MsgFun =
+ fun(debug) ->
+ io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
+ [Class,Reason,Stacktrace],
+ [{chars_limit, ssh_lib:max_log_len(SshParams)}]);
+ (_) ->
+ io_lib:format("Bad packet: Decrypted, but can't decode ~p:~p",
+ [Class, Reason],
+ [{chars_limit, ssh_lib:max_log_len(SshParams)}])
+ end,
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
- io_lib:format("Bad packet: Decrypted, but can't decode~n~p:~p~n~p",
- [C,E,ST]),
+ ?SELECT_MSG(MsgFun),
StateName, D1),
{stop, Shutdown, D}
end;
@@ -1563,33 +1214,43 @@ handle_event(info, {Proto, Sock, NewData
ssh_params = Ssh1}};
{bad_mac, Ssh1} ->
- {Shutdown, D} =
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
"Bad packet: bad mac",
StateName, D0#data{ssh_params=Ssh1}),
{stop, Shutdown, D};
{error, {exceeds_max_size,PacketLen}} ->
- {Shutdown, D} =
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
io_lib:format("Bad packet: Size (~p bytes) exceeds max size",
[PacketLen]),
StateName, D0),
{stop, Shutdown, D}
catch
- C:E:ST ->
- {Shutdown, D} =
- ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR,
- io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",[C,E,ST]),
+ Class:Reason0:Stacktrace ->
+ MsgFun =
+ fun(debug) ->
+ io_lib:format("Bad packet: Couldn't decrypt~n~p:~p~n~p",
+ [Class,Reason0,Stacktrace],
+ [{chars_limit, ssh_lib:max_log_len(SshParams)}]);
+ (_) ->
+ Reason = ssh_lib:trim_reason(Reason0),
+ io_lib:format("Bad packet: Couldn't decrypt~n~p:~p",
+ [Class,Reason],
+ [{chars_limit, ssh_lib:max_log_len(SshParams)}])
+ end,
+ {Shutdown, D} =
+ ?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, ?SELECT_MSG(MsgFun),
StateName, D0),
{stop, Shutdown, D}
end;
-%%%====
-handle_event(internal, prepare_next_packet, _, D) ->
+%%%====
+handle_event(internal, prepare_next_packet, _StateName, D) ->
Enough = erlang:max(8, D#data.ssh_params#ssh.decrypt_block_size),
- case size(D#data.encrypted_data_buffer) of
+ case byte_size(D#data.encrypted_data_buffer) of
Sz when Sz >= Enough ->
self() ! {D#data.transport_protocol, D#data.socket, <<>>};
_ ->
@@ -1622,27 +1283,58 @@ handle_event(info, {timeout, {_, From} =
%%% Handle that ssh channels user process goes down
handle_event(info, {'DOWN', _Ref, process, ChannelPid, _Reason}, _, D) ->
Cache = cache(D),
- ssh_client_channel:cache_foldl(
- fun(#channel{user=U,
- local_id=Id}, Acc) when U == ChannelPid ->
- ssh_client_channel:cache_delete(Cache, Id),
- Acc;
- (_,Acc) ->
- Acc
- end, [], Cache),
- {keep_state, D, cond_set_idle_timer(D)};
+ %% Here we first collect the list of channel id's handled by the process
+ %% Do NOT remove them from the cache - they are not closed yet!
+ Channels = ssh_client_channel:cache_foldl(
+ fun(#channel{user=U} = Channel, Acc) when U == ChannelPid ->
+ [Channel | Acc];
+ (_,Acc) ->
+ Acc
+ end, [], Cache),
+ %% Then for each channel where 'channel-close' has not been sent yet
+ %% we send 'channel-close' and(!) update the cache so that we remember
+ %% what we've done.
+ %% Also set user as 'undefined' as there is no such process anyway
+ {D2, NewTimers} = lists:foldl(
+ fun(#channel{remote_id = Id, sent_close = false} = Channel,
+ {D0, Timers}) when Id /= undefined ->
+ D1 = send_msg(ssh_connection:channel_close_msg(Id), D0),
+ ssh_client_channel:cache_update(cache(D1),
+ Channel#channel{sent_close = true,
+ user = undefined}),
+ ChannelTimer = channel_close_timer(D1, Id),
+ {D1, [ChannelTimer | Timers]};
+ (Channel, {D0, _} = Acc) ->
+ ssh_client_channel:cache_update(cache(D0),
+ Channel#channel{user = undefined}),
+ Acc
+ end, {D, []}, Channels),
+ {keep_state, D2, [cond_set_idle_timer(D2) | NewTimers]};
handle_event({timeout,idle_time}, _Data, _StateName, D) ->
case ssh_client_channel:cache_info(num_entries, cache(D)) of
- 0 ->
+ 0 ->
{stop, {shutdown, "Timeout"}};
_ ->
keep_state_and_data
end;
+handle_event({timeout,max_initial_idle_time}, _Data, _StateName, _D) ->
+ {stop, {shutdown, "Timeout"}};
+
+handle_event({timeout, {channel_close, ChannelId}}, _Data, _StateName, D) ->
+ Cache = cache(D),
+ case ssh_client_channel:cache_lookup(Cache, ChannelId) of
+ #channel{sent_close = true} ->
+ ssh_client_channel:cache_delete(Cache, ChannelId),
+ {keep_state, D, cond_set_idle_timer(D)};
+ _ ->
+ keep_state_and_data
+ end;
+
%%% So that terminate will be run when supervisor is shutdown
-handle_event(info, {'EXIT', _Sup, Reason}, StateName, _) ->
- Role = role(StateName),
+handle_event(info, {'EXIT', _Sup, Reason}, StateName, _D) ->
+ Role = ?role(StateName),
if
Role == client ->
%% OTP-8111 tells this function clause fixes a problem in
@@ -1650,7 +1342,7 @@ handle_event(info, {'EXIT', _Sup, Reason
{stop, {shutdown, Reason}};
Reason == normal ->
- %% An exit normal should not cause a server to crash. This has happend...
+ %% An exit normal should not cause a server to crash. This has happened...
keep_state_and_data;
true ->
@@ -1663,9 +1355,9 @@ handle_event(info, check_cache, _, D) ->
handle_event(info, {fwd_connect_received, Sock, ChId, ChanCB}, StateName, #data{connection_state = Connection}) ->
#connection{options = Options,
channel_cache = Cache,
- sub_system_supervisor = SubSysSup} = Connection,
+ connection_supervisor = ConnectionSup} = Connection,
Channel = ssh_client_channel:cache_lookup(Cache, ChId),
- {ok,Pid} = ssh_subsystem_sup:start_channel(role(StateName), SubSysSup, self(), ChanCB, ChId, [Sock], undefined, Options),
+ {ok,Pid} = ssh_connection_sup:start_channel(?role(StateName), ConnectionSup, self(), ChanCB, ChId, [Sock], undefined, Options),
ssh_client_channel:cache_update(Cache, Channel#channel{user=Pid}),
gen_tcp:controlling_process(Sock, Pid),
inet:setopts(Sock, [{active,once}]),
@@ -1674,8 +1366,8 @@ handle_event(info, {fwd_connect_received
handle_event({call,From},
{handle_direct_tcpip, ListenHost, ListenPort, ConnectToHost, ConnectToPort, _Timeout},
_StateName,
- #data{connection_state = #connection{sub_system_supervisor=SubSysSup}}) ->
- case ssh_tcpip_forward_acceptor:supervised_start(ssh_subsystem_sup:tcpip_fwd_supervisor(SubSysSup),
+ #data{connection_state = #connection{connection_supervisor=ConnectionSup}}) ->
+ case ssh_tcpip_forward_acceptor:supervised_start(ssh_connection_sup:tcpip_fwd_supervisor(ConnectionSup),
{ListenHost, ListenPort},
{ConnectToHost, ConnectToPort},
"direct-tcpip", ssh_tcpip_forward_client,
@@ -1698,7 +1390,7 @@ handle_event(info, UnexpectedMessage, St
"Local Address: ~p\n",
[UnexpectedMessage,
StateName,
- Ssh#ssh.role,
+ Ssh#ssh.role,
Ssh#ssh.peer,
?GET_INTERNAL_OPT(address, Ssh#ssh.opts, undefined)])),
error_logger:info_report(Msg),
@@ -1727,7 +1419,7 @@ handle_event(info, UnexpectedMessage, St
end;
handle_event(internal, {send_disconnect,Code,DetailedText,Module,Line}, StateName, D0) ->
- {Shutdown, D} =
+ {Shutdown, D} =
send_disconnect(Code, DetailedText, Module, Line, StateName, D0),
{stop, Shutdown, D};
@@ -1746,9 +1438,9 @@ handle_event(Type, Ev, StateName, D0) ->
"ssh_msg_" ++_ when Type==internal ->
lists:flatten(io_lib:format("Message ~p in wrong state (~p)", [element(1,Ev), StateName]));
_ ->
- io_lib:format("Unhandled event in state ~p:~n~p", [StateName,Ev])
+ io_lib:format("Unhandled event in state ~p and type ~p:~n~p", [StateName,Type,Ev])
end,
- {Shutdown, D} =
+ {Shutdown, D} =
?send_disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR, Details, StateName, D0),
{stop, Shutdown, D}.
@@ -1758,27 +1450,18 @@ handle_event(Type, Ev, StateName, D0) ->
state_name(),
#data{}
) -> term().
-
+
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+terminate(_, {wait_for_socket, _}, _) ->
+ %% No need to to anything - maybe we have not yet gotten
+ %% control over the socket
+ ok;
terminate(normal, _StateName, D) ->
- stop_subsystem(D),
- close_transport(D);
-
-terminate({shutdown,"Connection closed"}, _StateName, D) ->
- %% Normal: terminated by a sent by peer
- stop_subsystem(D),
- close_transport(D);
-
-terminate({shutdown,{init,Reason}}, StateName, D) ->
- %% Error in initiation. "This error should not occur".
- log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]),
- stop_subsystem(D),
close_transport(D);
terminate({shutdown,_R}, _StateName, D) ->
%% Internal termination, usually already reported via ?send_disconnect resulting in a log entry
- stop_subsystem(D),
close_transport(D);
terminate(shutdown, _StateName, D0) ->
@@ -1789,11 +1472,6 @@ terminate(shutdown, _StateName, D0) ->
D0),
close_transport(D);
-terminate(killed, _StateName, D) ->
- %% Got a killed signal
- stop_subsystem(D),
- close_transport(D);
-
terminate(Reason, StateName, D0) ->
%% Others, e.g undef, {badmatch,_}, ...
log(error, D0, Reason),
@@ -1801,16 +1479,21 @@ terminate(Reason, StateName, D0) ->
"Internal error",
io_lib:format("Reason: ~p",[Reason]),
StateName, D0),
- stop_subsystem(D),
close_transport(D).
%%--------------------------------------------------------------------
%% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-format_status(normal, [_, _StateName, D]) ->
+format_status(A, B) ->
+ try format_status0(A, B)
+ catch
+ _:_ -> "????"
+ end.
+
+format_status0(normal, [_PDict, _StateName, D]) ->
[{data, [{"State", D}]}];
-format_status(terminate, [_, _StateName, D]) ->
+format_status0(terminate, [_, _StateName, D]) ->
[{data, [{"State", clean(D)}]}].
@@ -1852,7 +1535,6 @@ clean(T) when is_tuple(T) ->
clean(X) ->
ssh_options:no_sensitive(filter, X).
-
fmt_stat_rec(FieldNames, Rec, Exclude) ->
Values = tl(tuple_to_list(Rec)),
list_to_tuple(
@@ -1883,70 +1565,10 @@ code_change(_OldVsn, StateName, State, _
%%====================================================================
%%--------------------------------------------------------------------
-%% Starting
-
-start_the_connection_child(UserPid, Role, Socket, Options0) ->
- Sups = ?GET_INTERNAL_OPT(supervisors, Options0),
- ConnectionSup = proplists:get_value(connection_sup, Sups),
- Options = ?PUT_INTERNAL_OPT({user_pid,UserPid}, Options0),
- InitArgs = [Role, Socket, Options],
- {ok, Pid} = ssh_connection_sup:start_child(ConnectionSup, InitArgs),
- ok = socket_control(Socket, Pid, Options), % transfer the Socket ownership in a controlled way.
- Pid.
-
-%%--------------------------------------------------------------------
-%% Stopping
-
-stop_subsystem(#data{ssh_params =
- #ssh{role = Role},
- connection_state =
- #connection{system_supervisor = SysSup,
- sub_system_supervisor = SubSysSup,
- options = Opts}
- }) when is_pid(SysSup) andalso is_pid(SubSysSup) ->
- C = self(),
- spawn(fun() ->
- wait_until_dead(C, 10000),
- case {Role, ?GET_INTERNAL_OPT(connected_socket,Opts,non_socket_started)} of
- {server, non_socket_started} ->
- ssh_system_sup:stop_subsystem(SysSup, SubSysSup);
- {client, non_socket_started} ->
- ssh_system_sup:stop_system(Role, SysSup);
- {server, _Socket} ->
- ssh_system_sup:stop_system(Role, SysSup);
- {client, _Socket} ->
- ssh_system_sup:stop_subsystem(SysSup, SubSysSup),
- wait_until_dead(SubSysSup, 1000),
- sshc_sup:stop_system(SysSup)
- end
- end);
-stop_subsystem(_) ->
- ok.
-
-
-wait_until_dead(Pid, Timeout) ->
- Mref = erlang:monitor(process, Pid),
- receive
- {'DOWN', Mref, process, Pid, _Info} -> ok
- after
- Timeout -> ok
- end.
-
-
close_transport(#data{transport_cb = Transport,
socket = Socket}) ->
- try
- Transport:close(Socket)
- of
- _ -> ok
- catch
- _:_ -> ok
- end.
-
-%%--------------------------------------------------------------------
-%% "Invert" the Role
-peer_role(client) -> server;
-peer_role(server) -> client.
+ catch Transport:close(Socket),
+ ok.
%%--------------------------------------------------------------------
available_hkey_algorithms(client, Options) ->
@@ -2051,7 +1673,7 @@ kex(#ssh{algorithms=#alg{kex=Kex}}) -> K
kex(_) -> undefined.
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}} ) ->
@@ -2065,7 +1687,7 @@ handle_ssh_msg_ext_info(#ssh_msg_ext_inf
ext_info({"server-sig-algs",SigAlgsStr},
D0 = #data{ssh_params=#ssh{role=client,
userauth_pubkeys=ClientSigAlgs}=Ssh0}) ->
- %% ClientSigAlgs are the pub_key algortithms that:
+ %% ClientSigAlgs are the pub_key algorithms 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'
@@ -2140,7 +1762,7 @@ handle_request(ChannelId, Type, Data, Wa
_ when WantReply==true ->
{error,closed};
-
+
_ ->
D
end.
@@ -2176,7 +1798,7 @@ start_rekeying(Role, D0) ->
send_bytes(SshPacket, D0),
D = D0#data{ssh_params = Ssh,
key_exchange_init_msg = KeyInitMsg},
- {next_state, {kexinit,Role,renegotiate}, D}.
+ {next_state, {kexinit,Role,renegotiate}, D, {change_callback_module,ssh_fsm_kexinit}}.
init_renegotiate_timers(_OldState, NewState, D) ->
@@ -2291,7 +1913,7 @@ conn_info(channels, D) -> try conn_info_
end;
%% dbg options ( = not documented):
conn_info(socket, D) -> D#data.socket;
-conn_info(chan_ids, D) ->
+conn_info(chan_ids, D) ->
ssh_client_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) ->
[Id | Acc]
end, [], cache(D)).
@@ -2357,15 +1979,18 @@ do_log(F, Reason0, #data{ssh_params=S})
Role==client ->
{PeerRole,PeerVersion} =
case Role of
- server -> {"Client", S#ssh.c_version};
- client -> {"Server", S#ssh.s_version}
+ server -> {"Peer client", S#ssh.c_version};
+ client -> {"Peer server", S#ssh.s_version}
end,
- error_logger:F("Erlang SSH ~p ~s ~s.~n"
- "~s: ~p~n"
+ error_logger:F("Erlang SSH ~p version: ~s ~s.~n"
+ "Address: ~s~n"
+ "~s version: ~p~n"
+ "Peer address: ~s~n"
"~s~n",
- [Role,
- ssh_log_version(), crypto_log_info(),
+ [Role, ssh_log_version(), crypto_log_info(),
+ ssh_lib:format_address_port(S#ssh.local),
PeerRole, PeerVersion,
+ ssh_lib:format_address_port(element(2,S#ssh.peer)),
Reason]);
_ ->
error_logger:F("Erlang SSH ~s ~s.~n"
@@ -2376,7 +2001,7 @@ do_log(F, Reason0, #data{ssh_params=S})
assure_string(S) ->
try io_lib:format("~s",[S])
- of _ -> S
+ of Formatted -> Formatted
catch
_:_ -> io_lib:format("~p",[S])
end.
@@ -2393,12 +2018,12 @@ limit_size(S, Len, MaxLen) when Len =< (
S;
limit_size(S, Len, MaxLen) when Len > MaxLen ->
%% Cut
- io_lib:format("~s ... (~w bytes skipped)",
+ io_lib:format("~s ... (~w bytes skipped)",
[string:substr(lists:flatten(S), 1, MaxLen),
Len-MaxLen]).
crypto_log_info() ->
- try
+ try
[{_,_,CI}] = crypto:info_lib(),
case crypto:info_fips() of
enabled ->
@@ -2456,8 +2081,6 @@ get_repl(X, Acc) ->
exit({get_repl,X,Acc}).
%%%----------------------------------------------------------------
--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).
@@ -2471,36 +2094,6 @@ debug_fun(#ssh_msg_debug{always_display
?CALL_FUN(ssh_msg_debug_fun,D)(self(), Display, DbgMsg, Lang).
-connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) ->
- ?CALL_FUN(connectfun,D)(User, Peer, Method).
-
-
-retry_fun(_, undefined, _) ->
- ok;
-retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
- peer = {_,Peer}
- }}) ->
- {Tag,Info} =
- case Reason of
- {error, Error} ->
- {failfun, Error};
- _ ->
- {infofun, Reason}
- end,
- Fun = ?GET_OPT(Tag, Opts),
- try erlang:fun_info(Fun, arity)
- of
- {arity, 2} -> %% Backwards compatible
- catch Fun(User, Info);
- {arity, 3} ->
- catch Fun(User, Peer, Info);
- _ ->
- ok
- catch
- _:_ ->
- ok
- end.
-
%%%----------------------------------------------------------------
%%% Cache idle timer that closes the connection if there are no
%%% channels open for a while.
@@ -2511,6 +2104,10 @@ cond_set_idle_timer(D) ->
_ -> {{timeout,idle_time}, infinity, none}
end.
+channel_close_timer(D, ChannelId) ->
+ {{timeout, {channel_close, ChannelId}},
+ ?GET_OPT(channel_close_timeout, (D#data.ssh_params)#ssh.opts), none}.
+
%%%----------------------------------------------------------------
start_channel_request_timer(_,_, infinity) ->
ok;
@@ -2518,43 +2115,17 @@ start_channel_request_timer(Channel, Fro
erlang:send_after(Time, self(), {timeout, {Channel, From}}).
%%%----------------------------------------------------------------
-%%% Connection start and initalization helpers
-socket_control(Socket, Pid, Options) ->
- {_, Callback, _} = ?GET_OPT(transport, Options),
- case Callback:controlling_process(Socket, Pid) of
- ok ->
- gen_statem:cast(Pid, socket_control);
- {error, Reason} ->
- {error, Reason}
- end.
-
-handshake(Pid, Ref, Timeout) ->
- receive
- ssh_connected ->
- erlang:demonitor(Ref),
- {ok, Pid};
- {Pid, not_connected, Reason} ->
- {error, Reason};
- {Pid, user_password} ->
- Pass = io:get_password(),
- Pid ! Pass,
- handshake(Pid, Ref, Timeout);
- {Pid, question} ->
- Answer = io:get_line(""),
- Pid ! Answer,
- handshake(Pid, Ref, Timeout);
- {'DOWN', _, process, Pid, {shutdown, Reason}} ->
- {error, Reason};
- {'DOWN', _, process, Pid, Reason} ->
- {error, Reason};
- {'EXIT',_,Reason} ->
- stop(Pid),
- {error, {exit,Reason}}
- after Timeout ->
- stop(Pid),
- {error, timeout}
- end.
+init_inet_buffers_window(Socket) ->
+ %% Initialize the inet buffer handling. First try to increase the buffers:
+ update_inet_buffers(Socket),
+ %% then get good start values for the window handling:
+ {ok,SockOpts} = inet:getopts(Socket, [buffer,recbuf]),
+ WinSz = proplists:get_value(recbuf, SockOpts, ?DEFAULT_WINDOW_SIZE),
+ PktSz = min(proplists:get_value(buffer, SockOpts, ?DEFAULT_PACKET_SIZE),
+ ?DEFAULT_PACKET_SIZE), % Too large packet size might cause deadlock
+ % between sending and receiving
+ {WinSz, PktSz}.
update_inet_buffers(Socket) ->
try
@@ -2564,7 +2135,11 @@ update_inet_buffers(Socket) ->
Val < MinVal]
of
[] -> ok;
- NewOpts -> inet:setopts(Socket, NewOpts)
+ NewOpts ->
+ inet:setopts(Socket, NewOpts),
+ %% Note that buffers might be of different size than we just requested,
+ %% the OS has the last word.
+ ok
catch
_:_ -> ok
end.
@@ -2574,17 +2149,18 @@ update_inet_buffers(Socket) ->
%%%# Tracing
%%%#
-ssh_dbg_trace_points() -> [terminate, disconnect, connections, connection_events,
- connection_handshake, renegotiation].
+ssh_dbg_trace_points() -> [terminate, disconnect, connections, connection_events, renegotiation,
+ tcp, connection_handshake].
ssh_dbg_flags(connections) -> [c | ssh_dbg_flags(terminate)];
ssh_dbg_flags(renegotiation) -> [c];
ssh_dbg_flags(connection_events) -> [c];
ssh_dbg_flags(connection_handshake) -> [c];
ssh_dbg_flags(terminate) -> [c];
+ssh_dbg_flags(tcp) -> [c];
ssh_dbg_flags(disconnect) -> [c].
-ssh_dbg_on(connections) -> dbg:tp(?MODULE, init_connection_handler, 3, x),
+ssh_dbg_on(connections) -> dbg:tp(?MODULE, init, 1, x),
ssh_dbg_on(terminate);
ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x);
ssh_dbg_on(connection_handshake) -> dbg:tpl(?MODULE, handshake, 3, x);
@@ -2594,11 +2170,22 @@ ssh_dbg_on(renegotiation) -> dbg:tpl(?MO
dbg:tpl(?MODULE, start_rekeying, 2, x),
dbg:tp(?MODULE, renegotiate, 1, x);
ssh_dbg_on(terminate) -> dbg:tp(?MODULE, terminate, 3, x);
+ssh_dbg_on(tcp) -> dbg:tp(?MODULE, handle_event, 4,
+ [{[info, {tcp,'_','_'}, '_', '_'], [], []},
+ {[info, {tcp_error,'_','_'}, '_', '_'], [], []},
+ {[info, {tcp_closed,'_'}, '_', '_'], [], []}
+ ]),
+ dbg:tp(?MODULE, send_bytes, 2, x),
+ dbg:tpl(?MODULE, close_transport, 1, x);
+
ssh_dbg_on(disconnect) -> dbg:tpl(?MODULE, send_disconnect, 7, x).
ssh_dbg_off(disconnect) -> dbg:ctpl(?MODULE, send_disconnect, 7);
ssh_dbg_off(terminate) -> dbg:ctpg(?MODULE, terminate, 3);
+ssh_dbg_off(tcp) -> dbg:ctpg(?MODULE, handle_event, 4), % How to avoid cancelling 'connection_events' ?
+ dbg:ctpl(?MODULE, send_bytes, 2),
+ dbg:ctpl(?MODULE, close_transport, 1);
ssh_dbg_off(renegotiation) -> dbg:ctpl(?MODULE, init_renegotiate_timers, 3),
dbg:ctpl(?MODULE, pause_renegotiate_timers, 3),
dbg:ctpl(?MODULE, check_data_rekeying_dbg, 2),
@@ -2606,11 +2193,11 @@ ssh_dbg_off(renegotiation) -> dbg:ctpl(?
dbg:ctpg(?MODULE, renegotiate, 1);
ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4);
ssh_dbg_off(connection_handshake) -> dbg:ctpl(?MODULE, handshake, 3);
-ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init_connection_handler, 3),
+ssh_dbg_off(connections) -> dbg:ctpg(?MODULE, init, 1),
ssh_dbg_off(terminate).
-ssh_dbg_format(connections, {call, {?MODULE,init_connection_handler, [Role, Sock, Opts]}}) ->
+ssh_dbg_format(connections, {call, {?MODULE,init, [[Role, Sock, Opts]]}}) ->
DefaultOpts = ssh_options:handle_options(Role,[]),
ExcludedKeys = [internal_options, user_options],
NonDefaultOpts =
@@ -2626,9 +2213,11 @@ ssh_dbg_format(connections, {call, {?MOD
{ok, {IPp,Portp}} = inet:peername(Sock),
{ok, {IPs,Ports}} = inet:sockname(Sock),
[io_lib:format("Starting ~p connection:\n",[Role]),
- io_lib:format("Socket = ~p, Peer = ~s:~p, Local = ~s:~p,~n"
+ io_lib:format("Socket = ~p, Peer = ~s, Local = ~s,~n"
"Non-default options:~n~p",
- [Sock,inet:ntoa(IPp),Portp,inet:ntoa(IPs),Ports,
+ [Sock,
+ ssh_lib:format_address_port(IPp,Portp),
+ ssh_lib:format_address_port(IPs,Ports),
NonDefaultOpts])
];
ssh_dbg_format(connections, F) ->
@@ -2643,11 +2232,48 @@ ssh_dbg_format(connection_events, {retur
io_lib:format("~p~n", [ssh_dbg:reduce_state(Ret, #data{})])
];
+ssh_dbg_format(tcp, {call, {?MODULE,handle_event, [info, {tcp,Sock,TcpData}, State, _Data]}}) ->
+ ["TCP stream data arrived\n",
+ io_lib:format("State: ~p~n"
+ "Socket: ~p~n"
+ "TcpData:~n~s", [State, Sock, ssh_dbg:hex_dump(TcpData, [{max_bytes,48}])])
+ ];
+ssh_dbg_format(tcp, {call, {?MODULE,handle_event, [info, {tcp_error,Sock,Msg}, State, _Data]}}) ->
+ ["TCP stream data ERROR arrived\n",
+ io_lib:format("State: ~p~n"
+ "Socket: ~p~n"
+ "ErrorMsg:~p~n", [State, Sock, Msg])
+ ];
+ssh_dbg_format(tcp, {call, {?MODULE,handle_event, [info, {tcp_closed,Sock}, State, _Data]}}) ->
+ ["TCP stream closed\n",
+ io_lib:format("State: ~p~n"
+ "Socket: ~p~n", [State, Sock])
+ ];
+ssh_dbg_format(tcp, {return_from, {?MODULE,handle_event,4}, _Ret}) ->
+ skip;
+
+ssh_dbg_format(tcp, {call, {?MODULE, send_bytes, ["",_D]}}) ->
+ skip;
+ssh_dbg_format(tcp, {call, {?MODULE, send_bytes, [TcpData, #data{socket=Sock}]}}) ->
+ ["TCP send stream data\n",
+ io_lib:format("Socket: ~p~n"
+ "TcpData:~n~s", [Sock, ssh_dbg:hex_dump(TcpData, [{max_bytes,48}])])
+ ];
+ssh_dbg_format(tcp, {return_from, {?MODULE,send_bytes,2}, _R}) ->
+ skip;
+
+ssh_dbg_format(tcp, {call, {?MODULE, close_transport, [#data{socket=Sock}]}}) ->
+ ["TCP close stream\n",
+ io_lib:format("Socket: ~p~n", [Sock])
+ ];
+ssh_dbg_format(tcp, {return_from, {?MODULE,close_transport,1}, _R}) ->
+ skip;
+
ssh_dbg_format(renegotiation, {call, {?MODULE,init_renegotiate_timers,[OldState,NewState,D]}}) ->
["Renegotiation: start timer (init_renegotiate_timers)\n",
io_lib:format("State: ~p --> ~p~n"
"rekey_limit: ~p ({ms,bytes})~n"
- "check_data_size: ~p (ms)~n",
+ "check_data_size: ~p (ms)~n",
[OldState, NewState,
?GET_OPT(rekey_limit, (D#data.ssh_params)#ssh.opts),
?REKEY_DATA_TIMOUT])
@@ -2657,7 +2283,7 @@ ssh_dbg_format(renegotiation, {return_fr
ssh_dbg_format(renegotiation, {call, {?MODULE,renegotiate,[ConnectionHandler]}}) ->
["Renegotiation: renegotiation forced\n",
- io_lib:format("~p:renegotiate(~p) called~n",
+ io_lib:format("~p:renegotiate(~p) called~n",
[?MODULE,ConnectionHandler])
];
ssh_dbg_format(renegotiation, {return_from, {?MODULE,renegotiate,1}, _Ret}) ->
@@ -2733,7 +2359,6 @@ ssh_dbg_format(renegotiation, {return_fr
skip.
-
ssh_dbg_format(connection_handshake, {call, {?MODULE,handshake,[Pid, Ref, Timeout]}}, Stack) ->
{["Connection handshake\n",
io_lib:format("Connection Child: ~p~nReg: ~p~nTimeout: ~p~n",
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_connection_sup.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_connection_sup.erl
@@ -1,8 +1,8 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-2024. 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
@@ -14,21 +14,24 @@
%% 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%
%%
%%
%%----------------------------------------------------------------------
-%% Purpose: Ssh connection supervisor.
+%% Purpose: The ssh connection supervisor
%%----------------------------------------------------------------------
-module(ssh_connection_sup).
-behaviour(supervisor).
-%% API
--export([start_link/1]).
--export([start_child/2]).
+-include("ssh.hrl").
+
+-export([start_link/4,
+ start_channel/8,
+ tcpip_fwd_supervisor/1
+ ]).
%% Supervisor callback
-export([init/1]).
@@ -36,23 +39,67 @@
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Args) ->
- supervisor:start_link(?MODULE, [Args]).
+start_link(Role, Id, Socket, Options) ->
+ case supervisor:start_link(?MODULE, [Role, Id, Socket, Options]) of
+ {error, {shutdown, {failed_to_start_child, _, Error}}} ->
+ {error,Error};
+ Other ->
+ Other
+ end.
+
+start_channel(Role, SupPid, ConnRef, Callback, Id, Args, Exec, Opts) ->
+ ChannelSup = channel_supervisor(SupPid),
+ ssh_channel_sup:start_child(Role, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts).
+
+tcpip_fwd_supervisor(ConnectionSup) ->
+ find_child(tcpip_forward_acceptor_sup, ConnectionSup).
-start_child(Sup, Args) ->
- supervisor:start_child(Sup, Args).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-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 % because there is no way to restart a crashed connection
- }
- ],
+init([Role, Id, Socket, Options]) ->
+ ConnectionSup = self(),
+ SupFlags = #{strategy => one_for_all,
+ auto_shutdown => any_significant,
+ intensity => 0,
+ period => 3600},
+ ChildSpecs =
+ [#{id => connection,
+ restart => temporary,
+ type => worker,
+ significant => true,
+ start => {ssh_connection_handler,
+ start_link,
+ [Role, Id, Socket,
+ ?PUT_INTERNAL_OPT([{connection_sup, ConnectionSup}], Options)]}
+ },
+ #{id => channel_sup,
+ restart => temporary,
+ type => supervisor,
+ start => {ssh_channel_sup, start_link, [Options]}
+ },
+
+ #{id => tcpip_forward_acceptor_sup,
+ restart => temporary,
+ type => supervisor,
+ start => {ssh_tcpip_forward_acceptor_sup, start_link, []}
+ }],
{ok, {SupFlags,ChildSpecs}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+channel_supervisor(ConnectionSup) -> find_child(channel_sup, ConnectionSup).
+
+find_child(Id, Sup) when is_pid(Sup) ->
+ try
+ {Id, Pid, _, _} = lists:keyfind(Id, 1, supervisor:which_children(Sup)),
+ Pid
+ catch
+ exit:{no_proc,_} ->
+ {error, no_proc};
+ _:_ ->
+ {error, {id_not_found,?MODULE,Id}}
+ end.
+
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2025. 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.
@@ -35,6 +35,7 @@
channel_info/3,
daemon/1, daemon/2, daemon/3,
daemon_info/1, daemon_info/2,
+ daemon_replace_options/2,
set_sock_opts/2, get_sock_opts/2,
default_algorithms/0,
chk_algos_opts/1,
@@ -45,6 +46,17 @@
tcpip_tunnel_to_server/5, tcpip_tunnel_to_server/6
]).
+%% In move from public_key
+-export([hostkey_fingerprint/1, hostkey_fingerprint/2
+ ]).
+
+
+%%% Internal export
+-export([is_host/2]).
+
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2, ssh_dbg_format/3]).
+
%%% "Deprecated" types export:
-export_type([ssh_daemon_ref/0, ssh_connection_ref/0, ssh_channel_id/0]).
-opaque ssh_daemon_ref() :: daemon_ref().
@@ -97,7 +109,7 @@ start(Type) ->
{ok, _} ->
%% Clear cached default_algorithms (if exists) ...
ssh_transport:clear_default_algorithms_env(),
- %% ... and rebuld them taking configure options in account
+ %% ... and rebuild them taking configure options in account
ssh_transport:default_algorithms(),
ok;
Other ->
@@ -115,90 +127,128 @@ stop() ->
%%--------------------------------------------------------------------
%% Description: Starts an ssh connection.
%%--------------------------------------------------------------------
--spec connect(OpenTcpSocket, Options) -> {ok,connection_ref()} | {error,term()} when
+-define(IS_VALID_OPTIONS(Options), is_list(Options)).
+-define(IS_VALID_PORT(Port), (is_integer(Port) andalso Port > 0)).
+-define(IS_VALID_TIMEOUT(Timeout),
+ (Timeout == infinity
+ orelse (is_integer(Timeout)
+ andalso Timeout >= 0))).
+
+-spec connect(OpenTcpSocket, Options)
+ -> {ok, connection_ref()}
+ | {error, term()} when
OpenTcpSocket :: open_socket(),
Options :: client_options().
-connect(OpenTcpSocket, Options) when is_port(OpenTcpSocket),
- is_list(Options) ->
- connect(OpenTcpSocket, Options, infinity).
-
+connect(OpenTcpSocket, Options) when ?IS_VALID_OPTIONS(Options) ->
+ connect(OpenTcpSocket, Options, infinity);
+connect(_OpenTcpSocket, Options) ->
+ bad_arg([{options, Options}]).
-spec connect(open_socket(), client_options(), timeout()) ->
- {ok,connection_ref()} | {error,term()}
+ {ok, connection_ref()} | {error, term()}
; (host(), inet:port_number(), client_options()) ->
- {ok,connection_ref()} | {error,term()}.
+ {ok, connection_ref()} | {error, term()}.
-connect(Socket, UserOptions, NegotiationTimeout) when is_port(Socket),
- is_list(UserOptions) ->
+connect(Host, Port, Options) when ?IS_VALID_PORT(Port),
+ ?IS_VALID_OPTIONS(Options) ->
+ Timeout = proplists:get_value(connect_timeout, Options, infinity),
+ connect(Host, Port, Options, Timeout);
+connect(Socket, UserOptions, NegotiationTimeout)
+ when ?IS_VALID_OPTIONS(UserOptions),
+ ?IS_VALID_TIMEOUT(NegotiationTimeout) ->
case ssh_options:handle_options(client, UserOptions) of
{error, Error} ->
{error, Error};
- Options ->
- case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
- ok ->
- connect_socket(Socket,
- ?PUT_INTERNAL_OPT({connected_socket,Socket}, Options),
- NegotiationTimeout);
- {error,SockError} ->
- {error,SockError}
- end
- end;
-
-connect(Host, Port, Options) when is_integer(Port),
- Port>0,
- is_list(Options) ->
- Timeout = proplists:get_value(connect_timeout, Options, infinity),
- connect(Host, Port, Options, Timeout).
+ Options = #{} ->
+ case valid_socket_to_use(Socket, ?GET_OPT(transport,Options)) of
+ ok ->
+ continue_connect(Socket, Options, NegotiationTimeout);
+ {error,SockError} ->
+ {error,SockError}
+ end
+ end;
+connect(_HostOrSocket, PortOrOptions, OptionsOrTimeout) ->
+ bad_arg(PortOrOptions, OptionsOrTimeout).
--spec connect(Host, Port, Options, NegotiationTimeout) -> {ok,connection_ref()} | {error,term()} when
+-spec connect(Host, Port, Options, NegotiationTimeout)
+ -> {ok, connection_ref()}
+ | {error, term()} when
Host :: host(),
Port :: inet:port_number(),
Options :: client_options(),
NegotiationTimeout :: timeout().
-connect(Host0, Port, UserOptions, NegotiationTimeout) when is_integer(Port),
- Port>0,
- is_list(UserOptions) ->
+connect(Host0, Port, UserOptions, NegotiationTimeout)
+ when ?IS_VALID_PORT(Port),
+ ?IS_VALID_OPTIONS(UserOptions),
+ ?IS_VALID_TIMEOUT(NegotiationTimeout) ->
case ssh_options:handle_options(client, UserOptions) of
- {error, _Reason} = Error ->
- Error;
+ {error, Reason} ->
+ {error, Reason};
+
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} ->
- connect_socket(Socket,
- ?PUT_INTERNAL_OPT({host,Host}, Options),
- NegotiationTimeout);
- {error, Reason} ->
- {error, Reason}
- catch
- exit:{function_clause, _F} ->
- {error, {options, {transport, TransportOpts}}};
- exit:badarg ->
- {error, {options, {socket_options, SocketOpts}}}
- end
+ SocketOpts = ?GET_OPT(socket_options,Options) ++ [{active,false}],
+ Host = mangle_connect_address(Host0, Options),
+ try
+ transport_connect(Host, Port, SocketOpts, Options)
+ of
+ {ok, Socket} ->
+ continue_connect(Socket, Options, NegotiationTimeout);
+ {error, Reason} ->
+ {error, Reason}
+ catch
+ _:badarg -> {error, {options,?GET_OPT(socket_options,Options)}};
+ _:{error,Reason} -> {error,Reason};
+ error:Error -> {error,Error};
+ Class:Error -> {error, {Class,Error}}
+ end
+ end;
+connect(_Host, Port, UserOptions, NegotiationTimeout) ->
+ bad_arg([{port, Port},
+ {options, UserOptions},
+ {timeout, NegotiationTimeout}]).
+
+bad_arg(Args) ->
+ hd(bad_args(Args)).
+
+%% Special handling for finding the incorrect args for connect/3,
+%% which has two distinctly different signatures.
+bad_arg(Arg2, Arg3) ->
+ E0 = bad_args([{port, Arg2}, {options, Arg3}]),
+ E1 = bad_args([{options, Arg2}, {timeout, Arg3}]),
+ %% Select the case with only one error
+ case {E0, E1} of
+ {[Error], _} -> Error;
+ {_, [Error]} -> Error;
+ {[Error, _], _} -> Error
end.
+%% Return list of errors
+-spec bad_args([{'options' | 'port' | 'timeout', any()}]) ->
+ [{'error', term()}].
+bad_args(Args) ->
+ IsErr = fun(true, _) -> false;
+ (false, Error) -> {true, {error, Error}}
+ end,
+ Check =
+ fun({options, Arg}) -> IsErr(?IS_VALID_OPTIONS(Arg), invalid_options);
+ ({timeout, Arg}) -> IsErr(?IS_VALID_TIMEOUT(Arg), invalid_timeout);
+ ({port, Arg}) -> IsErr(?IS_VALID_PORT(Arg), invalid_port)
+ end,
-connect_socket(Socket, Options0, NegotiationTimeout) ->
- {ok, {Host,Port}} = inet:sockname(Socket),
- Profile = ?GET_OPT(profile, Options0),
-
- {ok, {SystemSup, SubSysSup}} = sshc_sup:start_system_subsystem(Host, Port, Profile, Options0),
-
- ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup),
- Opts = ?PUT_INTERNAL_OPT([{user_pid,self()},
- {supervisors, [{system_sup, SystemSup},
- {subsystem_sup, SubSysSup},
- {connection_sup, ConnectionSup}]}
- ], Options0),
- ssh_connection_handler:start_connection(client, Socket, Opts, NegotiationTimeout).
+ lists:filtermap(Check, Args).
+%%%----------------
+continue_connect(Socket, Options0, NegTimeout) ->
+ {ok, {SockHost,SockPort}} = inet:sockname(Socket),
+ Options = ?PUT_INTERNAL_OPT([{negotiation_timeout,NegTimeout}], Options0),
+ Address = #address{address = SockHost,
+ port = SockPort,
+ profile = ?GET_OPT(profile,Options)
+ },
+ ssh_system_sup:start_connection(client, Address, Socket, Options).
%%--------------------------------------------------------------------
-spec close(ConnectionRef) -> ok | {error,term()} when
@@ -237,16 +287,18 @@ close(ConnectionRef) ->
| {options, client_options()}
| {algorithms, conn_info_algs()}
| {channels, conn_info_channels()}.
-
--spec connection_info(ConnectionRef) -> InfoTupleList when
+
+-spec connection_info(ConnectionRef) ->
+ InfoTupleList | {error, term()} when
ConnectionRef :: connection_ref(),
InfoTupleList :: [InfoTuple],
InfoTuple :: connection_info_tuple().
-connection_info(ConnectionRef) ->
+connection_info(ConnectionRef) ->
connection_info(ConnectionRef, []).
--spec connection_info(ConnectionRef, ItemList|Item) -> InfoTupleList|InfoTuple when
+-spec connection_info(ConnectionRef, ItemList|Item) ->
+ InfoTupleList | InfoTuple | {error, term()} when
ConnectionRef :: connection_ref(),
ItemList :: [Item],
Item :: client_version | server_version | user | peer | sockname | options | algorithms | sockname,
@@ -276,46 +328,48 @@ daemon(Port) ->
-spec daemon(inet:port_number()|open_socket(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}.
-daemon(Socket, UserOptions) when is_port(Socket) ->
- try
- #{} = Options = ssh_options:handle_options(server, UserOptions),
+daemon(Port, UserOptions) when 0 =< Port,Port =< 65535 ->
+ daemon(any, Port, 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(Socket, UserOptions) ->
+ case ssh_options:handle_options(server, UserOptions) of
+ #{} = Options0 ->
+ case valid_socket_to_use(Socket, ?GET_OPT(transport,Options0)) of
+ ok ->
+ try
+ %% throws error:Error if no usable hostkey is found
+ ssh_connection_handler:available_hkey_algorithms(server, Options0),
+ {ok, {SockHost,SockPort}} = inet:sockname(Socket),
+ Address = #address{address = SockHost,
+ port = SockPort,
+ profile = ?GET_OPT(profile,Options0)
+ },
+ Options = ?PUT_INTERNAL_OPT({connected_socket, Socket}, Options0),
+ case ssh_system_sup:start_connection(server, Address, Socket, Options) of
+ {ok,Pid} ->
+ {ok,Pid};
+ {error, {already_started, _}} ->
+ {error, eaddrinuse};
+ {error, Error} ->
+ {error, Error}
+ end
+ catch
+ error:{shutdown,Err} ->
+ {error,Err};
+ exit:{noproc, _} ->
+ {error, ssh_not_started};
+ C:R ->
+ {error,{could_not_start_connection,{C,R}}}
+ end;
+
+ {error,SockError} ->
+ {error,SockError}
+ end;
+
+ {error,OptionError} ->
+ {error,OptionError}
+ end.
-daemon(Port, UserOptions) when 0 =< Port, Port =< 65535 ->
- daemon(any, Port, UserOptions).
-spec daemon(any | inet:ip_address(), inet:port_number(), daemon_options()) -> {ok,daemon_ref()} | {error,term()}
@@ -327,31 +381,46 @@ daemon(Host0, Port0, UserOptions0) when
try
{Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0),
#{} = Options0 = ssh_options:handle_options(server, UserOptions),
- {open_listen_socket(Host1, Port0, Options0), Options0}
+ %% We need to open the listen socket here before start of the system supervisor. That
+ %% is because Port0 might be 0, or if an FD is provided in the Options0, in which case
+ %% the real listening port will be known only after the gen_tcp:listen call.
+ maybe_open_listen_socket(Host1, Port0, Options0)
of
- {{{Host,Port}, ListenSocket}, Options1} ->
+ {Host, Port, ListenSocket, Options1} ->
try
%% 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, Options1),
- ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options1),
- 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)
+ %% and ListenSocket, if provided, is for listening on connections. But
+ %% it is still owned by self()...
+
+ %% throws error:Error if no usable hostkey is found
+ ssh_connection_handler:available_hkey_algorithms(server, Options1),
+ ssh_system_sup:start_system(#address{address = Host,
+ port = Port,
+ profile = ?GET_OPT(profile,Options1)},
+ Options1)
of
- {error,Err} ->
+ {ok,DaemonRef} when ListenSocket == undefined ->
+ {ok,DaemonRef};
+ {ok,DaemonRef} ->
+ receive
+ {request_control, ListenSocket, ReqPid} ->
+ ok = controlling_process(ListenSocket, ReqPid, Options1),
+ ReqPid ! {its_yours,ListenSocket}
+ end,
+ {ok,DaemonRef};
+ {error, {already_started, _}} ->
close_listen_socket(ListenSocket, Options1),
- {error,Err};
- OK ->
- OK
+ {error, eaddrinuse};
+ {error, Error} ->
+ close_listen_socket(ListenSocket, Options1),
+ {error, Error}
catch
+ error:{shutdown,Err} ->
+ close_listen_socket(ListenSocket, Options1),
+ {error,Err};
+ exit:{noproc, _} ->
+ close_listen_socket(ListenSocket, Options1),
+ {error, ssh_not_started};
error:Error ->
close_listen_socket(ListenSocket, Options1),
error(Error);
@@ -376,6 +445,21 @@ daemon(_, _, _) ->
{error, badarg}.
%%--------------------------------------------------------------------
+-spec daemon_replace_options(DaemonRef, NewUserOptions) -> {ok,daemon_ref()}
+ | {error,term()} when
+ DaemonRef :: daemon_ref(),
+ NewUserOptions :: daemon_options().
+
+daemon_replace_options(DaemonRef, NewUserOptions) ->
+ case ssh_system_sup:get_acceptor_options(DaemonRef) of
+ {ok, Os0} ->
+ Os1 = ssh_options:merge_options(server, NewUserOptions, Os0),
+ ssh_system_sup:replace_acceptor_options(DaemonRef, Os1);
+ {error, _Reason} = Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
-type daemon_info_tuple() ::
{port, inet:port_number()}
| {ip, inet:ip_address()}
@@ -388,20 +472,16 @@ daemon(_, _, _) ->
InfoTuple :: daemon_info_tuple().
daemon_info(DaemonRef) ->
- case catch ssh_system_sup:acceptor_supervisor(DaemonRef) of
- AsupPid when is_pid(AsupPid) ->
- [{Host,Port,Profile}] =
- [{Hst,Prt,Prf}
- || {{ssh_acceptor_sup,Hst,Prt,Prf},_Pid,worker,[ssh_acceptor]}
- <- supervisor:which_children(AsupPid)],
- IP =
- case inet:parse_strict_address(Host) of
- {ok,IP0} -> IP0;
- _ -> Host
+ case ssh_system_sup:get_daemon_listen_address(DaemonRef) of
+ {ok,A} ->
+ Address =
+ case inet:parse_strict_address(A#address.address) of
+ {ok,IP} -> A#address{address=IP};
+ _ -> A
end,
-
Opts =
- case ssh_system_sup:get_options(DaemonRef, Host, Port, Profile) of
+ %% Pick a subset of the Options to present:
+ case ssh_system_sup:get_options(DaemonRef, Address) of
{ok, OptMap} ->
lists:sort(
maps:to_list(
@@ -412,11 +492,12 @@ daemon_info(DaemonRef) ->
[]
end,
- {ok, [{port,Port},
- {ip,IP},
- {profile,Profile},
- {options,Opts}
+ {ok, [{port, Address#address.port},
+ {ip, Address#address.address},
+ {profile, Address#address.profile},
+ {options, Opts}
]};
+
_ ->
{error,bad_daemon_ref}
end.
@@ -460,14 +541,13 @@ stop_listener(Address, Port) ->
-spec stop_listener(any|inet:ip_address(), inet:port_number(), term()) -> ok.
-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) ->
- map_ip(fun(IP) ->
- ssh_system_sup:stop_listener(IP, Port, Profile)
- end, {address,Address}).
+ lists:foreach(fun({Sup,_Addr}) ->
+ stop_listener(Sup)
+ end,
+ ssh_system_sup:addresses(#address{address=Address,
+ port=Port,
+ profile=Profile})).
%%--------------------------------------------------------------------
%% Description: Stops the listener and all connections started by
@@ -476,7 +556,7 @@ stop_listener(Address, Port, Profile) ->
-spec stop_daemon(DaemonRef::daemon_ref()) -> ok.
stop_daemon(SysSup) ->
- ssh_system_sup:stop_system(server, SysSup).
+ ssh_system_sup:stop_system(SysSup).
-spec stop_daemon(inet:ip_address(), inet:port_number()) -> ok.
@@ -487,14 +567,13 @@ stop_daemon(Address, Port) ->
-spec stop_daemon(any|inet:ip_address(), inet:port_number(), atom()) -> ok.
-stop_daemon(any, Port, Profile) ->
- map_ip(fun(IP) ->
- ssh_system_sup:stop_system(server, IP, Port, Profile)
- end, [{0,0,0,0},{0,0,0,0,0,0,0,0}]);
stop_daemon(Address, Port, Profile) ->
- map_ip(fun(IP) ->
- ssh_system_sup:stop_system(server, IP, Port, Profile)
- end, {address,Address}).
+ lists:foreach(fun({Sup,_Addr}) ->
+ stop_daemon(Sup)
+ end,
+ ssh_system_sup:addresses(#address{address=Address,
+ port=Port,
+ profile=Profile})).
%%--------------------------------------------------------------------
%% Description: Starts an interactive shell to an SSH server on the
@@ -504,9 +583,6 @@ stop_daemon(Address, Port, Profile) ->
%%--------------------------------------------------------------------
-spec shell(open_socket() | host() | connection_ref()) -> _.
-shell(Socket) when is_port(Socket) ->
- shell(Socket, []);
-
shell(ConnectionRef) when is_pid(ConnectionRef) ->
case ssh_connection:session_channel(ConnectionRef, infinity) of
{ok,ChannelId} ->
@@ -529,23 +605,37 @@ shell(ConnectionRef) when is_pid(Connect
Error
end;
-shell(Host) ->
- shell(Host, ?SSH_DEFAULT_PORT, []).
+shell(Dest) ->
+ case is_host(Dest, []) of
+ true ->
+ shell(Dest, ?SSH_DEFAULT_PORT, []);
+ false ->
+ %% Maybe socket
+ shell_socket(Dest, [])
+ end.
+
-spec shell(open_socket() | host(), client_options()) -> _.
-shell(Socket, Options) when is_port(Socket) ->
+shell(Dest, Options) ->
+ case is_host(Dest, Options) of
+ true ->
+ shell(Dest, ?SSH_DEFAULT_PORT, Options);
+ false ->
+ %% Maybe socket
+ shell_socket(Dest, Options)
+ end.
+
+shell_socket(Socket, Options) ->
case connect(Socket, Options) of
{ok,ConnectionRef} ->
shell(ConnectionRef),
close(ConnectionRef);
Error ->
Error
- end;
-
-shell(Host, Options) ->
- shell(Host, ?SSH_DEFAULT_PORT, Options).
+ end.
+
-spec shell(Host, Port, Options) -> _ when
@@ -716,6 +806,56 @@ tcpip_tunnel_from_server(ConnectionRef,
end.
%%--------------------------------------------------------------------
+%% In move from public_key
+%%--------------------------------------------------------------------
+-spec hostkey_fingerprint(public_key:public_key()) -> string().
+
+hostkey_fingerprint(Key) ->
+ sshfp_string(md5, ssh_message:ssh2_pubkey_encode(Key) ).
+
+-spec hostkey_fingerprint(TypeOrTypes, Key) -> StringOrString
+ when
+ TypeOrTypes :: public_key:digest_type() | [public_key:digest_type()],
+ Key :: public_key:public_key(),
+ StringOrString :: string() | [string()] .
+
+hostkey_fingerprint(HashAlgs, Key) when is_list(HashAlgs) ->
+ EncKey = ssh_message:ssh2_pubkey_encode(Key),
+ [sshfp_full_string(HashAlg,EncKey) || HashAlg <- HashAlgs];
+hostkey_fingerprint(HashAlg, Key) when is_atom(HashAlg) ->
+ EncKey = ssh_message:ssh2_pubkey_encode(Key),
+ sshfp_full_string(HashAlg, EncKey).
+
+
+sshfp_string(HashAlg, EncodedKey) ->
+ %% Other HashAlgs than md5 will be printed with
+ %% other formats than hextstr by
+ %% ssh-keygen -E <alg> -lf <file>
+ fp_fmt(sshfp_fmt(HashAlg), crypto:hash(HashAlg, EncodedKey)).
+
+sshfp_full_string(HashAlg, EncKey) ->
+ lists:concat([sshfp_alg_name(HashAlg),
+ [$: | sshfp_string(HashAlg, EncKey)]
+ ]).
+
+sshfp_alg_name(sha) -> "SHA1";
+sshfp_alg_name(Alg) -> string:to_upper(atom_to_list(Alg)).
+
+sshfp_fmt(md5) -> hexstr;
+sshfp_fmt(_) -> b64.
+
+fp_fmt(hexstr, Bin) ->
+ lists:flatten(string:join([io_lib:format("~2.16.0b",[C1]) || <<C1>> <= Bin], ":"));
+fp_fmt(b64, Bin) ->
+ %% This function clause *seems* to be
+ %% [C || C<-base64:encode_to_string(Bin), C =/= $=]
+ %% but I am not sure. Must be checked.
+ B64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+ BitsInLast = 8*byte_size(Bin) rem 6,
+ Padding = (6-BitsInLast) rem 6, % Want BitsInLast = [1:5] to map to padding [5:1] and 0 -> 0
+ [lists:nth(C+1,B64Chars) || <<C:6>> <= <<Bin/binary,0:Padding>> ].
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
%% The handle_daemon_args/2 function basically only sets the ip-option in Opts
@@ -759,17 +899,26 @@ is_tcp_socket(Socket) ->
end.
%%%----------------------------------------------------------------
-open_listen_socket(_Host0, Port0, Options0) ->
- {ok,LSock} =
- case ?GET_SOCKET_OPT(fd, Options0) of
- undefined ->
- ssh_acceptor:listen(Port0, Options0);
+maybe_open_listen_socket(Host, Port, Options) ->
+ Opened =
+ case ?GET_SOCKET_OPT(fd, Options) of
+ undefined when Port == 0 ->
+ ssh_acceptor:listen(0, Options);
Fd when is_integer(Fd) ->
%% Do gen_tcp:listen with the option {fd,Fd}:
- ssh_acceptor:listen(0, Options0)
+ ssh_acceptor:listen(0, Options);
+ undefined ->
+ open_later
end,
- {ok,{LHost,LPort}} = inet:sockname(LSock),
- {{LHost,LPort}, LSock}.
+ case Opened of
+ {ok,LSock} ->
+ {ok,{LHost,LPort}} = inet:sockname(LSock),
+ {LHost, LPort, LSock, ?PUT_INTERNAL_OPT({lsocket,{LSock,self()}}, Options)};
+ open_later ->
+ {Host, Port, undefined, Options};
+ Others ->
+ Others
+ end.
%%%----------------------------------------------------------------
close_listen_socket(ListenSocket, Options) ->
@@ -780,41 +929,32 @@ close_listen_socket(ListenSocket, Option
_C:_E -> ok
end.
+controlling_process(ListenSocket, ReqPid, Options) ->
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ Callback:controlling_process(ListenSocket, ReqPid).
+
+transport_connect(Host, Port, SocketOpts, Options) ->
+ {_, Callback, _} = ?GET_OPT(transport, Options),
+ Callback:connect(Host, Port, SocketOpts, ?GET_OPT(connect_timeout,Options)).
+
%%%----------------------------------------------------------------
-finalize_start(Host, Port, Profile, Options0, F) ->
- try
- %% throws error:Error if no usable hostkey is found
- ssh_connection_handler:available_hkey_algorithms(server, Options0),
-
- sshd_sup:start_child(Host, Port, Profile, Options0)
- of
- {error, {already_started, _}} ->
- {error, eaddrinuse};
- {error, Error} ->
- {error, Error};
- Result = {ok,_} ->
- F(Options0, Result)
+is_host(X, Opts) ->
+ try is_host1(mangle_connect_address(X, Opts))
catch
- error:{shutdown,Err} ->
- {error,Err};
- exit:{noproc, _} ->
- {error, ssh_not_started}
+ _:_ -> false
end.
+
-%%%----------------------------------------------------------------
-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).
+is_host1(L) when is_list(L) -> true; %% "string()"
+is_host1(T) when tuple_size(T)==4 -> lists:all(fun(I) -> 0=<I andalso I=<255 end,
+ tuple_to_list(T));
+is_host1(T) when tuple_size(T)==16 -> lists:all(fun(I) -> 0=<I andalso I=<65535 end,
+ tuple_to_list(T));
+is_host1(loopback) -> true.
%%%----------------------------------------------------------------
+mangle_connect_address(A, #{socket_options := SockOpts}) ->
+ mangle_connect_address(A, SockOpts);
mangle_connect_address(A, SockOpts) ->
mangle_connect_address1(A, proplists:get_value(inet6,SockOpts,false)).
@@ -845,3 +985,58 @@ mangle_tunnel_address(X) when is_list(X)
{ok, {0,0,0,0,0,0,0,0}} -> <<"">>;
_ -> list_to_binary(X)
end.
+
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [tcp].
+
+ssh_dbg_flags(tcp) -> [c].
+
+ssh_dbg_on(tcp) -> dbg:tpl(?MODULE, controlling_process, 3, x),
+ dbg:tpl(?MODULE, transport_connect, 4, x),
+ dbg:tpl(?MODULE, close_listen_socket, 2, x).
+
+ssh_dbg_off(tcp) ->dbg:ctpl(?MODULE, controlling_process, 3),
+ dbg:ctpl(?MODULE, transport_connect, 4),
+ dbg:ctpl(?MODULE, close_listen_socket, 2).
+
+ssh_dbg_format(tcp, {call, {?MODULE,controlling_process, [ListenSocket, ReqPid, _Opts]}}) ->
+ ["TCP socket transferred to\n",
+ io_lib:format("Sock: ~p~n"
+ "ToPid: ~p~n", [ListenSocket, ReqPid])
+ ];
+ssh_dbg_format(tcp, {return_from, {?MODULE,controlling_process,3}, _Result}) ->
+ skip;
+
+ssh_dbg_format(tcp, {call, {?MODULE,close_listen_socket, [ListenSocket, _Opts]}}) ->
+ ["TCP socket listening closed\n",
+ io_lib:format("Sock: ~p~n", [ListenSocket])
+ ];
+ssh_dbg_format(tcp, {return_from, {?MODULE,close_listen_socket,2}, _Result}) ->
+ skip.
+
+
+ssh_dbg_format(tcp, {call, {?MODULE,transport_connect, [Host,Port,SockOpts,_Opts]}}, Stack) ->
+ {skip, [{transport_connect,Host,Port,SockOpts}|Stack]};
+ssh_dbg_format(tcp, {return_from, {?MODULE,transport_connect,4}, {ok,Sock}},
+ [{transport_connect,Host,Port,SockOpts}|Stack]) ->
+ {["TCP connected to\n",
+ io_lib:format("Host: ~p~n"
+ "Port: ~p~n"
+ "SockOpts: ~p~n"
+ "Socket: ~p~n", [Host,Port,SockOpts,Sock])
+ ],
+ Stack};
+ssh_dbg_format(tcp, {return_from, {?MODULE,transport_connect,4}, Result},
+ [{transport_connect,Host,Port,SockOpts}|Stack]) ->
+ {["TCP connected FAILED to\n",
+ io_lib:format("Host: ~p~n"
+ "Port: ~p~n"
+ "SockOpts: ~p~n"
+ "Result: ~p~n", [Host,Port,SockOpts,Result])
+ ],
+ Stack}.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2025. 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,6 +36,8 @@
-define(DEFAULT_SHELL, {shell, start, []} ).
+-define(DEFAULT_TIMEOUT, 5000).
+
-define(MAX_RND_PADDING_LEN, 15).
-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password").
@@ -48,7 +50,7 @@
-define(UINT16(X), (X):16/unsigned-big-integer).
-define(UINT32(X), (X):32/unsigned-big-integer).
-define(UINT64(X), (X):64/unsigned-big-integer).
--define(STRING(X), ?UINT32((size(X))), (X)/binary).
+-define(STRING(X), ?UINT32((byte_size(X))), (X)/binary).
-define(DEC_BIN(X,Len), ?UINT32(Len), X:Len/binary ).
-define(DEC_INT(I,Len), ?UINT32(Len), I:Len/big-signed-integer-unit:8 ).
@@ -117,7 +119,6 @@
-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) ).
--define(DELETE_OPT(KeyOrKeys,Opts), ?do_del_opt(user_options,KeyOrKeys,Opts) ).
%% Types
@@ -321,6 +322,7 @@
| tcpip_tunnel_in_daemon_option()
| authentication_daemon_options()
| diffie_hellman_group_exchange_daemon_option()
+ | max_initial_idle_time_daemon_option()
| negotiation_timeout_daemon_option()
| hello_timeout_daemon_option()
| hardening_daemon_options()
@@ -361,7 +363,9 @@
| {user_passwords, [{UserName::string(),Pwd::string()}]}
| {pk_check_user, boolean()}
| {password, string()}
- | {pwdfun, pwdfun_2() | pwdfun_4()} .
+ | {pwdfun, pwdfun_2() | pwdfun_4()}
+ | {no_auth_needed, boolean()}
+ .
-type prompt_texts() ::
kb_int_tuple()
@@ -389,6 +393,7 @@
-type explicit_group_file() :: {file,string()} .
-type ssh_moduli_file() :: {ssh_moduli_file,string()}.
+-type max_initial_idle_time_daemon_option() :: {max_initial_idle_time, timeout()} .
-type negotiation_timeout_daemon_option() :: {negotiation_timeout, timeout()} .
-type hello_timeout_daemon_option() :: {hello_timeout, timeout()} .
@@ -413,6 +418,11 @@
%% Records
+-record(address, {address,
+ port,
+ profile
+ }).
+
-record(ssh,
{
role :: client | role(),
@@ -433,6 +443,8 @@
send_ext_info, %% May send ext-info to peer
recv_ext_info, %% Expect ext-info from peer
+ kex_strict_negotiated = false,
+
algorithms, %% #alg{}
send_mac = none, %% send MAC algorithm
@@ -504,7 +516,8 @@
c_lng,
s_lng,
send_ext_info,
- recv_ext_info
+ recv_ext_info,
+ kex_strict_negotiated = false
}).
-record(ssh_pty, {c_version = "", % client version string, e.g "SSH-2.0-Erlang/4.10.5"
@@ -548,5 +561,11 @@
-define(CIRC_BUF_IN_ONCE(VALUE),
((fun(V) -> ?CIRC_BUF_IN(V), V end)(VALUE))
).
-
+
+-define(SELECT_MSG(__Fun),
+ (fun() ->
+ #{level := __Level} = logger:get_primary_config(),
+ __Fun(__Level)
+ end)()).
+
-endif. % SSH_HRL defined
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_info.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_info.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2024. 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,17 +20,17 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: Print some info of a running ssh aplication.
+%% Purpose: Print some info of a running ssh application.
%%----------------------------------------------------------------------
-module(ssh_info).
-export([print/0,
print/1,
- string/0,
- collect_pids/0
+ string/0
]).
+-include("ssh.hrl").
-include("ssh_connect.hrl").
print() ->
@@ -52,14 +52,11 @@ string() ->
[io_lib:nl(),
print_general(),
io_lib:nl(),
- underline("Client part", $=),
- lists:map(fun print_system/1, children(sshc_sup)),
+ underline("Client(s)", $-),
+ print_sups(client, sshc_sup),
io_lib:nl(),
- underline("Server part", $=),
- lists:map(fun print_system/1, children(sshd_sup)),
- io_lib:nl(),
- underline("Supervisors", $=),
- walk_sups(ssh_sup),
+ underline("Daemon(s)", $-),
+ print_sups(server, sshd_sup),
io_lib:nl()]
catch
_:_ ->
@@ -78,113 +75,226 @@ print_general() ->
io_lib:format('This printout is generated ~s. ~n',[datetime()])
].
+print_sups(Role, StartPid) ->
+ walk_tree(Role, get_subs_tree(StartPid)).
+
+%%%================================================================
+get_subs_tree(StartPid) ->
+ lists:foldl(fun({Id,_,worker,_}=C, Acc) -> [{C,chspec(StartPid,Id)}|Acc];
+ ({Id,Pid,supervisor,_}=C, Acc) -> [{C,chspec(StartPid,Id),get_subs_tree(Pid)}|Acc]
+ end, [], children(StartPid)).
-print_system({{server,ssh_system_sup,Addr,Port,Profile}, Pid, supervisor, [ssh_system_sup]}) ->
- [io_lib:format(?INDENT"Local: ~s (~p children) Profile ~p~n",
- [fmt_host_port({Addr,Port}),
- ssh_acceptor:number_of_connections(Pid),
- Profile
+chspec(Sup, Id) ->
+ try supervisor:get_childspec(Sup, Id)
+ of
+ {ok,Spec} -> Spec;
+ {error,_} -> undefined
+ catch _:_ -> undefined
+ end.
+
+%%%----------------------------------------------------------------
+walk_tree(Role, Tree) ->
+ walk_tree(Role, Tree, _Indent=?inc(0)).
+
+walk_tree(Role, [{{_,_,supervisor,_},_,_}=H|T], Indent) ->
+ [io_lib:format('~s',[format_sup(Role,H,Indent)]) |
+ walk_tree(Role, T, Indent)
+ ];
+walk_tree(Role, [{{_,_,worker,_},_}=H|T], Indent) ->
+ [io_lib:format('~s',[format_wrk(Role,H,Indent)]) |
+ walk_tree(Role, T, Indent)
+ ];
+walk_tree(_Role, [], _) ->
+ "".
+
+
+format_sup(server,
+ {{{ssh_system_sup,LocalAddress},Pid,supervisor,[ssh_system_sup]}, _Spec,
+ [{{{ssh_acceptor_sup,Address},AccSupPid,supervisor,[ssh_acceptor_sup]}, _AccSupSpec,
+ [{{{ssh_acceptor_sup,Address},AccPid,worker,[ssh_acceptor]}, _AccSpec}]}
+ | Children]
+ }, Indent) ->
+ [indent(Indent),
+ io_lib:format("Local listen: ~s, Daemon_ref = ~s~n"
+ "~ssys_sup=~s, acc_sup=~s, acc=~s~n",
+ [format_address(LocalAddress),print_pid(Pid),
+ indent(Indent),print_pid(Pid),print_pid(AccSupPid),print_pid(AccPid)]),
+ walk_tree(server, Children, ?inc(Indent)),
+ io_lib:nl() % Separate system supervisors by an empty line
+ ];
+format_sup(server, {{{ssh_system_sup,LocalAddress},Pid,supervisor,[ssh_system_sup]}, _Spec, Children}, Indent) ->
+ [indent(Indent),
+ io_lib:format("Local listen: none (was: ~s), Daemon_ref = ~s~n"
+ "~ssys_sup=~s~n",
+ [format_address(LocalAddress),print_pid(Pid),
+ indent(Indent),print_pid(Pid)]),
+ walk_tree(server, Children, ?inc(Indent)),
+ io_lib:nl() % Separate system supervisors by an empty line
+ ];
+format_sup(client,
+ {{Ref,ConnSup,supervisor,[ssh_connection_sup]}, _ConnSupSpec,
+ [{{connection,ConnPid,worker,[ssh_connection_handler]}, _ConnSpec}
+ | Children]
+ },
+ Indent) when is_reference(Ref) ->
+ [io_lib:format("~sLocal: ~s~n"
+ "~sRemote: ~s (Version: ~s)~n"
+ "~sConnectionRef=~s, connection_sup=~s~n",
+ [indent(Indent), local_addr(ConnPid),
+ indent(Indent), peer_addr(ConnPid), peer_version(client,ConnPid),
+ indent(Indent), print_pid(ConnPid), print_pid(ConnSup)
]),
- lists:map(fun print_subsystem/1, children(Pid))
+ walk_tree(client,
+ [{H,{connref,ConnPid},Cs} || {H,_,Cs} <- Children],
+ ?inc(Indent)),
+ io_lib:nl() % Separate sub system supervisors by an empty line
];
-print_system({{client,ssh_system_sup,Addr,Port,Profile}, Pid, supervisor, [ssh_system_sup]}) ->
- [io_lib:format(?INDENT"Local: ~s Profile ~p~n",
- [fmt_host_port({Addr,Port}),
- Profile
+format_sup(server,
+ {{Ref,ConnSup,supervisor,[ssh_connection_sup]}, _ConnSupSpec,
+ [{{connection,ConnPid,worker,[ssh_connection_handler]}, _ConnSpec}
+ | Children]
+ },
+ Indent) when is_reference(Ref) ->
+ [io_lib:format("~sRemote: ~s (Version: ~s)~n"
+ "~sConnectionRef=~s, connection_sup=~s~n",
+ [indent(Indent), peer_addr(ConnPid), peer_version(server,ConnPid),
+ indent(Indent), print_pid(ConnPid), print_pid(ConnSup)
]),
- lists:map(fun print_subsystem/1, children(Pid))
+ walk_tree(server,
+ [{H,{connref,ConnPid},Cs} || {H,_,Cs} <- Children],
+ ?inc(Indent)),
+ io_lib:nl() % Separate sub system supervisors by an empty line
+ ];
+format_sup(Role, {{channel_sup,Pid,supervisor,[ssh_channel_sup]}, {connref,ConnPid}, Children}, Indent) ->
+ [indent(Indent),
+ case Children of
+ [] ->
+ io_lib:format("No open channels (chan_sup=~s).~n",[print_pid(Pid)]);
+ _ ->
+ Cinfo = try
+ {ok,L} = ssh_connection_handler:info(ConnPid),
+ L
+ catch
+ _:_ -> []
+ end,
+ [io_lib:format("Open channels (chan_sup=~s):~n",[print_pid(Pid)]),
+ walk_tree(Role,
+ [{ChH, lists:keyfind(ChPid,#channel.user,Cinfo)} || {ChH={_,ChPid,_,_},_} <- Children],
+ ?inc(Indent))
+ ]
+ end
+ ];
+format_sup(Role, {{tcpip_forward_acceptor_sup,Pid,supervisor,[ssh_tcpip_forward_acceptor_sup]}, {connref,_ConnPid}, Children}, Indent) ->
+ [indent(Indent),
+ case Children of
+ [] ->
+ io_lib:format("TCP/IP forwarding not started (fwd_sup=~s)~n", [print_pid(Pid)]);
+ _ ->
+ [io_lib:format("TCP/IP forwarding (fwd_sup=~s):~n", [print_pid(Pid)]),
+ walk_tree(Role, Children, ?inc(Indent))
+ ]
+ end
];
-print_system({_, _Pid, worker, [ssh_controller]}) ->
- ""; % io_lib:format(?INDENT"Controller~n",[]);
-print_system(_X) ->
- io_lib:format(?INDENT"nyi system ~p~n",[_X]).
+format_sup(Role, {H, Spec, Children}, Indent) ->
+ [indent(Indent),
+ io_lib:format("?: ~200p ~s ~n", [H,print_spec(Spec)]),
+ walk_tree(Role, Children, ?inc(Indent))
+ ].
+format_wrk(_Role, {{{ssh_acceptor_sup,Address},Pid,worker,[ssh_acceptor]}, _Spec}, Indent) ->
+ [indent(Indent),
+ io_lib:format("acceptor: ~s ~s~n", [format_address(Address),print_pid(Pid)])
+ ];
+format_wrk(_Role, {{{From,To},Pid,worker,[ssh_tcpip_forward_acceptor]}, _Spec}, Indent) ->
+ io_lib:format("~sssh_tcpip_forward_acceptor ~s From: ~s, To: ~s~n", [indent(Indent),print_pid(Pid),
+ format_address(From), format_address(To)]);
+format_wrk(_Role, {{Ref,Pid,worker,[Cb]}, C}, Indent) when is_reference(Ref) ->
+ Str =
+ try io_lib:format("~p: (remote ~p)~s~s", [C#channel.local_id, C#channel.remote_id,
+ if_true(C#channel.sent_close, " sent_close"),
+ if_true(C#channel.recv_close, " recv_close")
+ ])
+ catch
+ _:_ -> "?:"
+ end,
+ ChCb = try
+ case Cb of
+ ssh_server_channel -> io_lib:format(" ~s", [Cb:get_print_info(Pid, channel_cb)]);
+ ssh_client_channel -> io_lib:format(" ~s", [Cb:get_print_info(Pid, channel_cb)]);
+ _ -> ""
+ end
+ catch _:_ -> ""
+ end,
+ [indent(Indent),
+ io_lib:format("ch ~s ~p~s ~s~n", [Str, Cb, ChCb, print_pid(Pid)])
+ ];
+format_wrk(_Role, {H,Spec}, Indent) ->
+ [indent(Indent),
+ io_lib:format("?: ~200p ~s~n", [H,print_spec(Spec)])
+ ].
-print_subsystem({{ssh_acceptor_sup,_Addr,_Port,_Profile}, _Pid, supervisor, [ssh_acceptor_sup]}) ->
- io_lib:format(?INDENT?INDENT"Acceptor~n",[]);
-print_subsystem({Ref,Pid,supervisor,[ssh_subsystem_sup]}) when is_reference(Ref),
- is_pid(Pid) ->
- Cs = children(Pid),
- [
- lists:map(
- fun(Sup) ->
- [print_conn(P) || {undefined,P,worker,[ssh_connection_handler]} <- children(Sup)]
- end,
- [P || {_Ref,P,supervisor,[ssh_connection_sup]} <- Cs]),
-
- lists:map(
- fun(Sup) ->
- [print_ch(M,P) || {_Ref,P,worker,[M]} <- children(Sup),
- lists:member(M, [ssh_channel,
- ssh_channel_sup,
- ssh_client_channel,
- ssh_daemon_channel,
- ssh_server_channel
- ])]
- end,
- [P || {_Ref,P,supervisor,[ssh_channel_sup]} <- Cs]),
-
- lists:map(
- fun(Sup) ->
- [io_lib:format(?INDENT?INDENT?INDENT"TCP/IP fwd acceptor: ~p~n", [Pa])
- || {undefined,Pa,worker,[ssh_tcpip_forward_acceptor]} <- children(Sup)]
- end,
- [P || {_Ref,P,supervisor,[ssh_tcpip_forward_acceptor_sup]} <- Cs])
- ];
-print_subsystem(_X) ->
- io_lib:format(?INDENT?INDENT"nyi subsystem ~p~n",[_X]).
+if_true(true, Str) -> Str;
+if_true(_, _) -> "".
+
+peer_version(Role, Pid) ->
+ try
+ Key =
+ case Role of
+ client -> server_version;
+ server -> client_version
+ end,
+ [{Key, {{_,_},V}}] =
+ ssh_connection_handler:connection_info(Pid, [Key]),
+ V
+ catch
+ _:_ -> "?"
+ end.
-print_conn(Pid) ->
+peer_addr(Pid) ->
try
- {{_Local,Remote},StrM} = ssh_connection_handler:get_print_info(Pid),
- io_lib:format(?INDENT?INDENT"Remote: ~s ConnectionRef = ~p ~s~n",[fmt_host_port(Remote),Pid,StrM])
+ [{peer,{_,AddrPort}}] =
+ ssh_connection_handler:connection_info(Pid, [peer]),
+ ssh_lib:format_address_port(AddrPort)
catch
- C:E ->
- io_lib:format('****print_conn FAILED for ConnPid ~p: ~p:~p~n',[Pid, C, E])
- end.
-
+ _:_ -> "?"
+ end.
-print_ch(CBmod, Pid) ->
+local_addr(Pid) ->
try
- {{_ConnManager,ChannelID}, Str} = ssh_server_channel:get_print_info(Pid),
- io_lib:format(?INDENT?INDENT?INDENT"ch ~p ~p ~p: ~s~n",[ChannelID, Pid, CBmod, Str])
+ [{socket,Socket}] =
+ ssh_connection_handler:connection_info(Pid, [socket]),
+ {ok, AddrPort} = inet:sockname(Socket),
+ ssh_lib:format_address_port(AddrPort)
catch
- C:E ->
- io_lib:format('****print_ch FAILED for ChanPid ~p: ~p:~p~n',[Pid, C, E])
+ _:_ -> "?"
end.
-%%%================================================================
-walk_sups(StartPid) ->
- walk_sups(children(StartPid), _Indent=?inc(0)).
+format_address(#address{address=Addr, port=Port, profile=Prof}) ->
+ io_lib:format("~s (profile ~p)", [ssh_lib:format_address_port({Addr,Port}),Prof]);
+format_address(A) ->
+ io_lib:format("~p",[A]).
-walk_sups([H={_,Pid,_,_}|T], Indent) ->
- [indent(Indent),
- io_lib:format('~200p ~p is ~s~n',[H,Pid,dead_or_alive(Pid)]),
- case H of
- {_,_,supervisor,[ssh_connection_handler]} -> "";
- {_,Pid,supervisor,_} -> walk_sups(children(Pid), ?inc(Indent));
- _ -> ""
- end,
- walk_sups(T, Indent)
- ];
-walk_sups([], _) ->
- "".
+
+print_pid(Pid) ->
+ io_lib:format("~p~s",[Pid, dead_or_alive(Pid)]).
dead_or_alive(Name) when is_atom(Name) ->
case whereis(Name) of
undefined ->
- "**UNDEFINED**";
+ " **UNDEFINED**";
Pid ->
dead_or_alive(Pid)
end;
dead_or_alive(Pid) when is_pid(Pid) ->
- case process_info(Pid) of
- undefined -> "**DEAD**";
- _ -> "alive"
+ case process_info(Pid,message_queue_len) of
+ undefined -> " ***DEAD***";
+ {message_queue_len,N} when N>10 -> io_lib:format(" ***msg_queue_len: ~p***",[N]);
+ {message_queue_len,N} when N>0 -> io_lib:format(" (msg_queue_len: ~p)",[N]);
+ _ -> ""
end.
indent(I) -> io_lib:format('~*c',[I,$ ]).
@@ -204,29 +314,8 @@ children(Pid) ->
[]
end.
-is_connection_handler(Pid) ->
- try
- {ssh_connection_handler,init,_} =
- proplists:get_value(
- '$initial_call',
- proplists:get_value(
- dictionary,
- process_info(Pid, [dictionary])))
- of
- _ -> true
-
- catch
- _:_ ->
- false
- end.
-
-channels(Pid) ->
- case is_connection_handler(Pid) of
- true ->
- ssh_connection_handler:info(Pid,all);
- false ->
- false
- end.
+%%%================================================================
+print_spec(_Spec) -> "".
%%%================================================================
underline(Str, LineChar) ->
@@ -238,85 +327,7 @@ datetime() ->
lists:flatten(io_lib:format('~4w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w UTC',[YYYY,MM,DD, H,M,S])).
-fmt_host_port({{A,B,C,D},Port}) -> io_lib:format('~p.~p.~p.~p:~p',[A,B,C,D,Port]);
-fmt_host_port({Host,Port}) -> io_lib:format('~s:~p',[Host,Port]).
-
-%%%################################################################
-collect_pids() -> collect_pids(ssh_sup).
-
-collect_pids(P) ->
- Collector = pcollect_pids(P, spawn(fun init_collector/0)),
- Collector ! {get_values,self()},
- receive
- {values,Values} ->
- Values
- end.
-
-%%%----------------
-pcollect_pids(undefined, Collector) ->
- Collector;
-
-pcollect_pids(A, Collector) when is_atom(A) ->
- pcollect_pids(whereis(A), Collector);
-
-pcollect_pids(Pid, Collector) when is_pid(Pid) ->
- Collector ! {expect,Pid},
- spawn(fun() ->
- lists:foreach(
- fun(P2) ->
- pcollect_pids(P2,Collector)
- end, children(Pid)),
- Collector ! {value,Pid,Pid}
- end),
- Collector;
-
-pcollect_pids({Ref,Pid,supervisor,_}, Collector) when is_pid(Pid),
- is_reference(Ref) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({sshc_sup,Pid,supervisor,_}, Collector) when is_pid(Pid) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({sshd_sup,Pid,supervisor,_}, Collector) when is_pid(Pid) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({{ssh_acceptor_sup,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({{server,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({{server,_,_,_,_},Pid,supervisor,_}, Collector) when is_pid(Pid) ->
- pcollect_pids(Pid, Collector);
-
-pcollect_pids({undefined,Pid,supervisor,[ssh_connection_handler]}, Collector) ->
- Collector ! {value,Pid,Pid},
- case channels(Pid) of
- {ok,L} ->
- [Collector!{value,P,P} || #channel{user=P} <- L];
- _ ->
- ok
- end,
- Collector;
-
-pcollect_pids({_,Pid,_,_}, Collector) when is_pid(Pid) ->
- Collector ! {value,Pid,Pid},
- Collector;
-
-pcollect_pids(_, Collector) ->
- Collector.
-
-%%%----------------
-init_collector() ->
- loop_collector([],[]).
+%%%================================================================
-loop_collector(Expects, Values) ->
- receive
- {expect, Ref} ->
- loop_collector([Ref|Expects], Values);
- {value, Ref, Val} ->
- loop_collector(Expects--[Ref], [Val|Values]);
- {get_values, From} when Expects==[] ->
-%% Values=/=[] ->
- From ! {values,Values}
- end.
+%% ?wr_record(address);
+%% wr_record(R) -> io_lib:format('~p~n',[R]).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_message.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_message.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_message.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2025. 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.
@@ -24,6 +24,7 @@
-module(ssh_message).
-include_lib("public_key/include/public_key.hrl").
+-include_lib("kernel/include/logger.hrl").
-include("ssh.hrl").
-include("ssh_connect.hrl").
@@ -33,16 +34,21 @@
-export([encode/1, decode/1, decode_keyboard_interactive_prompts/2]).
-export([ssh2_pubkey_decode/1,
ssh2_pubkey_encode/1,
- ssh2_privkey_decode2/1]).
+ ssh2_privkey_decode2/1,
+ oid2ssh_curvename/1,
+ ssh_curvename2oid/1,
+ %% experimental:
+ ssh2_privkey_encode/1
+ ]).
-behaviour(ssh_dbg).
-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
-
+-define(ALG_NAME_LIMIT, 64). % RFC4251 sec6
ucl(B) ->
try unicode:characters_to_list(B) of
L when is_list(L) -> L;
- {error,_Matched,Rest} -> throw({error,{bad_unicode,Rest}})
+ {error,_Matched,_Rest} -> throw({error,bad_unicode})
catch
_:_ -> throw({error,bad_unicode})
end.
@@ -170,7 +176,7 @@ encode(#ssh_msg_userauth_pk_ok{
<<?Ebyte(?SSH_MSG_USERAUTH_PK_OK), ?Estring(Alg), ?Ebinary(KeyBlob)>>;
encode(#ssh_msg_userauth_passwd_changereq{prompt = Prompt,
- languge = Lang
+ language = Lang
})->
<<?Ebyte(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?Estring_utf8(Prompt), ?Estring(Lang)>>;
@@ -202,12 +208,12 @@ encode(#ssh_msg_disconnect{
encode(#ssh_msg_service_request{
name = Service
}) ->
- <<?Ebyte(?SSH_MSG_SERVICE_REQUEST), ?Estring_utf8(Service)>>;
+ <<?Ebyte(?SSH_MSG_SERVICE_REQUEST), ?Estring(Service)>>;
encode(#ssh_msg_service_accept{
name = Service
}) ->
- <<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring_utf8(Service)>>;
+ <<?Ebyte(?SSH_MSG_SERVICE_ACCEPT), ?Estring(Service)>>;
encode(#ssh_msg_ext_info{
nr_extensions = N,
@@ -370,7 +376,7 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_REQUEST)
try
#ssh_msg_channel_request{
recipient_channel = Recipient,
- request_type = ?unicode_list(RequestType),
+ request_type = binary:bin_to_list(RequestType),
want_reply = erl_boolean(Bool),
data = Data
}
@@ -400,8 +406,8 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_REQUEST
Data/binary>>) ->
#ssh_msg_userauth_request{
user = ?unicode_list(User),
- service = ?unicode_list(Service),
- method = ?unicode_list(Method),
+ service = binary:bin_to_list(Service),
+ method = binary:bin_to_list(Method),
data = Data
};
@@ -409,7 +415,7 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_FAILURE
?DEC_BIN(Auths,__0),
?BYTE(Bool)>>) ->
#ssh_msg_userauth_failure {
- authentications = ?unicode_list(Auths),
+ authentications = binary:bin_to_list(Auths),
partial_success = erl_boolean(Bool)
};
@@ -436,7 +442,7 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RE
decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?DEC_BIN(Prompt,__0), ?DEC_BIN(Lang,__1) >>) ->
#ssh_msg_userauth_passwd_changereq{
prompt = Prompt,
- languge = Lang
+ language = Lang
};
%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST:
@@ -523,12 +529,12 @@ decode(<<"ecdh",?BYTE(?SSH_MSG_KEX_ECDH_
decode(<<?SSH_MSG_SERVICE_REQUEST, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_request{
- name = ?unicode_list(Service)
+ name = binary:bin_to_list(Service)
};
decode(<<?SSH_MSG_SERVICE_ACCEPT, ?DEC_BIN(Service,__0)>>) ->
#ssh_msg_service_accept{
- name = ?unicode_list(Service)
+ name = binary:bin_to_list(Service)
};
decode(<<?BYTE(?SSH_MSG_DISCONNECT), ?UINT32(Code), ?DEC_BIN(Desc,__0), ?DEC_BIN(Lang,__1)>>) ->
@@ -569,16 +575,29 @@ decode(<<?BYTE(?SSH_MSG_DEBUG), ?BYTE(Bo
%%%-------- public key --------
ssh2_pubkey_encode(#'RSAPublicKey'{modulus = N, publicExponent = E}) ->
<<?STRING(<<"ssh-rsa">>), ?Empint(E), ?Empint(N)>>;
+
ssh2_pubkey_encode({Y, #'Dss-Parms'{p = P, q = Q, g = G}}) ->
<<?STRING(<<"ssh-dss">>), ?Empint(P), ?Empint(Q), ?Empint(G), ?Empint(Y)>>;
+
+ssh2_pubkey_encode({#'ECPoint'{point = Q}, {namedCurve,OID}}) when OID == ?'id-Ed25519' orelse
+ OID == ?'id-Ed448' ->
+ {KeyType, _} = oid2ssh_curvename(OID),
+ <<?STRING(KeyType), ?Estring(Q)>>;
+
+ssh2_pubkey_encode(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Key}) when OID == ?'id-Ed25519' orelse
+ OID == ?'id-Ed448' ->
+ {KeyType, _} = oid2ssh_curvename(OID),
+ <<?STRING(KeyType), ?Estring(Key)>>;
+
+ssh2_pubkey_encode(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Key}) ->
+ {KeyType,Curve} = oid2ssh_curvename(OID),
+ <<?STRING(KeyType), ?STRING(Curve), ?Estring(Key)>>;
+
ssh2_pubkey_encode({#'ECPoint'{point = Q}, {namedCurve,OID}}) ->
- Curve = public_key:oid2ssh_curvename(OID),
- KeyType = <<"ecdsa-sha2-", Curve/binary>>,
- <<?STRING(KeyType), ?STRING(Curve), ?Estring(Q)>>;
-ssh2_pubkey_encode({ed_pub, ed25519, Key}) ->
- <<?STRING(<<"ssh-ed25519">>), ?Estring(Key)>>;
-ssh2_pubkey_encode({ed_pub, ed448, Key}) ->
- <<?STRING(<<"ssh-ed448">>), ?Estring(Key)>>.
+ {KeyType,Curve} = oid2ssh_curvename(OID),
+ <<?STRING(KeyType), ?STRING(Curve), ?Estring(Q)>>.
%%%--------
ssh2_pubkey_decode(KeyBlob) ->
@@ -602,33 +621,90 @@ ssh2_pubkey_decode2(<<?UINT32(7), "ssh-d
q = Q,
g = G}
}, Rest};
-ssh2_pubkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) ->
- Sz = TL-11,
- <<_Curve:Sz/binary,
- ?DEC_BIN(SshName, _IL),
- ?DEC_BIN(Q, _QL),
- Rest/binary>> = KeyRest,
- OID = public_key:ssh_curvename2oid(SshName),
- {{#'ECPoint'{point = Q}, {namedCurve,OID}
- }, Rest};
-ssh2_pubkey_decode2(<<?UINT32(11), "ssh-ed25519",
- ?DEC_BIN(Key, _L),
- Rest/binary>>) ->
- {{ed_pub, ed25519, Key},
- Rest};
-ssh2_pubkey_decode2(<<?UINT32(9), "ssh-ed448",
- ?DEC_BIN(Key, _L),
- Rest/binary>>) ->
- {{ed_pub, ed448, Key},
+
+ssh2_pubkey_decode2(<<?DEC_BIN(SshCurveName,SCNL), Rest0/binary>>) ->
+ {Pub, Rest} =
+ case {SshCurveName, Rest0} of
+ {<<"ecdsa-sha2-", _/binary>>,
+ <<?DEC_BIN(_Curve, _IL),
+ ?DEC_BIN(Q, _QL),
+ Rest1/binary>>} -> {Q, Rest1};
+
+ {<<"ssh-ed",_/binary>>,
+ <<?DEC_BIN(Key, _L),
+ Rest1/binary>>} -> {Key, Rest1}
+ end,
+ OID = ssh_curvename2oid(SshCurveName),
+ {{#'ECPoint'{point = Pub}, {namedCurve,OID}},
Rest}.
-
+
%%%-------- private key --------
%% dialyser... ssh2_privkey_decode(KeyBlob) ->
%% dialyser... {Key,_RestBlob} = ssh2_privkey_decode2(KeyBlob),
%% dialyser... Key.
-
%% See sshkey_private_serialize_opt in sshkey.c
+
+ssh2_privkey_encode(#'RSAPrivateKey'
+ {version = 'two-prime', % Found this in public_key:generate_key/1 ..
+ modulus = N,
+ publicExponent = E,
+ privateExponent = D,
+ prime1 = P,
+ prime2 = Q,
+ %% exponent1, % D_mod_P_1
+ %% exponent2, % D_mod_Q_1
+ coefficient = IQMP
+ }) ->
+ <<?STRING(<<"ssh-rsa">>),
+ ?Empint(N), % Yes, N and E is reversed relative pubkey format
+ ?Empint(E), % --"--
+ ?Empint(D),
+ ?Empint(IQMP),
+ ?Empint(P),
+ ?Empint(Q)>>;
+
+ssh2_privkey_encode(#'DSAPrivateKey'
+ {version = 0,
+ p = P,
+ q = Q,
+ g = G,
+ y = Y,
+ x = X
+ }) ->
+ <<?STRING(<<"ssh-dss">>),
+ ?Empint(P),
+ ?Empint(Q),
+ ?Empint(G),
+ ?Empint(Y), % Publ key
+ ?Empint(X) % Priv key
+ >>;
+
+ssh2_privkey_encode(#'ECPrivateKey'
+ {version = 1,
+ parameters = {namedCurve,OID},
+ privateKey = Priv,
+ publicKey = Pub
+ }) when OID == ?'id-Ed25519' orelse
+ OID == ?'id-Ed448' ->
+ {CurveName,_} = oid2ssh_curvename(OID),
+ <<?STRING(CurveName),
+ ?STRING(Pub),
+ ?STRING(Priv)>>;
+
+ssh2_privkey_encode(#'ECPrivateKey'
+ {version = 1,
+ parameters = {namedCurve,OID},
+ privateKey = Priv,
+ publicKey = Q
+ }) ->
+ {CurveName,_} = oid2ssh_curvename(OID),
+ <<?STRING(CurveName),
+ ?STRING(CurveName), % SIC!
+ ?STRING(Q),
+ ?STRING(Priv)>>.
+
+%%%--------
ssh2_privkey_decode2(<<?UINT32(7), "ssh-rsa",
?DEC_INT(N, _NL), % Yes, N and E is reversed relative pubkey format
?DEC_INT(E, _EL), % --"--
@@ -661,30 +737,49 @@ ssh2_privkey_decode2(<<?UINT32(7), "ssh-
y = Y,
x = X
}, Rest};
-ssh2_privkey_decode2(<<?UINT32(TL), "ecdsa-sha2-",KeyRest/binary>>) ->
- Sz = TL-11,
- <<_Curve:Sz/binary,
- ?DEC_BIN(CurveName, _SNN),
- ?DEC_BIN(Q, _QL),
- ?DEC_BIN(Priv, _PrivL),
- Rest/binary>> = KeyRest,
- OID = public_key:ssh_curvename2oid(CurveName),
+
+ssh2_privkey_decode2(<<?DEC_BIN(SshCurveName,SCNL), Rest0/binary>>) ->
+ {Pub, Priv, Rest} =
+ case {SshCurveName, Rest0} of
+ {<<"ecdsa-sha2-",_/binary>>,
+ <<?DEC_BIN(_Curve, _IL),
+ ?DEC_BIN(Pub1, _QL),
+ ?DEC_BIN(Priv1, _PrivL),
+ Rest1/binary>>} ->
+ {Pub1, Priv1, Rest1};
+
+ {<<"ssh-ed",_/binary>>,
+ <<?DEC_BIN(Pub1, PL),
+ ?DEC_BIN(PrivPub, PPL),
+ Rest1/binary>>} ->
+ PL = PPL div 2,
+ <<Priv1:PL/binary, _/binary>> = PrivPub,
+ {Pub1, Priv1, Rest1}
+ end,
+ OID = ssh_curvename2oid(SshCurveName),
{#'ECPrivateKey'{version = 1,
parameters = {namedCurve,OID},
privateKey = Priv,
- publicKey = Q
- }, Rest};
-ssh2_privkey_decode2(<<?UINT32(11), "ssh-ed25519",
- ?DEC_BIN(Pub,_Lpub),
- ?DEC_BIN(Priv,_Lpriv),
- Rest/binary>>) ->
- {{ed_pri, ed25519, Pub, Priv}, Rest};
-ssh2_privkey_decode2(<<?UINT32(9), "ssh-ed448",
- ?DEC_BIN(Pub,_Lpub),
- ?DEC_BIN(Priv,_Lpriv),
- Rest/binary>>) ->
- {{ed_pri, ed448, Pub, Priv}, Rest}.
+ publicKey = Pub
+ }, Rest}.
+
+%% Description: Converts from the ssh name of elliptic curves to
+%% the OIDs.
+%%--------------------------------------------------------------------
+ssh_curvename2oid(<<"ssh-ed25519">>) -> ?'id-Ed25519';
+ssh_curvename2oid(<<"ssh-ed448">> ) -> ?'id-Ed448';
+ssh_curvename2oid(<<"ecdsa-sha2-nistp256">>) -> ?'secp256r1';
+ssh_curvename2oid(<<"ecdsa-sha2-nistp384">>) -> ?'secp384r1';
+ssh_curvename2oid(<<"ecdsa-sha2-nistp521">>) -> ?'secp521r1'.
+
+%% Description: Converts from elliptic curve OIDs to the ssh name.
+%%--------------------------------------------------------------------
+oid2ssh_curvename(?'id-Ed25519')-> {<<"ssh-ed25519">>, 'n/a'};
+oid2ssh_curvename(?'id-Ed448') -> {<<"ssh-ed448">>, 'n/a'};
+oid2ssh_curvename(?'secp256r1') -> {<<"ecdsa-sha2-nistp256">>, <<"nistp256">>};
+oid2ssh_curvename(?'secp384r1') -> {<<"ecdsa-sha2-nistp384">>, <<"nistp384">>};
+oid2ssh_curvename(?'secp521r1') -> {<<"ecdsa-sha2-nistp521">>, <<"nistp521">>}.
%%%================================================================
%%%
@@ -726,9 +821,33 @@ decode_kex_init(<<?BYTE(Bool)>>, Acc, 0)
%% See rfc 4253 7.1
X = 0,
list_to_tuple(lists:reverse([X, erl_boolean(Bool) | Acc]));
-decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) ->
- Names = string:tokens(?unicode_list(Data), ","),
- decode_kex_init(Rest, [Names | Acc], N -1).
+decode_kex_init(<<?DEC_BIN(Data,__0), Rest/binary>>, Acc, N) when
+ byte_size(Data) < ?MAX_NUM_ALGORITHMS * ?ALG_NAME_LIMIT ->
+ BinParts = binary:split(Data, <<$,>>, [global]),
+ AlgCount = length(BinParts),
+ case AlgCount =< ?MAX_NUM_ALGORITHMS of
+ true ->
+ Process =
+ fun(<<>>, PAcc) ->
+ PAcc;
+ (Part, PAcc) ->
+ case byte_size(Part) =< ?ALG_NAME_LIMIT of
+ true ->
+ Name = binary:bin_to_list(Part),
+ [Name | PAcc];
+ false ->
+ ?LOG_DEBUG("Ignoring too long name", []),
+ PAcc
+ end
+ end,
+ Names = lists:foldr(Process, [], BinParts),
+ decode_kex_init(Rest, [Names | Acc], N - 1);
+ false ->
+ throw({error, {kexinit_error, N, {alg_count, AlgCount}}})
+ end;
+decode_kex_init(<<?DEC_BIN(Data,__0), _Rest/binary>>, _Acc, N) ->
+ throw({error, {kexinit, N, {string_size, byte_size(Data)}}}).
+
%%%================================================================
@@ -746,13 +865,8 @@ encode_signature(#'RSAPublicKey'{}, SigA
encode_signature({_, #'Dss-Parms'{}}, _SigAlg, Signature) ->
<<?Ebinary(<<"ssh-dss">>), ?Ebinary(Signature)>>;
encode_signature({#'ECPoint'{}, {namedCurve,OID}}, _SigAlg, Signature) ->
- Curve = public_key:oid2ssh_curvename(OID),
- <<?Ebinary(<<"ecdsa-sha2-",Curve/binary>>), ?Ebinary(Signature)>>;
-encode_signature({ed_pub, ed25519,_}, _SigAlg, Signature) ->
- <<?Ebinary(<<"ssh-ed25519">>), ?Ebinary(Signature)>>;
-encode_signature({ed_pub, ed448,_}, _SigAlg, Signature) ->
- <<?Ebinary(<<"ssh-ed448">>), ?Ebinary(Signature)>>.
-
+ {SshCurveName,_} = oid2ssh_curvename(OID),
+ <<?Ebinary(<<SshCurveName/binary>>), ?Ebinary(Signature)>>.
%%%################################################################
@@ -878,4 +992,3 @@ ssh_dbg_format(raw_messages, {return_fro
?wr_record(ssh_msg_channel_failure);
wr_record(R) -> io_lib:format('~p~n',[R]).
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_options.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_options.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_options.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2025. 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,7 +34,8 @@
keep_set_options/2,
no_sensitive/2,
initial_default_algorithms/2,
- check_preferred_algorithms/1
+ check_preferred_algorithms/1,
+ merge_options/3
]).
-export_type([private_options/0
@@ -146,26 +147,18 @@ put_socket_value(A, SockOpts) when is_at
-spec delete_key(option_class(), option_key(), private_options(),
atom(), non_neg_integer()) -> private_options().
-delete_key(user_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) ->
- if
- is_list(Key) ->
- lists:foldl(fun maps:remove/2, Opts, Key);
- true ->
- maps:remove(Key, Opts)
- end;
-
-delete_key(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) andalso
- (Class == socket_options orelse
- Class == internal_options) ->
- Opts#{Class :=
- if
- is_list(Key) ->
- lists:foldl(fun maps:remove/2, maps:get(Class,Opts), Key);
- true ->
- maps:remove(Key, maps:get(Class,Opts))
- end
- }.
+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)}.
+
+%%%================================================================
+%%%
+%%% Replace 0 or more options in an options map
+%%%
+merge_options(Role, NewPropList, Opts0) when is_list(NewPropList),
+ is_map(Opts0) ->
+ check_and_save(NewPropList, default(Role), Opts0).
%%%================================================================
%%%
@@ -183,8 +176,7 @@ handle_options(Role, PropList0) ->
handle_options(Role, OptsList0, Opts0) when is_map(Opts0),
is_list(OptsList0) ->
OptsList1 = proplists:unfold(
- lists:foldr(fun(T,Acc) when is_tuple(T),
- size(T) =/= 2-> [{special_trpt_args,T} | Acc];
+ lists:foldr(fun(T,Acc) when tuple_size(T) =/= 2 -> [{special_trpt_args,T} | Acc];
(X,Acc) -> [X|Acc]
end,
[], OptsList0)),
@@ -235,10 +227,8 @@ handle_options(Role, OptsList0, Opts0) w
%% Enter the user's values into the map; unknown keys are
%% treated as socket options
- final_preferred_algorithms(
- lists:foldl(fun(KV, Vals) ->
- save(KV, OptionDefinitions, Vals)
- end, InitialMap, OptsList2))
+ check_and_save(OptsList2, OptionDefinitions, InitialMap)
+
catch
error:{EO, KV, Reason} when EO == eoptions ; EO == eerl_env ->
if
@@ -251,6 +241,13 @@ handle_options(Role, OptsList0, Opts0) w
end
end.
+check_and_save(OptsList, OptionDefinitions, InitialMap) ->
+ final_preferred_algorithms(
+ lists:foldl(fun(KV, Vals) ->
+ save(KV, OptionDefinitions, Vals)
+ end, InitialMap, OptsList)).
+
+
cnf_key(server) -> server_options;
cnf_key(client) -> client_options.
@@ -311,7 +308,7 @@ save({Inet,false}, _Defs, OptMap) when I
save({special_trpt_args,T}, _Defs, OptMap) when is_map(OptMap) ->
OptMap#{socket_options := [T | maps:get(socket_options,OptMap)]};
-%% and finaly the 'real stuff':
+%% and finally the 'real stuff':
save({Key,Value}, Defs, OptMap) when is_map(OptMap) ->
try (check_fun(Key,Defs))(Value)
of
@@ -493,6 +490,12 @@ default(server) ->
class => user_option
},
+ no_auth_needed =>
+ #{default => false,
+ chk => fun(V) -> erlang:is_boolean(V) end,
+ class => user_option
+ },
+
pk_check_user =>
#{default => false,
chk => fun(V) -> erlang:is_boolean(V) end,
@@ -529,6 +532,12 @@ default(server) ->
class => user_option
},
+ max_initial_idle_time =>
+ #{default => infinity, %% To not break compatibility
+ chk => fun(V) -> check_timeout(V) end,
+ class => user_option
+ },
+
negotiation_timeout =>
#{default => 2*60*1000,
chk => fun(V) -> check_timeout(V) end,
@@ -876,6 +885,12 @@ default(common) ->
#{default => ?MAX_RND_PADDING_LEN,
chk => fun(V) -> check_non_neg_integer(V) end,
class => undoc_user_option
+ },
+
+ channel_close_timeout =>
+ #{default => 5 * 1000,
+ chk => fun(V) -> check_non_neg_integer(V) end,
+ class => undoc_user_option
}
}.
@@ -1069,7 +1084,7 @@ check_modify_algorithms(M) when is_list(
[error_in_check(Op_KVs, "Bad modify_algorithms")
|| Op_KVs <- M,
not is_tuple(Op_KVs)
- orelse (size(Op_KVs) =/= 2)
+ orelse (tuple_size(Op_KVs) =/= 2)
orelse (not lists:member(element(1,Op_KVs), [append,prepend,rm]))],
{true, [{Op,normalize_mod_algs(KVs,false)} || {Op,KVs} <- M]};
check_modify_algorithms(_) ->
@@ -1094,11 +1109,11 @@ normalize_mod_algs([K|Ks], KVs0, Acc, Us
normalize_mod_algs(Ks, KVs, [{K,Vs} | Acc], UseDefaultAlgs);
normalize_mod_algs([], [], Acc, _) ->
%% No values left in the key-value list after removing the expected entries
- %% (thats good)
+ %% (that's good)
lists:reverse(Acc);
normalize_mod_algs([], [{K,_}|_], _, _) ->
%% Some values left in the key-value list after removing the expected entries
- %% (thats bad)
+ %% (that's bad)
case ssh_transport:algo_class(K) of
true -> error_in_check(K, "Duplicate key");
false -> error_in_check(K, "Unknown key")
@@ -1181,7 +1196,7 @@ check_input_ok(Algs) ->
[error_in_check(KVs, "Bad preferred_algorithms")
|| KVs <- Algs,
not is_tuple(KVs)
- orelse (size(KVs) =/= 2)].
+ orelse (tuple_size(KVs) =/= 2)].
%%%----------------------------------------------------------------
final_preferred_algorithms(Options0) ->
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_sftpd.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_sftpd.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_sftpd.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2025. 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,7 +27,7 @@
-behaviour(ssh_server_channel).
-include_lib("kernel/include/file.hrl").
-
+-include_lib("kernel/include/logger.hrl").
-include("ssh.hrl").
-include("ssh_xfer.hrl").
-include("ssh_connect.hrl"). %% For ?DEFAULT_PACKET_SIZE and ?DEFAULT_WINDOW_SIZE
@@ -52,6 +52,8 @@
file_handler, % atom() - callback module
file_state, % state for the file callback module
max_files, % integer >= 0 max no files sent during READDIR
+ max_handles, % integer > 0 - max number of file handles
+ max_path, % integer > 0 - max length of path
options, % from the subsystem declaration
handles % list of open handles
%% handle is either {<int>, directory, {Path, unread|eof}} or
@@ -65,6 +67,8 @@
Options :: [ {cwd, string()} |
{file_handler, CbMod | {CbMod, FileState}} |
{max_files, integer()} |
+ {max_handles, integer()} |
+ {max_path, integer()} |
{root, string()} |
{sftpd_vsn, integer()}
],
@@ -102,7 +106,7 @@ init(Options) ->
%% Get the root of the file system (symlinks must be followed,
%% otherwise the realpath call won't work). But since symbolic links
- %% isn't supported on all plattforms we have to use the root property
+ %% isn't supported on all platforms we have to use the root property
%% supplied by the user.
{Root, State} =
case resolve_symlinks(Root0,
@@ -115,8 +119,14 @@ init(Options) ->
{Root0, State0}
end,
MaxLength = proplists:get_value(max_files, Options, 0),
+ MaxHandles = proplists:get_value(max_handles, Options, 1000),
+ MaxPath = proplists:get_value(max_path, Options, 4096),
Vsn = proplists:get_value(sftpd_vsn, Options, 5),
- {ok, State#state{cwd = CWD, root = Root, max_files = MaxLength,
+ {ok, State#state{cwd = CWD,
+ root = Root,
+ max_files = MaxLength,
+ max_handles = MaxHandles,
+ max_path = MaxPath,
options = Options,
handles = [], pending = <<>>,
xf = #ssh_xfer{vsn = Vsn, ext = []}}}.
@@ -128,9 +138,8 @@ init(Options) ->
%% Description: Handles channel messages
%%--------------------------------------------------------------------
handle_ssh_msg({ssh_cm, _ConnectionManager,
- {data, _ChannelId, Type, Data}}, State) ->
- State1 = handle_data(Type, Data, State),
- {ok, State1};
+ {data, ChannelId, Type, Data}}, State) ->
+ handle_data(Type, ChannelId, Data, State);
handle_ssh_msg({ssh_cm, _, {eof, ChannelId}}, State) ->
{stop, ChannelId, State};
@@ -187,24 +196,77 @@ terminate(_, #state{handles=Handles, fil
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-handle_data(0, <<?UINT32(Len), Msg:Len/binary, Rest/binary>>,
+handle_data(0, ChannelId, <<?UINT32(Len), Msg:Len/binary, Rest/binary>>,
State = #state{pending = <<>>}) ->
<<Op, ?UINT32(ReqId), Data/binary>> = Msg,
NewState = handle_op(Op, ReqId, Data, State),
case Rest of
<<>> ->
- NewState;
+ {ok, NewState};
_ ->
- handle_data(0, Rest, NewState)
+ handle_data(0, ChannelId, Rest, NewState)
end;
-
-handle_data(0, Data, State = #state{pending = <<>>}) ->
- State#state{pending = Data};
-
-handle_data(Type, Data, State = #state{pending = Pending}) ->
- handle_data(Type, <<Pending/binary, Data/binary>>,
- State#state{pending = <<>>}).
-
+handle_data(0, _ChannelId, Data, State = #state{pending = <<>>}) ->
+ {ok, State#state{pending = Data}};
+handle_data(Type, ChannelId, Data0, State = #state{pending = Pending}) ->
+ Data = <<Pending/binary, Data0/binary>>,
+ Size = byte_size(Data),
+ case Size > ?SSH_MAX_PACKET_SIZE of
+ true ->
+ ReportFun =
+ fun([S]) ->
+ Report =
+ #{label => {error_logger, error_report},
+ report =>
+ io_lib:format("SFTP packet size (~B) exceeds the limit!",
+ [S])},
+ Meta =
+ #{error_logger =>
+ #{tag => error_report,type => std_error},
+ report_cb => fun(#{report := Msg}) -> {Msg, []} end},
+ {Report, Meta}
+ end,
+ ?LOG_ERROR(ReportFun, [Size]),
+ {stop, ChannelId, State};
+ _ ->
+ handle_data(Type, ChannelId, Data, State#state{pending = <<>>})
+ end.
+
+%% From draft-ietf-secsh-filexfer-02 "The file handle strings MUST NOT be longer than 256 bytes."
+handle_op(Request, ReqId, <<?UINT32(HLen), _/binary>>, State = #state{xf = XF})
+ when (Request == ?SSH_FXP_CLOSE orelse
+ Request == ?SSH_FXP_FSETSTAT orelse
+ Request == ?SSH_FXP_FSTAT orelse
+ Request == ?SSH_FXP_READ orelse
+ Request == ?SSH_FXP_READDIR orelse
+ Request == ?SSH_FXP_WRITE),
+ HLen > 256 ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE, "Invalid handle"),
+ State;
+handle_op(Request, ReqId, <<?UINT32(PLen), _/binary>>,
+ State = #state{max_path = MaxPath, xf = XF})
+ when (Request == ?SSH_FXP_LSTAT orelse
+ Request == ?SSH_FXP_MKDIR orelse
+ Request == ?SSH_FXP_OPEN orelse
+ Request == ?SSH_FXP_OPENDIR orelse
+ Request == ?SSH_FXP_READLINK orelse
+ Request == ?SSH_FXP_REALPATH orelse
+ Request == ?SSH_FXP_REMOVE orelse
+ Request == ?SSH_FXP_RMDIR orelse
+ Request == ?SSH_FXP_SETSTAT orelse
+ Request == ?SSH_FXP_STAT),
+ PLen > MaxPath ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NO_SUCH_PATH,
+ "No such path"),
+ State;
+handle_op(Request, ReqId, <<?UINT32(PLen), _:PLen/binary, ?UINT32(PLen2), _/binary>>,
+ State = #state{max_path = MaxPath, xf = XF})
+ when (Request == ?SSH_FXP_RENAME orelse
+ Request == ?SSH_FXP_SYMLINK),
+ (PLen > MaxPath orelse PLen2 > MaxPath) ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NO_SUCH_PATH,
+ "No such path"),
+ State;
handle_op(?SSH_FXP_INIT, Version, B, State) when is_binary(B) ->
XF = State#state.xf,
Vsn = lists:min([XF#ssh_xfer.vsn, Version]),
@@ -212,7 +274,7 @@ handle_op(?SSH_FXP_INIT, Version, B, Sta
ssh_xfer:xf_send_reply(XF1, ?SSH_FXP_VERSION, <<?UINT32(Vsn)>>),
State#state{xf = XF1};
handle_op(?SSH_FXP_REALPATH, ReqId,
- <<?UINT32(Rlen), RPath:Rlen/binary>>,
+ <<?UINT32(RLen), RPath:RLen/binary>>,
State0) ->
RelPath = relate_file_name(RPath, State0, _Canonicalize=false),
{Res, State} = resolve_symlinks(RelPath, State0),
@@ -228,14 +290,16 @@ handle_op(?SSH_FXP_REALPATH, ReqId,
end;
handle_op(?SSH_FXP_OPENDIR, ReqId,
<<?UINT32(RLen), RPath:RLen/binary>>,
- State0 = #state{xf = #ssh_xfer{vsn = Vsn},
- file_handler = FileMod, file_state = FS0}) ->
+ State0 = #state{xf = #ssh_xfer{vsn = Vsn},
+ file_handler = FileMod, file_state = FS0,
+ max_handles = MaxHandles}) ->
RelPath = unicode:characters_to_list(RPath),
AbsPath = relate_file_name(RelPath, State0),
XF = State0#state.xf,
{IsDir, FS1} = FileMod:is_dir(AbsPath, FS0),
State1 = State0#state{file_state = FS1},
+ HandlesCnt = length(State0#state.handles),
case IsDir of
false when Vsn > 5 ->
ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NOT_A_DIRECTORY,
@@ -245,8 +309,12 @@ handle_op(?SSH_FXP_OPENDIR, ReqId,
ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_FAILURE,
"Not a directory"),
State1;
- true ->
- add_handle(State1, XF, ReqId, directory, {RelPath,unread})
+ true when HandlesCnt < MaxHandles ->
+ add_handle(State1, XF, ReqId, directory, {RelPath,unread});
+ true ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_FAILURE,
+ "max_handles limit reached"),
+ State1
end;
handle_op(?SSH_FXP_READDIR, ReqId,
<<?UINT32(HLen), BinHandle:HLen/binary>>,
@@ -381,14 +449,12 @@ handle_op(?SSH_FXP_RMDIR, ReqId, <<?UINT
send_status(Status, ReqId, State1);
handle_op(?SSH_FXP_RENAME, ReqId,
- Bin = <<?UINT32(PLen), _:PLen/binary, ?UINT32(PLen2),
- _:PLen2/binary>>,
+ Bin = <<?UINT32(PLen), _:PLen/binary, ?UINT32(PLen2), _:PLen2/binary>>,
State = #state{xf = #ssh_xfer{vsn = Vsn}}) when Vsn==3; Vsn==4 ->
handle_op(?SSH_FXP_RENAME, ReqId, <<Bin/binary, 0:32>>, State);
handle_op(?SSH_FXP_RENAME, ReqId,
- <<?UINT32(PLen), BPath:PLen/binary, ?UINT32(PLen2),
- BPath2:PLen2/binary, ?UINT32(Flags)>>,
+ <<?UINT32(PLen), BPath:PLen/binary, ?UINT32(PLen2), BPath2:PLen2/binary, ?UINT32(Flags)>>,
State0 = #state{file_handler = FileMod, file_state = FS0}) ->
Path = relate_file_name(BPath, State0),
Path2 = relate_file_name(BPath2, State0),
@@ -426,23 +492,29 @@ handle_op(?SSH_FXP_SYMLINK, ReqId,
State1 = State0#state{file_state = FS1},
send_status(Status, ReqId, State1).
-new_handle([], H) ->
- H;
-new_handle([{N, _,_} | Rest], H) when N =< H ->
- new_handle(Rest, N+1);
-new_handle([_ | Rest], H) ->
- new_handle(Rest, H).
+new_handle_id([]) -> 0;
+new_handle_id([{_, _, _} | _] = Handles) ->
+ {HandleIds, _, _} = lists:unzip3(Handles),
+ new_handle_id(lists:sort(HandleIds));
+new_handle_id(HandleIds) ->
+ find_gap(HandleIds).
+
+find_gap([Id]) -> % no gap found
+ Id + 1;
+find_gap([Id1, Id2 | _]) when Id2 - Id1 > 1 -> % gap found
+ Id1 + 1;
+find_gap([_, Id | Rest]) ->
+ find_gap([Id | Rest]).
add_handle(State, XF, ReqId, Type, DirFileTuple) ->
Handles = State#state.handles,
- Handle = new_handle(Handles, 0),
- ssh_xfer:xf_send_handle(XF, ReqId, integer_to_list(Handle)),
- %% OBS: If you change handles-tuple also change new_handle!
- %% Is this this the best way to implement new handle?
- State#state{handles = [{Handle, Type, DirFileTuple} | Handles]}.
+ HandleId = new_handle_id(Handles),
+ ssh_xfer:xf_send_handle(XF, ReqId, integer_to_list(HandleId)),
+ %% OBS: If you change handles-tuple also change new_handle_id!
+ State#state{handles = [{HandleId, Type, DirFileTuple} | Handles]}.
get_handle(Handles, BinHandle) ->
- case (catch list_to_integer(binary_to_list(BinHandle))) of
+ case (catch binary_to_integer(BinHandle)) of
I when is_integer(I) ->
case lists:keysearch(I, 1, Handles) of
{value, T} -> T;
@@ -564,7 +636,10 @@ get_attrs(RelPath, [F | Rest], FileMod,
end,
Attrs = ssh_sftp:info_to_attr(Info),
get_attrs(RelPath, Rest, FileMod, FS1, Vsn, [{Name, Attrs} | Acc]);
- {{error, enoent}, FS1} ->
+ {{error, Msg}, FS1} when
+ Msg == enoent ; % The item has disappeared after reading the list of items to check
+ Msg == eacces -> % You are not allowed to read this
+ %% Skip this F and check the remaining Rest
get_attrs(RelPath, Rest, FileMod, FS1, Vsn, Acc);
{Error, FS1} ->
{Error, FS1}
@@ -694,7 +769,9 @@ open(Vsn, ReqId, Data, State) when Vsn >
do_open(ReqId, State, Path, Flags).
do_open(ReqId, State0, Path, Flags) ->
- #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}} = State0,
+ #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn},
+ max_handles = MaxHandles} = State0,
+ HandlesCnt = length(State0#state.handles),
AbsPath = relate_file_name(Path, State0),
{IsDir, _FS1} = FileMod:is_dir(AbsPath, FS0),
case IsDir of
@@ -706,7 +783,7 @@ do_open(ReqId, State0, Path, Flags) ->
ssh_xfer:xf_send_status(State0#state.xf, ReqId,
?SSH_FX_FAILURE, "File is a directory"),
State0;
- false ->
+ false when HandlesCnt < MaxHandles ->
OpenFlags = [binary | Flags],
{Res, FS1} = FileMod:open(AbsPath, OpenFlags, FS0),
State1 = State0#state{file_state = FS1},
@@ -717,7 +794,11 @@ do_open(ReqId, State0, Path, Flags) ->
ssh_xfer:xf_send_status(State1#state.xf, ReqId,
ssh_xfer:encode_erlang_status(Error)),
State1
- end
+ end;
+ false ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_FAILURE, "max_handles limit reached"),
+ State0
end.
%% resolve all symlinks in a path
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_sftp.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_sftp.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_sftp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2025. 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,9 +58,9 @@
-record(state,
{
xf,
- rep_buf = <<>>,
+ rep_buf = <<>> :: binary(),
req_id,
- req_list = [], %% {ReqId, Fun}
+ req_list = [], %% {ReqId, Fun}
inf, %% list of fileinf,
opts
}).
@@ -75,14 +75,14 @@
-record(bufinf,
{
- mode, % read | write (=from or to buffer by user)
- crypto_state,
+ mode :: read | write, % read | write (=from or to buffer by user)
+ crypto_state :: term() | undefined,
crypto_fun, % For encode or decode depending on the mode field
- size = 0, % # bytes "before" the current buffer for the postion call
+ size = 0 :: non_neg_integer() | undefined, % # bytes "before" the current buffer for the position call
- chunksize, % The size of the chunks to be sent or received
- enc_text_buf = <<>>, % Encrypted text
- plain_text_buf = <<>> % Decrypted text
+ chunksize :: non_neg_integer() | undefined, % The size of the chunks to be sent or received
+ enc_text_buf = <<>> :: binary() | undefined, % Encrypted text
+ plain_text_buf = <<>> :: binary() | undefined % Decrypted text
}).
-define(FILEOP_TIMEOUT, infinity).
@@ -111,16 +111,9 @@
%%%----------------------------------------------------------------
%%% start_channel/1
-start_channel(Cm) when is_pid(Cm) ->
- start_channel(Cm, []);
+start_channel(Dest) ->
+ start_channel(Dest, []).
-start_channel(Socket) when is_port(Socket) ->
- start_channel(Socket, []);
-
-start_channel(Host) ->
- start_channel(Host, []).
-
-
%%%----------------------------------------------------------------
%%% start_channel/2
@@ -128,7 +121,7 @@ start_channel(Host) ->
%%% function clauses.
-spec start_channel(ssh:open_socket(),
- [ssh:client_options() | sftp_option()]
+ [ssh:client_option() | sftp_option()]
)
-> {ok,pid(),ssh:connection_ref()} | {error,reason()};
@@ -138,25 +131,10 @@ start_channel(Host) ->
-> {ok,pid()} | {ok,pid(),ssh:connection_ref()} | {error,reason()};
(ssh:host(),
- [ssh:client_options() | sftp_option()]
+ [ssh:client_option() | sftp_option()]
)
-> {ok,pid(),ssh:connection_ref()} | {error,reason()} .
-start_channel(Socket, UserOptions0) when is_port(Socket) ->
- UserOptions = legacy_timeout(UserOptions0),
- Timeout = proplists:get_value(connect_timeout, UserOptions, infinity),
- {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions),
- case ssh:connect(Socket, SshOpts, Timeout) of
- {ok,Cm} ->
- case start_channel(Cm, ChanOpts ++ SftpOpts) of
- {ok, Pid} ->
- {ok, Pid, Cm};
- Error ->
- Error
- end;
- Error ->
- Error
- end;
start_channel(Cm, UserOptions0) when is_pid(Cm) ->
UserOptions = legacy_timeout(UserOptions0),
Timeout = proplists:get_value(timeout, UserOptions, infinity),
@@ -181,8 +159,29 @@ start_channel(Cm, UserOptions0) when is_
Error
end;
-start_channel(Host, UserOptions) ->
- start_channel(Host, 22, UserOptions).
+start_channel(Dest, UserOptions0) ->
+ UserOptions = legacy_timeout(UserOptions0),
+ {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions),
+ case ssh:is_host(Dest, SshOpts) of
+ true ->
+ %% Dest looks like is a Host
+ start_channel(Dest, 22, UserOptions);
+ false ->
+ %% No, it is probably not a Host, must be a socket
+ Socket = Dest,
+ Timeout = proplists:get_value(connect_timeout, SshOpts, infinity),
+ case ssh:connect(Socket, SshOpts, Timeout) of
+ {ok,Cm} ->
+ case start_channel(Cm, ChanOpts ++ SftpOpts) of
+ {ok, Pid} ->
+ {ok, Pid, Cm};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end
+ end.
%%%----------------------------------------------------------------
@@ -230,8 +229,8 @@ stop_channel(Pid) ->
receive {'DOWN',MonRef,_,_,_} -> ok
after
1000 ->
- exit(Pid, kill),
erlang:demonitor(MonRef, [flush]),
+ exit(Pid, kill),
ok
end;
false ->
@@ -777,14 +776,15 @@ read_file(Pid, Name) ->
Timeout :: timeout(),
Error :: {error, reason()}.
read_file(Pid, Name, FileOpTimeout) ->
- case open(Pid, Name, [read, binary], FileOpTimeout) of
- {ok, Handle} ->
- {ok,{_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout),
- Res = read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, []),
- close(Pid, Handle),
- Res;
- Error ->
- Error
+ try
+ {ok, Handle} = open(Pid, Name, [read, binary], FileOpTimeout),
+ {ok, {_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout),
+ Res = read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, []),
+ close(Pid, Handle),
+ Res
+ catch
+ error:{badmatch, Error = {error,_}} ->
+ Error
end.
read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) ->
@@ -814,15 +814,16 @@ write_file(Pid, Name, List) ->
write_file(Pid, Name, List, FileOpTimeout) when is_list(List) ->
write_file(Pid, Name, to_bin(List), FileOpTimeout);
write_file(Pid, Name, Bin, FileOpTimeout) ->
- case open(Pid, Name, [write, binary], FileOpTimeout) of
- {ok, Handle} ->
- {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
- Res = write_file_loop(Pid, Handle, 0, Bin, size(Bin), Packet,
- FileOpTimeout),
- close(Pid, Handle, FileOpTimeout),
- Res;
- Error ->
- Error
+ try
+ {ok, Handle} = open(Pid, Name, [write, binary], FileOpTimeout),
+ {ok, {_Window,Packet}} = send_window(Pid, FileOpTimeout),
+ Res = write_file_loop(Pid, Handle, 0, Bin, byte_size(Bin), Packet,
+ FileOpTimeout),
+ close(Pid, Handle, FileOpTimeout),
+ Res
+ catch
+ error:{badmatch, Error = {error, _}} ->
+ Error
end.
write_file_loop(_Pid, _Handle, _Pos, _Bin, 0, _PacketSz,_FileOpTimeout) ->
@@ -1018,7 +1019,7 @@ do_handle_call({pwrite,Async,Handle,At,D
{ok,Offset} ->
Data = to_bin(Data0),
ReqID = State#state.req_id,
- Size = size(Data),
+ Size = byte_size(Data),
ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),
State1 = update_size(Handle, Offset+Size, State),
make_reply(ReqID, Async, From, State1);
@@ -1031,7 +1032,7 @@ do_handle_call({write,Async,Handle,Data0
{ok,Offset} ->
Data = to_bin(Data0),
ReqID = State#state.req_id,
- Size = size(Data),
+ Size = byte_size(Data),
ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),
State1 = update_offset(Handle, Offset+Size, State),
make_reply(ReqID, Async, From, State1);
@@ -1225,10 +1226,10 @@ terminate(_Reason, State) ->
%% Internal functions
%%====================================================================
legacy_timeout(UserOptions) ->
- %% Make both connect_timeout and timeout defined if exaclty one of them is defined:
+ %% Make both connect_timeout and timeout defined if exactly one of them is defined:
case {proplists:get_value(connect_timeout, UserOptions),
proplists:get_value(timeout, UserOptions)} of
- {undefined, undefined} ->
+ {undefined, undefined} ->
UserOptions;
{undefined, TO} ->
[{connect_timeout,TO} | UserOptions];
@@ -1583,7 +1584,7 @@ erase_handle(Handle, State) ->
State#state{inf = FI}.
%%
-%% Caluclate a integer offset
+%% Calculate a integer offset
%%
lseek_position(Handle, Pos, State) ->
case maps:find(Handle, State#state.inf) of
@@ -1635,13 +1636,18 @@ to_bin(Data) when is_binary(Data) -> Dat
read_repeat(Pid, Handle, Len, FileOpTimeout) ->
- {ok,{_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout),
- read_rpt(Pid, Handle, Len, PacketSz, FileOpTimeout, <<>>).
+ try
+ {ok,{_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout),
+ read_rpt(Pid, Handle, Len, PacketSz, FileOpTimeout, <<>>)
+ catch
+ error:{badmatch, Error = {error, _}} ->
+ Error
+ end.
read_rpt(Pid, Handle, WantedLen, PacketSz, FileOpTimeout, Acc) when WantedLen > 0 ->
case read(Pid, Handle, min(WantedLen,PacketSz), FileOpTimeout) of
{ok, Data} ->
- read_rpt(Pid, Handle, WantedLen-size(Data), PacketSz, FileOpTimeout, <<Acc/binary, Data/binary>>);
+ read_rpt(Pid, Handle, WantedLen-byte_size(Data), PacketSz, FileOpTimeout, <<Acc/binary, Data/binary>>);
eof ->
{ok, Acc};
Error ->
@@ -1654,8 +1660,13 @@ read_rpt(_Pid, _Handle, WantedLen, _Pack
write_to_remote_tar(_Pid, _SftpHandle, <<>>, _FileOpTimeout) ->
ok;
write_to_remote_tar(Pid, SftpHandle, Bin, FileOpTimeout) ->
- {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
- write_file_loop(Pid, SftpHandle, 0, Bin, size(Bin), Packet, FileOpTimeout).
+ try
+ {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
+ write_file_loop(Pid, SftpHandle, 0, Bin, byte_size(Bin), Packet, FileOpTimeout)
+ catch
+ error:{badmatch, Error = {error, _}} ->
+ Error
+ end.
position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout) ->
{ok,#bufinf{mode = Mode,
@@ -1663,7 +1674,7 @@ position_buf(Pid, SftpHandle, BufHandle,
size = Size}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout),
case Pos of
{cur,0} when Mode==write ->
- {ok,Size+size(Buf0)};
+ {ok,Size+byte_size(Buf0)};
{cur,0} when Mode==read ->
{ok,Size};
@@ -1692,23 +1703,29 @@ position_buf(Pid, SftpHandle, BufHandle,
end.
read_buf(Pid, SftpHandle, BufHandle, WantedLen, FileOpTimeout) ->
- {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
- {ok,B0} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout),
- case do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0) of
- {ok,ResultBin,B} ->
- call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
- {ok,ResultBin};
- {error,Error} ->
- {error,Error};
- {eof,B} ->
- call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
- eof
- end.
+ try
+ {ok, {_Window, Packet}} = send_window(Pid, FileOpTimeout),
+ {ok, B0} = call(Pid, {get_bufinf, BufHandle}, FileOpTimeout),
+ case do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0) of
+ {ok, ResultBin, B} ->
+ call(Pid, {put_bufinf, BufHandle, B}, FileOpTimeout),
+ {ok, ResultBin};
+ {eof, B} ->
+ call(Pid, {put_bufinf, BufHandle, B}, FileOpTimeout),
+ eof
+ end
+ catch
+ error:{badmatch, Error = {error, _}} ->
+ Error;
+ error:{case_clause, Error = {error, _}} ->
+ Error
+ end.
+
do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout,
B=#bufinf{plain_text_buf=PlainBuf0,
size = Size})
- when size(PlainBuf0) >= WantedLen ->
+ when byte_size(PlainBuf0) >= WantedLen ->
%% We already have the wanted number of bytes decoded and ready!
<<ResultBin:WantedLen/binary, PlainBuf/binary>> = PlainBuf0,
{ok,ResultBin,B#bufinf{plain_text_buf=PlainBuf,
@@ -1719,8 +1736,8 @@ do_the_read_buf(Pid, SftpHandle, WantedL
enc_text_buf = EncBuf0,
chunksize = undefined
})
- when size(EncBuf0) > 0 ->
- %% We have (at least) one decodable byte waiting for decodeing.
+ when byte_size(EncBuf0) > 0 ->
+ %% We have (at least) one decodable byte waiting for decoding.
{ok,DecodedBin,B} = apply_crypto(EncBuf0, B0),
do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
B#bufinf{plain_text_buf = <<PlainBuf0/binary, DecodedBin/binary>>,
@@ -1732,8 +1749,8 @@ do_the_read_buf(Pid, SftpHandle, WantedL
enc_text_buf = EncBuf0,
chunksize = ChunkSize0
})
- when size(EncBuf0) >= ChunkSize0 ->
- %% We have (at least) one chunk of decodable bytes waiting for decodeing.
+ when byte_size(EncBuf0) >= ChunkSize0 ->
+ %% We have (at least) one chunk of decodable bytes waiting for decoding.
<<ToDecode:ChunkSize0/binary, EncBuf/binary>> = EncBuf0,
{ok,DecodedBin,B} = apply_crypto(ToDecode, B0),
do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout,
@@ -1755,21 +1772,22 @@ do_the_read_buf(Pid, SftpHandle, WantedL
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,
- B0#bufinf{plain_text_buf = <<PTB/binary,PlainBin/binary>>}) of
- {ok, B} ->
- call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
- ok;
- {error,Error} ->
- {error,Error}
+ try
+ {ok, {_Window,Packet}} = send_window(Pid, FileOpTimeout),
+ {ok, B0=#bufinf{plain_text_buf=PTB}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout),
+ {ok, B} = do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
+ B0#bufinf{plain_text_buf = <<PTB/binary,PlainBin/binary>>}),
+ call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout),
+ ok
+ catch
+ error:{badmatch, Error = {error, _}} ->
+ Error
end.
do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
B=#bufinf{enc_text_buf = EncBuf0,
size = Size})
- when size(EncBuf0) >= Packet ->
+ when byte_size(EncBuf0) >= Packet ->
<<BinToWrite:Packet/binary, EncBuf/binary>> = EncBuf0,
case write(Pid, SftpHandle, BinToWrite, FileOpTimeout) of
ok ->
@@ -1784,7 +1802,7 @@ do_the_write_buf(Pid, SftpHandle, Packet
B0=#bufinf{plain_text_buf = PlainBuf0,
enc_text_buf = EncBuf0,
chunksize = undefined})
- when size(PlainBuf0) > 0 ->
+ when byte_size(PlainBuf0) > 0 ->
{ok,EncodedBin,B} = apply_crypto(PlainBuf0, B0),
do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
B#bufinf{plain_text_buf = <<>>,
@@ -1795,7 +1813,7 @@ do_the_write_buf(Pid, SftpHandle, Packet
enc_text_buf = EncBuf0,
chunksize = ChunkSize0
})
- when size(PlainBuf0) >= ChunkSize0 ->
+ when byte_size(PlainBuf0) >= ChunkSize0 ->
<<ToEncode:ChunkSize0/binary, PlainBuf/binary>> = PlainBuf0,
{ok,EncodedBin,B} = apply_crypto(ToEncode, B0),
do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout,
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_system_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_system_sup.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_system_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,153 +31,243 @@
-include("ssh.hrl").
--export([start_link/5, stop_listener/1,
- stop_listener/3, stop_system/2,
- stop_system/4, system_supervisor/3,
- subsystem_supervisor/1, channel_supervisor/1,
- connection_supervisor/1,
- acceptor_supervisor/1, start_subsystem/6,
- stop_subsystem/2,
- get_options/4
+-export([start_link/3,
+ stop_listener/1,
+ stop_system/1,
+ start_system/2,
+ start_connection/4,
+ get_daemon_listen_address/1,
+ addresses/1,
+ get_options/2,
+ get_acceptor_options/1,
+ replace_acceptor_options/2
]).
%% Supervisor callback
-export([init/1]).
--define(START(Address, Port, Profile, Options),
- {ssh_acceptor_sup, start_link, [Address, Port, Profile, Options]}).
-
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Role, Address, Port, Profile, Options) ->
- Name = make_name(Address, Port, Profile),
- supervisor:start_link({local, Name}, ?MODULE, [Role, Address, Port, Profile, Options]).
+
+start_system(Address0, Options) ->
+ case find_system_sup(Address0) of
+ {ok,{SysPid,Address}} ->
+ restart_acceptor(SysPid, Address, Options);
+ {error,not_found} ->
+ supervisor:start_child(sshd_sup,
+ #{id => {?MODULE,Address0},
+ start => {?MODULE, start_link, [server, Address0, Options]},
+ restart => temporary,
+ type => supervisor
+ })
+ end.
+
+%%%----------------------------------------------------------------
+stop_system(SysSup) when is_pid(SysSup) ->
+ case lists:keyfind(SysSup, 2, supervisor:which_children(sup(server))) of
+ {{?MODULE, Id}, SysSup, _, _} -> stop_system(Id);
+ false -> ok
+ end;
+stop_system(Id) ->
+ supervisor:terminate_child(sup(server), {?MODULE, Id}).
+
+
+%%%----------------------------------------------------------------
+stop_listener(SystemSup) when is_pid(SystemSup) ->
+ {Id, _, _, _} = lookup(ssh_acceptor_sup, SystemSup),
+ supervisor:terminate_child(SystemSup, Id),
+ supervisor:delete_child(SystemSup, Id).
+
+%%%----------------------------------------------------------------
+get_daemon_listen_address(SystemSup) ->
+ try lookup(ssh_acceptor_sup, SystemSup)
+ of
+ {{ssh_acceptor_sup,Address}, _, _, _} ->
+ {ok, Address};
+ _ ->
+ {error, not_found}
+ catch
+ _:_ ->
+ {error, not_found}
+ end.
+
+%%%----------------------------------------------------------------
+%%% Start the connection child. It is a significant child of the system
+%%% supervisor (callback = this module) for server and non-significant
+%%% child of sshc_sup for client
+start_connection(Role = client, _, Socket, Options) ->
+ do_start_connection(Role, sup(client), false, Socket, Options);
+start_connection(Role = server, Address=#address{}, Socket, Options) ->
+ case get_system_sup(Address, Options) of
+ {ok, SysPid} ->
+ do_start_connection(Role, SysPid, true, Socket, Options);
+ Others ->
+ Others
+ end.
+
+do_start_connection(Role, SupPid, Significant, Socket, Options0) ->
+ Id = make_ref(),
+ Options = ?PUT_INTERNAL_OPT([{user_pid, self()}], Options0),
+ case supervisor:start_child(SupPid,
+ #{id => Id,
+ start => {ssh_connection_sup, start_link,
+ [Role,Id,Socket,Options]
+ },
+ restart => temporary,
+ significant => Significant,
+ type => supervisor
+ })
+ of
+ {ok,_ConnectionSupPid} ->
+ try
+ receive
+ {new_connection_ref, Id, ConnPid} ->
+ ssh_connection_handler:takeover(ConnPid, Role, Socket, Options)
+ after 10000 ->
+ error(timeout)
+ end
+ catch
+ error:{badmatch,{error,Error}} ->
+ {error,Error};
+ error:timeout ->
+ %% The connection was started, but the takover procedure timed out,
+ %% therefore it exists a subtree, but it is not quite ready and
+ %% must be removed (by the supervisor above):
+ supervisor:terminate_child(SupPid, Id),
+ {error, connection_start_timeout}
+ end;
+ Others ->
+ Others
+ end.
+
+%%%----------------------------------------------------------------
+start_link(Role, Address, Options) ->
+ supervisor:start_link(?MODULE, [Role, Address, Options]).
+
+%%%----------------------------------------------------------------
+addresses(#address{address=Address, port=Port, profile=Profile}) ->
+ [{SysSup,A} || {{ssh_system_sup,A},SysSup,supervisor,_} <-
+ supervisor:which_children(sshd_sup),
+ Address == any orelse A#address.address == Address,
+ Port == any orelse A#address.port == Port,
+ Profile == any orelse A#address.profile == Profile].
+
+%%%----------------------------------------------------------------
+%% SysPid is the DaemonRef
+get_acceptor_options(SysPid) ->
+ case get_daemon_listen_address(SysPid) of
+ {ok,Address} ->
+ get_options(SysPid, Address);
+ {error,not_found} ->
+ {error,bad_daemon_ref}
+ end.
+
+replace_acceptor_options(SysPid, NewOpts) ->
+ case get_daemon_listen_address(SysPid) of
+ {ok,Address} ->
+ try stop_listener(SysPid)
+ of
+ ok ->
+ restart_acceptor(SysPid, Address, NewOpts)
+ catch
+ error:_ ->
+ restart_acceptor(SysPid, Address, NewOpts)
+ end;
+ {error,Error} ->
+ {error,Error}
+ end.
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([server, Address, Port, Profile, Options]) ->
- SupFlags = #{strategy => one_for_one,
+init([Role, Address, Options]) ->
+ SupFlags = #{strategy => one_for_one,
+ auto_shutdown => all_significant,
intensity => 0,
period => 3600
},
ChildSpecs =
- case ?GET_INTERNAL_OPT(connected_socket,Options,undefined) of
- undefined ->
- [#{id => id(ssh_acceptor_sup, Address, Port, Profile),
- start => ?START(Address,Port,Profile,Options),
- restart => transient,
- type => supervisor
- }];
+ case {Role, is_socket_server(Options)} of
+ {server, false} ->
+ [acceptor_sup_child_spec(_SysSup=self(), Address, Options)];
_ ->
[]
end,
- {ok, {SupFlags,ChildSpecs}};
-
-init([client, _Address, _Port, _Profile, _Options]) ->
- SupFlags = #{strategy => one_for_one,
- intensity => 0,
- period => 3600
- },
- ChildSpecs = [],
{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) ->
- stop_listener(
- system_supervisor(Address, Port, Profile)).
-
-stop_system(server, SysSup) -> catch sshd_sup:stop_child(SysSup), ok;
-stop_system(client, SysSup) -> catch sshc_sup:stop_child(SysSup), ok.
+%% A macro to keep get_options/2 and acceptor_sup_child_spec/3 synchronized
+-define(accsup_start(SysSup,Addr,Opts),
+ {ssh_acceptor_sup, start_link, [SysSup,Addr,Opts]}
+ ).
-stop_system(server, Address, Port, Profile) ->
- catch sshd_sup:stop_child(Address, Port, Profile),
- ok.
-
-
-get_options(Sup, Address, Port, Profile) ->
+get_options(Sup, Address = #address{}) ->
+ %% Lookup the Option parameter in the running ssh_acceptor_sup:
try
- {ok, #{start:=?START(Address,Port,Profile,Options)}} =
- supervisor:get_childspec(Sup, id(ssh_acceptor_sup,Address,Port,Profile)),
- {ok, Options}
+ {ok, #{start:=?accsup_start(_, _, Options)}} =
+ supervisor:get_childspec(Sup, {ssh_acceptor_sup,Address}),
+ {ok, Options}
catch
_:_ -> {error,not_found}
end.
-system_supervisor(Address, Port, Profile) ->
- Name = make_name(Address, Port, Profile),
- whereis(Name).
-
-subsystem_supervisor(SystemSup) ->
- {_, Child, _, _} = lookup(ssh_subsystem_sup, SystemSup),
- Child.
-
-channel_supervisor(SystemSup) ->
- ssh_subsystem_sup:channel_supervisor(
- subsystem_supervisor(SystemSup)).
-
-connection_supervisor(SystemSup) ->
- ssh_subsystem_sup:connection_supervisor(
- subsystem_supervisor(SystemSup)).
-
-acceptor_supervisor(SystemSup) ->
- {_, Child, _, _} = lookup(ssh_acceptor_sup, SystemSup),
- Child.
-
-
-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,
- type => supervisor
- },
- supervisor:start_child(SystemSup, SubsystemSpec).
-
-stop_subsystem(SystemSup, SubSys) ->
- case catch lists:keyfind(SubSys, 2, supervisor:which_children(SystemSup)) of
- false ->
- {error, not_found};
- {Id, _, _, _} ->
- spawn(fun() -> supervisor:terminate_child(SystemSup, Id),
- supervisor:delete_child(SystemSup, Id) end),
- ok;
- {'EXIT', {noproc, _}} ->
- %% Already terminated; probably shutting down.
- ok;
- {'EXIT', {shutdown, _}} ->
- %% Already shutting down.
- ok
- end.
-
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-id(Sup, Address, Port, Profile) ->
- {Sup, Address, Port, Profile}.
-make_name(Address, Port, Profile) ->
- list_to_atom(lists:flatten(io_lib:format("ssh_system_~s_~p_~p_sup", [fmt_host(Address), Port, Profile]))).
+%% A separate function because this spec is need in >1 places
+acceptor_sup_child_spec(SysSup, Address, Options) ->
+ #{id => {ssh_acceptor_sup,Address},
+ start => ?accsup_start(SysSup, Address, Options),
+ restart => transient,
+ significant => true,
+ type => supervisor
+ }.
-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.
+lookup(SupModule, SystemSup) ->
+ lists:keyfind([SupModule], 4, supervisor:which_children(SystemSup)).
+get_system_sup(Address0, Options) ->
+ case find_system_sup(Address0) of
+ {ok,{SysPid,_Address}} ->
+ {ok,SysPid};
+ {error,not_found} ->
+ start_system(Address0, Options);
+ {error,Error} ->
+ {error,Error}
+ end.
-lookup(SupModule, SystemSup) ->
- lists:keyfind([SupModule], 4,
- supervisor:which_children(SystemSup)).
+find_system_sup(Address0) ->
+ case addresses(Address0) of
+ [{SysSupPid,Address}] ->
+ {ok,{SysSupPid,Address}};
+ [] -> {error,not_found};
+ [_,_|_] -> {error,ambiguous}
+ end.
+sup(client) -> sshc_sup;
+sup(server) -> sshd_sup.
+
+
+is_socket_server(Options) ->
+ undefined =/= ?GET_INTERNAL_OPT(connected_socket,Options,undefined).
+
+restart_acceptor(SysPid, Address, Options) ->
+ case lookup(ssh_acceptor_sup, SysPid) of
+ {_,_,supervisor,_} ->
+ {error, eaddrinuse};
+ false ->
+ ChildSpec = acceptor_sup_child_spec(SysPid, Address, Options),
+ case supervisor:start_child(SysPid, ChildSpec) of
+ {ok,_ChildPid} ->
+ {ok,SysPid}; % sic!
+ {ok,_ChildPid,_Info} ->
+ {ok,SysPid}; % sic!
+ {error,Error} ->
+ {error,Error}
+ end
+ end.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_tcpip_forward_acceptor.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2024. 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.
@@ -35,9 +35,8 @@ supervised_start(FwdSup, {ListenAddrStr,
case get_fwd_listen_opts(ListenAddrStr) of
{ok,Opts} ->
%% start listening on Addr:BoundPort
- case gen_tcp:listen(ListenPort, [binary,
- {reuseaddr,true},
- {active,false} | Opts]) of
+ case gen_tcp:listen(ListenPort,
+ Opts ++ [binary, {reuseaddr,true}, {active,false}]) of
{ok,LSock} ->
{ok,{_, TrueListenPort}} = inet:sockname(LSock),
ssh_tcpip_forward_acceptor_sup:start_child(FwdSup,
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_transport.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2025. 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,12 +26,11 @@
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/inet.hrl").
-
-include("ssh_transport.hrl").
-include("ssh.hrl").
-export([versions/2, hello_version_msg/1]).
--export([next_seqnum/1,
+-export([next_seqnum/3,
supported_algorithms/0, supported_algorithms/1,
default_algorithms/0, default_algorithms/1,
clear_default_algorithms_env/0,
@@ -42,17 +41,18 @@
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_kexinit_msg/4, 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,
valid_key_sha_alg/3,
- sha/1, sign/3, verify/5,
+ sign/3, sign/4,
+ verify/5,
+ sha/1,
get_host_key/2,
call_KeyCb/3,
public_algo/1]).
@@ -60,6 +60,8 @@
-behaviour(ssh_dbg).
-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
+-define(MIN_DH_KEY_SIZE, 400).
+
%%% For test suites
-export([pack/3, adjust_algs_for_peer_version/2]).
@@ -86,33 +88,45 @@ clear_default_algorithms_env() ->
| no_return() % error(Reason)
.
default_algorithms() ->
+ FipsMode = crypto:info_fips(),
case application:get_env(ssh, ?DEFAULT_ALGS) of
undefined ->
- %% Not cached, have to build the default, connection independent
- %% set of algorithms:
- Opts = get_alg_conf(),
- Algs1 =
- case proplists:get_value(preferred_algorithms, Opts) of
- undefined ->
- [{K,default_algorithms1(K)} || K <- algo_classes()];
- Algs0 ->
- {true,Algs01} = ssh_options:check_preferred_algorithms(Algs0),
- Algs01
- end,
- Algs =
- case proplists:get_value(modify_algorithms, Opts) of
- undefined ->
- Algs1;
- Modifications ->
- ssh_options:initial_default_algorithms(Algs1, Modifications)
- end,
- application:set_env(ssh, ?DEFAULT_ALGS, Algs),
+ Algs = build_cache(),
+ application:set_env(ssh, ?DEFAULT_ALGS, {FipsMode,Algs}),
Algs;
- {ok,Algs} ->
+ {ok,{FipsMode,Algs}} ->
+ %% Cached, and the FIPS mode is the same now as when it was cached.
+ Algs;
+
+ {ok,{_OtherFipsMode,_Algs}} ->
+ %% Cached, but the FIPS mode has changed.
+ Algs = build_cache(),
+ application:set_env(ssh, ?DEFAULT_ALGS, {FipsMode,Algs}),
Algs
end.
+build_cache() ->
+ Opts = get_alg_conf(),
+ Algs1 =
+ case proplists:get_value(preferred_algorithms, Opts) of
+ undefined ->
+ [{K,default_algorithms1(K)} || K <- algo_classes()];
+ Algs0 ->
+ {true,Algs01} = ssh_options:check_preferred_algorithms(Algs0),
+ Algs01
+ end,
+ Algs =
+ case proplists:get_value(modify_algorithms, Opts) of
+ undefined ->
+ Algs1;
+ Modifications ->
+ ssh_options:initial_default_algorithms(Algs1, Modifications)
+ end,
+ Algs.
+
+
+
get_alg_conf() ->
[{T,L} || T <- [preferred_algorithms, modify_algorithms],
L <- [application:get_env(ssh, T, [])],
@@ -137,10 +151,17 @@ algo_two_spec_class(_) -> false.
default_algorithms(Tag) ->
+ FipsMode = crypto:info_fips(),
case application:get_env(ssh, ?DEFAULT_ALGS) of
undefined ->
default_algorithms1(Tag);
- {ok,Algs} ->
+ {ok,{FipsMode,Algs}} ->
+ %% Cached, and the FIPS mode is the same now as when it was cached.
+ proplists:get_value(Tag, Algs, []);
+ {ok,{_OtherFipsMode,_Algs}} ->
+ %% Cached, but the FIPS mode has changed.
+ Algs = build_cache(),
+ application:set_env(ssh, ?DEFAULT_ALGS, {FipsMode,Algs}),
proplists:get_value(Tag, Algs, [])
end.
@@ -166,6 +187,7 @@ default_algorithms1(mac) ->
default_algorithms1(public_key) ->
supported_algorithms(public_key, [
+ 'ssh-rsa',
%% Gone in OpenSSH 7.3.p1:
'ssh-dss'
]);
@@ -179,18 +201,16 @@ supported_algorithms() -> [{K,supported_
supported_algorithms(kex) ->
select_crypto_supported(
[
- {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {curves,secp384r1}, {hashs,sha384}]},
+ {'curve25519-sha256', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]},
+ {'curve25519-sha256@libssh.org', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]},
+ {'curve448-sha512', [{public_keys,ecdh}, {curves,x448}, {hashs,sha512}]},
{'ecdh-sha2-nistp521', [{public_keys,ecdh}, {curves,secp521r1}, {hashs,sha512}]},
+ {'ecdh-sha2-nistp384', [{public_keys,ecdh}, {curves,secp384r1}, {hashs,sha384}]},
{'ecdh-sha2-nistp256', [{public_keys,ecdh}, {curves,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
- %% https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves
- %% Secure Shell (SSH) Key Exchange Method using Curve25519 and Curve448
- {'curve25519-sha256', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]},
- {'curve25519-sha256@libssh.org', [{public_keys,ecdh}, {curves,x25519}, {hashs,sha256}]},
- {'curve448-sha512', [{public_keys,ecdh}, {curves,x448}, {hashs,sha512}]},
{'diffie-hellman-group14-sha1', [{public_keys,dh}, {hashs,sha}]},
{'diffie-hellman-group-exchange-sha1', [{public_keys,dh}, {hashs,sha}]},
{'diffie-hellman-group1-sha1', [{public_keys,dh}, {hashs,sha}]}
@@ -198,13 +218,13 @@ supported_algorithms(kex) ->
supported_algorithms(public_key) ->
select_crypto_supported(
[
- {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]},
- {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]},
- {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]},
{'ssh-ed25519', [{public_keys,eddsa}, {curves,ed25519} ]},
{'ssh-ed448', [{public_keys,eddsa}, {curves,ed448} ]},
- {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
+ {'ecdsa-sha2-nistp521', [{public_keys,ecdsa}, {hashs,sha512}, {curves,secp521r1}]},
+ {'ecdsa-sha2-nistp384', [{public_keys,ecdsa}, {hashs,sha384}, {curves,secp384r1}]},
+ {'ecdsa-sha2-nistp256', [{public_keys,ecdsa}, {hashs,sha256}, {curves,secp256r1}]},
{'rsa-sha2-512', [{public_keys,rsa}, {hashs,sha512} ]},
+ {'rsa-sha2-256', [{public_keys,rsa}, {hashs,sha256} ]},
{'ssh-rsa', [{public_keys,rsa}, {hashs,sha} ]},
{'ssh-dss', [{public_keys,dss}, {hashs,sha} ]} % Gone in OpenSSH 7.3.p1
]);
@@ -213,7 +233,6 @@ supported_algorithms(cipher) ->
same(
select_crypto_supported(
[
- {'chacha20-poly1305@openssh.com', [{ciphers,chacha20}, {macs,poly1305}]},
{'aes256-gcm@openssh.com', [{ciphers,aes_256_gcm}]},
{'aes256-ctr', [{ciphers,aes_256_ctr}]},
{'aes192-ctr', [{ciphers,aes_192_ctr}]},
@@ -221,6 +240,7 @@ supported_algorithms(cipher) ->
{'aes128-ctr', [{ciphers,aes_128_ctr}]},
{'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]},
{'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]},
+ {'chacha20-poly1305@openssh.com', [{ciphers,chacha20}, {macs,poly1305}]},
{'aes256-cbc', [{ciphers,aes_256_cbc}]},
{'aes192-cbc', [{ciphers,aes_192_cbc}]},
{'aes128-cbc', [{ciphers,aes_128_cbc}]},
@@ -230,15 +250,15 @@ supported_algorithms(cipher) ->
supported_algorithms(mac) ->
same(
select_crypto_supported(
- [{'hmac-sha2-256-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
- {'hmac-sha2-512-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
- {'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]},
+ [{'hmac-sha2-512-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
+ {'hmac-sha2-256-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]},
+ {'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha1-etm@openssh.com', [{macs,hmac}, {hashs,sha256}]},
{'hmac-sha1', [{macs,hmac}, {hashs,sha}]},
{'hmac-sha1-96', [{macs,hmac}, {hashs,sha}]},
- {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]},
- {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]}
+ {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]},
+ {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]}
]
));
supported_algorithms(compression) ->
@@ -274,14 +294,19 @@ random_id(Nlo, Nup) ->
hello_version_msg(Data) ->
[Data,"\r\n"].
-next_seqnum(SeqNum) ->
+next_seqnum({State, _Role, init}, 16#ffffffff,
+ #ssh{algorithms = #alg{kex_strict_negotiated = true}})
+ when State == kexinit; State == key_exchange; State == new_keys ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation: recv_sequence = 16#ffffffff", []));
+next_seqnum(_State, SeqNum, _) ->
(SeqNum + 1) band 16#ffffffff.
is_valid_mac(_, _ , #ssh{recv_mac_size = 0}) ->
true;
is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
recv_mac_key = Key, recv_sequence = SeqNum}) ->
- crypto:equal_const_time(Mac, mac(Algorithm, Key, SeqNum, Data)).
+ ssh_lib:comp(Mac, mac(Algorithm, Key, SeqNum, Data)).
handle_hello_version(Version) ->
try
@@ -336,7 +361,8 @@ kexinit_message(Role, Random, Algs, Host
#ssh_msg_kexinit{
cookie = Random,
kex_algorithms = to_strings( get_algs(kex,Algs) )
- ++ kex_ext_info(Role,Opts),
+ ++ kex_ext_info(Role,Opts)
+ ++ kex_strict_alg(Role),
server_host_key_algorithms = HostKeyAlgs,
encryption_algorithms_client_to_server = c2s(cipher,Algs),
encryption_algorithms_server_to_client = s2c(cipher,Algs),
@@ -353,7 +379,8 @@ s2c(Key, Algs) -> x2y(server2client, Key
x2y(DirectionKey, Key, Algs) -> to_strings(proplists:get_value(DirectionKey, get_algs(Key,Algs))).
-get_algs(Key, Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)).
+get_algs(Key, {_FipsMode,Algs}) when is_list(Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key));
+get_algs(Key, Algs) when is_list(Algs) -> proplists:get_value(Key, Algs, default_algorithms(Key)).
to_strings(L) -> lists:map(fun erlang:atom_to_list/1, L).
@@ -364,52 +391,69 @@ new_keys_message(Ssh0) ->
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = client} = Ssh) ->
+ #ssh{role = client} = Ssh, ReNeg) ->
try
- {ok, Algorithms} = select_algorithm(client, Own, CounterPart, Ssh#ssh.opts),
+ {ok, Algorithms} =
+ select_algorithm(client, Own, CounterPart, Ssh, ReNeg),
true = verify_algorithm(Algorithms),
+ true = verify_kexinit_is_first_msg(Algorithms, Ssh, ReNeg),
Algorithms
of
Algos ->
key_exchange_first_msg(Algos#alg.kex,
Ssh#ssh{algorithms = Algos})
catch
- Class:Error ->
- Msg = kexinit_error(Class, Error, client, Own, CounterPart),
+ Class:Reason0 ->
+ Reason = ssh_lib:trim_reason(Reason0),
+ Msg = kexinit_error(Class, Reason, client, Own, CounterPart, Ssh),
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, Msg)
end;
handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
- #ssh{role = server} = Ssh) ->
+ #ssh{role = server} = Ssh, ReNeg) ->
try
- {ok, Algorithms} = select_algorithm(server, CounterPart, Own, Ssh#ssh.opts),
+ {ok, Algorithms} =
+ select_algorithm(server, CounterPart, Own, Ssh, ReNeg),
true = verify_algorithm(Algorithms),
+ true = verify_kexinit_is_first_msg(Algorithms, Ssh, ReNeg),
Algorithms
of
Algos ->
{ok, Ssh#ssh{algorithms = Algos}}
catch
- Class:Error ->
- Msg = kexinit_error(Class, Error, server, Own, CounterPart),
+ Class:Reason0 ->
+ Reason = ssh_lib:trim_reason(Reason0),
+ Msg = kexinit_error(Class, Reason, server, Own, CounterPart, Ssh),
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, Msg)
end.
-kexinit_error(Class, Error, Role, Own, CounterPart) ->
+kexinit_error(Class, Error, Role, Own, CounterPart, Ssh) ->
{Fmt,Args} =
case {Class,Error} of
{error, {badmatch,{false,Alg}}} ->
{Txt,W,C} = alg_info(Role, Alg),
- {"No common ~s algorithm,~n"
- " we have:~n ~s~n"
- " peer have:~n ~s~n",
- [Txt,
- lists:join(", ", element(W,Own)),
- lists:join(", ", element(C,CounterPart))
- ]};
+ MsgFun =
+ fun(debug) ->
+ {"No common ~s algorithm,~n"
+ " we have:~n ~s~n"
+ " peer have:~n ~s~n",
+ [Txt,
+ lists:join(", ", element(W,Own)),
+ lists:join(", ", element(C,CounterPart))]};
+ (_) ->
+ {"No common ~s algorithm", [Txt]}
+ end,
+ ?SELECT_MSG(MsgFun);
_ ->
{"Kexinit failed in ~p: ~p:~p", [Role,Class,Error]}
end,
- io_lib:format(Fmt, Args).
+ try io_lib:format(Fmt, Args, [{chars_limit, ssh_lib:max_log_len(Ssh)}]) of
+ R -> R
+ catch
+ _:_ ->
+ io_lib:format("Kexinit failed in ~p: ~p:~p", [Role, Class, Error],
+ [{chars_limit, ssh_lib:max_log_len(Ssh)}])
+ end.
alg_info(client, Alg) ->
alg_info(Alg);
@@ -459,6 +503,21 @@ verify_algorithm(#alg{kex = Kex}) ->
false -> {false, "kex"}
end.
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = false}, _, _) ->
+ true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true}, _, renegotiate) ->
+ true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true},
+ #ssh{send_sequence = 1, recv_sequence = 1},
+ init) ->
+ true;
+verify_kexinit_is_first_msg(#alg{kex_strict_negotiated = true},
+ #ssh{send_sequence = SendSequence,
+ recv_sequence = RecvSequence}, init) ->
+ error_logger:warning_report(
+ lists:concat(["KEX strict violation (", SendSequence, ", ", RecvSequence, ")."])),
+ {false, "kex_strict"}.
+
%%%----------------------------------------------------------------
%%%
%%% Key exchange initialization
@@ -530,24 +589,34 @@ handle_kexdh_init(#ssh_msg_kexdh_init{e
{Public, Private} = generate_key(dh, [P,G,2*Sz]),
K = compute_key(dh, E, Private, [P,G]),
MyPrivHostKey = get_host_key(SignAlg, Opts),
- MyPubHostKey = extract_public_key(MyPrivHostKey),
+ MyPubHostKey = ssh_file:extract_public_key(MyPrivHostKey),
H = kex_hash(Ssh0, MyPubHostKey, sha(Kex), {E,Public,K}),
- H_SIG = sign(H, sha(SignAlg), MyPrivHostKey),
- {SshPacket, Ssh1} =
- 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 = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh1, H)}};
-
+ case sign(H, SignAlg, MyPrivHostKey, Ssh0) of
+ {ok,H_SIG} ->
+ {SshPacket, Ssh1} =
+ 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 = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}};
+ {error,unsupported_sign_alg} ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("Unsupported algorithm ~p", [SignAlg],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}]))
+ end;
true ->
- ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ MsgFun =
+ fun(debug) ->
io_lib:format("Kexdh init failed, received 'e' out of bounds~n E=~p~n P=~p",
- [E,P])
- )
+ [E,P], [{chars_limit, ssh_lib:max_log_len(Opts)}]);
+ (_) ->
+ io_lib:format("Kexdh init failed, received 'e' out of bounds", [],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}] )
+ end,
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, ?SELECT_MSG(MsgFun))
end.
handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = PeerPubHostKey,
@@ -568,14 +637,15 @@ handle_kexdh_reply(#ssh_msg_kexdh_reply{
session_id = sid(Ssh, H)})};
Error ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("Kexdh init failed. Verify host key: ~p",[Error])
+ io_lib:format("Kexdh init failed. Verify host key: ~p",[Error],
+ [{chars_limit, ssh_lib:max_log_len(Ssh0)}])
)
end;
true ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
io_lib:format("Kexdh init failed, received 'f' out of bounds~n F=~p~n P=~p",
- [F,P])
+ [F,P], [{chars_limit, ssh_lib:max_log_len(Ssh0)}])
)
end.
@@ -601,7 +671,8 @@ handle_kex_dh_gex_request(#ssh_msg_kex_d
}};
{error,_} ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("No possible diffie-hellman-group-exchange group found",[])
+ io_lib:format("No possible diffie-hellman-group-exchange group found",[],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}])
)
end;
@@ -633,8 +704,8 @@ handle_kex_dh_gex_request(#ssh_msg_kex_d
}};
{error,_} ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("No possible diffie-hellman-group-exchange group found",[])
- )
+ io_lib:format("No possible diffie-hellman-group-exchange group found",[],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}]))
end;
handle_kex_dh_gex_request(_, _) ->
@@ -660,7 +731,6 @@ handle_kex_dh_gex_group(#ssh_msg_kex_dh_
{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)
-
{ok, SshPacket,
Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
@@ -677,27 +747,36 @@ handle_kex_dh_gex_init(#ssh_msg_kex_dh_g
if
1<K, K<(P-1) ->
MyPrivHostKey = get_host_key(SignAlg, Opts),
- MyPubHostKey = extract_public_key(MyPrivHostKey),
+ MyPubHostKey = ssh_file:extract_public_key(MyPrivHostKey),
H = kex_hash(Ssh0, MyPubHostKey, 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,SignAlg},
- f = Public,
- h_sig = H_SIG}, Ssh0),
- {ok, SshPacket, Ssh#ssh{shared_secret = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh, H)
- }};
+ case sign(H, SignAlg, MyPrivHostKey, Ssh0) of
+ {ok,H_SIG} ->
+ {SshPacket, Ssh} =
+ 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 = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)
+ }};
+ {error,unsupported_sign_alg} ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("Unsupported algorithm ~p", [SignAlg],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}]))
+ end;
true ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- "Kexdh init failed, received 'k' out of bounds"
- )
+ "Kexdh init failed, received 'k' out of bounds")
end;
true ->
- ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("Kexdh gex init failed, received 'e' out of bounds~n E=~p~n P=~p",
- [E,P])
- )
+ MsgFun =
+ fun(debug) ->
+ io_lib:format("Kexdh gex init failed, received 'e' out of bounds~n"
+ " E=~p~n P=~p", [E,P]);
+ (_) ->
+ io_lib:format("Kexdh gex init failed, received 'e' out of bounds", [])
+ end,
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, ?SELECT_MSG(MsgFun))
end.
handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = PeerPubHostKey,
@@ -722,20 +801,18 @@ handle_kex_dh_gex_reply(#ssh_msg_kex_dh_
session_id = sid(Ssh, H)})};
Error ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("Kexdh gex reply failed. Verify host key: ~p",[Error])
- )
+ io_lib:format("Kexdh gex reply failed. Verify host key: ~p",
+ [Error], [{chars_limit, ssh_lib:max_log_len(Ssh0)}]))
end;
true ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- "Kexdh gex init failed, 'K' out of bounds"
- )
+ "Kexdh gex init failed, 'K' out of bounds")
end;
true ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
io_lib:format("Kexdh gex init failed, received 'f' out of bounds~n F=~p~n P=~p",
- [F,P])
- )
+ [F,P], [{chars_limit, ssh_lib:max_log_len(Ssh0)}]))
end.
%%%----------------------------------------------------------------
@@ -754,26 +831,40 @@ handle_kex_ecdh_init(#ssh_msg_kex_ecdh_i
of
K ->
MyPrivHostKey = get_host_key(SignAlg, Opts),
- MyPubHostKey = extract_public_key(MyPrivHostKey),
+ MyPubHostKey = ssh_file:extract_public_key(MyPrivHostKey),
H = kex_hash(Ssh0, MyPubHostKey, 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,SignAlg},
- q_s = MyPublic,
- h_sig = H_SIG},
- Ssh0),
- {ok, SshPacket, Ssh1#ssh{keyex_key = {{MyPublic,MyPrivate},Curve},
- shared_secret = ssh_bits:mpint(K),
- exchanged_hash = H,
- session_id = sid(Ssh1, H)}}
+ case sign(H, SignAlg, MyPrivHostKey, Ssh0) of
+ {ok,H_SIG} ->
+ {SshPacket, Ssh1} =
+ 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 = ssh_bits:mpint(K),
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}};
+ {error,unsupported_sign_alg} ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("Unsupported algorithm ~p", [SignAlg],
+ [{chars_limit, ssh_lib:max_log_len(Opts)}]))
+ end
catch
- Class:Error ->
- ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ Class:Reason0 ->
+ Reason = ssh_lib:trim_reason(Reason0),
+ MsgFun =
+ fun(debug) ->
io_lib:format("ECDH compute key failed in server: ~p:~p~n"
"Kex: ~p, Curve: ~p~n"
"PeerPublic: ~p",
- [Class,Error,Kex,Curve,PeerPublic])
- )
+ [Class,Reason,Kex,Curve,PeerPublic],
+ [{chars_limit, ssh_lib:max_log_len(Ssh0)}]);
+ (_) ->
+ io_lib:format("ECDH compute key failed in server: ~p:~p",
+ [Class,Reason],
+ [{chars_limit, ssh_lib:max_log_len(Ssh0)}])
+ end,
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED, ?SELECT_MSG(MsgFun))
end.
handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_reply{public_host_key = PeerPubHostKey,
@@ -796,15 +887,14 @@ handle_kex_ecdh_reply(#ssh_msg_kex_ecdh_
session_id = sid(Ssh, H)})};
Error ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
- io_lib:format("ECDH reply failed. Verify host key: ~p",[Error])
- )
+ io_lib:format("ECDH reply failed. Verify host key: ~p",[Error],
+ [{chars_limit, ssh_lib:max_log_len(Ssh0)}]))
end
catch
Class:Error ->
?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
io_lib:format("Peer ECDH public key seem invalid: ~p:~p",
- [Class,Error])
- )
+ [Class,Error], [{chars_limit, ssh_lib:max_log_len(Ssh0)}]))
end.
@@ -821,6 +911,9 @@ handle_new_keys(#ssh_msg_newkeys{}, Ssh0
)
end.
+%%%----------------------------------------------------------------
+kex_strict_alg(client) -> [?kex_strict_c];
+kex_strict_alg(server) -> [?kex_strict_s].
%%%----------------------------------------------------------------
kex_ext_info(Role, Opts) ->
@@ -886,29 +979,11 @@ call_KeyCb(F, Args, Opts) ->
UserOpts = ?GET_OPT(key_cb_options, Opts),
apply(KeyCb, F, Args ++ [[{key_cb_private,KeyCbOpts}|UserOpts]]).
-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}) ->
- {Y, #'Dss-Parms'{p=P, q=Q, g=G}};
-extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
- publicKey = Q}) when is_tuple(OID) ->
- {#'ECPoint'{point=Q}, {namedCurve,OID}};
-extract_public_key({ed_pri, Alg, Pub, _Priv}) ->
- {ed_pub, Alg, Pub};
-extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) ->
- case {Alg, crypto:privkey_to_pubkey(Alg, M)} of
- {rsa, [E,N]} ->
- #'RSAPublicKey'{modulus = N, publicExponent = E};
- {dss, [P,Q,G,Y]} ->
- {Y, #'Dss-Parms'{p=P, q=Q, g=G}}
- end.
-
-
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, SSH) of
+ case verify(Digest, Alg#alg.hkey, Signature, PublicKey, SSH) of
false ->
{error, bad_signature};
true ->
@@ -938,31 +1013,31 @@ accepted_host(Ssh, PeerName, Port, Publi
"~s host key fingerprint is ~s.~n"
"New host ~p~p accept",
[fmt_hostkey(HostKeyAlg),
- public_key:ssh_hostkey_fingerprint(Alg,Public),
+ ssh:hostkey_fingerprint(Alg,Public),
PeerName, PortStr]),
yes == yes_no(Ssh, Prompt);
%% Call-back alternatives: A user provided fun is called for the decision:
F when is_function(F,2) ->
- case catch F(PeerName, public_key:ssh_hostkey_fingerprint(Public)) of
+ case catch F(PeerName, ssh:hostkey_fingerprint(Public)) of
true -> true;
_ -> {error, fingerprint_check_failed}
end;
F when is_function(F,3) ->
- case catch F(PeerName, Port, public_key:ssh_hostkey_fingerprint(Public)) of
+ case catch F(PeerName, Port, ssh:hostkey_fingerprint(Public)) of
true -> true;
_ -> {error, fingerprint_check_failed}
end;
{DigestAlg,F} when is_function(F,2) ->
- case catch F(PeerName, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of
+ case catch F(PeerName, ssh:hostkey_fingerprint(DigestAlg,Public)) of
true -> true;
_ -> {error, {fingerprint_check_failed,DigestAlg}}
end;
{DigestAlg,F} when is_function(F,3) ->
- case catch F(PeerName, Port, public_key:ssh_hostkey_fingerprint(DigestAlg,Public)) of
+ case catch F(PeerName, Port, ssh:hostkey_fingerprint(DigestAlg,Public)) of
true -> true;
_ -> {error, {fingerprint_check_failed,DigestAlg}}
end
@@ -1029,7 +1104,33 @@ known_host_key(#ssh{opts = Opts, peer =
%%
%% 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, Opts) ->
+select_algorithm(Role, Client, Server,
+ #ssh{opts = Opts,
+ kex_strict_negotiated = KexStrictNegotiated0},
+ ReNeg) ->
+ KexStrictNegotiated =
+ case ReNeg of
+ %% KEX strict negotiated once per connection
+ init ->
+ Result =
+ case Role of
+ server ->
+ lists:member(?kex_strict_c,
+ Client#ssh_msg_kexinit.kex_algorithms);
+ client ->
+ lists:member(?kex_strict_s,
+ Server#ssh_msg_kexinit.kex_algorithms)
+ end,
+ case Result of
+ true ->
+ logger:debug(lists:concat([Role, " will use strict KEX ordering"]));
+ _ ->
+ ok
+ end,
+ Result;
+ _ ->
+ KexStrictNegotiated0
+ end,
{Encrypt0, Decrypt0} = select_encrypt_decrypt(Role, Client, Server),
{SendMac0, RecvMac0} = select_send_recv_mac(Role, Client, Server),
@@ -1043,14 +1144,9 @@ select_algorithm(Role, Client, Server, O
Server#ssh_msg_kexinit.languages_client_to_server),
S_Lng = select(Client#ssh_msg_kexinit.languages_server_to_client,
Server#ssh_msg_kexinit.languages_server_to_client),
- HKey = select_all(Client#ssh_msg_kexinit.server_host_key_algorithms,
- Server#ssh_msg_kexinit.server_host_key_algorithms),
- HK = case HKey of
- [] -> undefined;
- [HK0|_] -> HK0
- end,
- %% Fixme verify Kex against HKey list and algorithms
-
+ HKey = select(Client#ssh_msg_kexinit.server_host_key_algorithms,
+ Server#ssh_msg_kexinit.server_host_key_algorithms),
+ %% FIXME verify Kex against HKey list and algorithms (see RFC4253 sec 7.1)
Kex = select(Client#ssh_msg_kexinit.kex_algorithms,
Server#ssh_msg_kexinit.kex_algorithms),
@@ -1070,7 +1166,7 @@ select_algorithm(Role, Client, Server, O
?GET_OPT(recv_ext_info,Opts),
{ok, #alg{kex = Kex,
- hkey = HK,
+ hkey = HKey,
encrypt = Encrypt,
decrypt = Decrypt,
send_mac = SendMac,
@@ -1080,7 +1176,8 @@ select_algorithm(Role, Client, Server, O
c_lng = C_Lng,
s_lng = S_Lng,
send_ext_info = SendExtInfo,
- recv_ext_info = RecvExtInfo
+ recv_ext_info = RecvExtInfo,
+ kex_strict_negotiated = KexStrictNegotiated
}}.
@@ -1178,7 +1275,8 @@ alg_setup(snd, SSH) ->
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
+ recv_ext_info = ALG#alg.recv_ext_info,
+ kex_strict_negotiated = ALG#alg.kex_strict_negotiated
};
alg_setup(rcv, SSH) ->
@@ -1190,22 +1288,23 @@ alg_setup(rcv, SSH) ->
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
+ recv_ext_info = ALG#alg.recv_ext_info,
+ kex_strict_negotiated = ALG#alg.kex_strict_negotiated
}.
-
-alg_init(snd, SSH0) ->
+alg_init(Dir = snd, SSH0) ->
{ok,SSH1} = send_mac_init(SSH0),
{ok,SSH2} = encrypt_init(SSH1),
{ok,SSH3} = compress_init(SSH2),
- SSH3;
+ {ok,SSH4} = maybe_reset_sequence(Dir, SSH3),
+ SSH4;
-alg_init(rcv, SSH0) ->
+alg_init(Dir = rcv, SSH0) ->
{ok,SSH1} = recv_mac_init(SSH0),
{ok,SSH2} = decrypt_init(SSH1),
{ok,SSH3} = decompress_init(SSH2),
- SSH3.
-
+ {ok,SSH4} = maybe_reset_sequence(Dir, SSH3),
+ SSH4.
alg_final(snd, SSH0) ->
{ok,SSH1} = send_mac_final(SSH0),
@@ -1219,38 +1318,27 @@ alg_final(rcv, SSH0) ->
{ok,SSH3} = decompress_final(SSH2),
SSH3.
-
-select_all(CL, SL) when length(CL) + length(SL) < ?MAX_NUM_ALGORITHMS ->
- %% algortihms only used by client
- %% NOTE: an algorithm occuring more than once in CL will still be present
- %% in CLonly. This is not a problem for nice clients.
- CLonly = CL -- SL,
-
- %% algorithms used by client and server (client pref)
- lists:foldr(fun(ALG, Acc) ->
- try [list_to_existing_atom(ALG) | Acc]
- catch
- %% If an malicious client uses the same non-existing algorithm twice,
- %% we will end up here
- _:_ -> Acc
- end
- end, [], (CL -- CLonly));
-
-select_all(CL, SL) ->
- Error = lists:concat(["Received too many algorithms (",length(CL),"+",length(SL)," >= ",?MAX_NUM_ALGORITHMS,")."]),
- ?DISCONNECT(?SSH_DISCONNECT_PROTOCOL_ERROR,
- Error).
-
-
select([], []) ->
none;
select(CL, SL) ->
- C = case select_all(CL,SL) of
- [] -> undefined;
- [ALG|_] -> ALG
- end,
- C.
-
+ select_first(CL, SL).
+
+select_first([ClientAlg | ClientRest], SL) ->
+ case lists:member(ClientAlg, SL) of
+ true ->
+ try list_to_existing_atom(ClientAlg) of
+ Alg when is_atom(Alg) ->
+ Alg
+ catch
+ error:badarg ->
+ select_first(ClientRest, SL)
+ end;
+ false ->
+ select_first(ClientRest, SL)
+ end;
+select_first([], _) ->
+ undefined.
+
ssh_packet(#ssh_msg_kexinit{} = Msg, Ssh0) ->
BinMsg = ssh_message:encode(Msg),
Ssh = key_init(Ssh0#ssh.role, Ssh0, BinMsg),
@@ -1282,9 +1370,9 @@ pack(common, rfc4253, PlainText, DeltaLe
#ssh{send_sequence = SeqNum,
send_mac = MacAlg,
send_mac_key = MacKey} = Ssh0) ->
- PadLen = padding_length(4+1+size(PlainText), Ssh0),
+ PadLen = padding_length(4+1+byte_size(PlainText), Ssh0),
Pad = ssh_bits:random(PadLen),
- TextLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ TextLen = 1 + byte_size(PlainText) + PadLen + DeltaLenTst,
PlainPkt = <<?UINT32(TextLen),?BYTE(PadLen), PlainText/binary, Pad/binary>>,
{Ssh1, CipherPkt} = encrypt(Ssh0, PlainPkt),
MAC0 = mac(MacAlg, MacKey, SeqNum, PlainPkt),
@@ -1295,9 +1383,9 @@ pack(common, enc_then_mac, PlainText, De
#ssh{send_sequence = SeqNum,
send_mac = MacAlg,
send_mac_key = MacKey} = Ssh0) ->
- PadLen = padding_length(1+size(PlainText), Ssh0),
+ PadLen = padding_length(1+byte_size(PlainText), Ssh0),
Pad = ssh_bits:random(PadLen),
- PlainLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ PlainLen = 1 + byte_size(PlainText) + PadLen + DeltaLenTst,
PlainPkt = <<?BYTE(PadLen), PlainText/binary, Pad/binary>>,
{Ssh1, CipherPkt} = encrypt(Ssh0, PlainPkt),
EncPacketPkt = <<?UINT32(PlainLen), CipherPkt/binary>>,
@@ -1305,9 +1393,9 @@ pack(common, enc_then_mac, PlainText, De
{<<?UINT32(PlainLen), CipherPkt/binary, MAC0/binary>>, Ssh1};
pack(aead, _, PlainText, DeltaLenTst, Ssh0) ->
- PadLen = padding_length(1+size(PlainText), Ssh0),
+ PadLen = padding_length(1+byte_size(PlainText), Ssh0),
Pad = ssh_bits:random(PadLen),
- PlainLen = 1 + size(PlainText) + PadLen + DeltaLenTst,
+ PlainLen = 1 + byte_size(PlainText) + PadLen + DeltaLenTst,
PlainPkt = <<?BYTE(PadLen), PlainText/binary, Pad/binary>>,
{Ssh1, {CipherPkt,MAC0}} = encrypt(Ssh0, <<?UINT32(PlainLen),PlainPkt/binary>>),
{<<CipherPkt/binary,MAC0/binary>>, Ssh1}.
@@ -1334,7 +1422,7 @@ handle_packet_part(<<>>, Encrypted0, AEA
end;
handle_packet_part(DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0)
- when (size(DecryptedPfx)+size(EncryptedBuffer)) < TotalNeeded ->
+ when (byte_size(DecryptedPfx)+byte_size(EncryptedBuffer)) < TotalNeeded ->
%% need more bytes to finalize the packet
{get_more, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded, Ssh0};
@@ -1353,7 +1441,7 @@ handle_packet_part(DecryptedPfx, Encrypt
%%%----------------
unpack(common, rfc4253, DecryptedPfx, EncryptedBuffer, _AEAD, TotalNeeded,
#ssh{recv_mac_size = MacSize} = Ssh0) ->
- MoreNeeded = TotalNeeded - size(DecryptedPfx) - MacSize,
+ MoreNeeded = TotalNeeded - byte_size(DecryptedPfx) - MacSize,
<<EncryptedSfx:MoreNeeded/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
{Ssh1, DecryptedSfx} = decrypt(Ssh0, EncryptedSfx),
PlainPkt = <<DecryptedPfx/binary, DecryptedSfx/binary>>,
@@ -1370,7 +1458,7 @@ unpack(common, enc_then_mac, <<?UINT32(P
case is_valid_mac(MAC0, <<?UINT32(PlainLen),Payload/binary>>, Ssh0) of
true ->
{Ssh1, <<?BYTE(PaddingLen), PlainRest/binary>>} = decrypt(Ssh0, Payload),
- CompressedPlainTextLen = size(PlainRest) - PaddingLen,
+ CompressedPlainTextLen = byte_size(PlainRest) - PaddingLen,
<<CompressedPlainText:CompressedPlainTextLen/binary, _Padding/binary>> = PlainRest,
{ok, CompressedPlainText, NextPacketBytes, Ssh1};
false ->
@@ -1380,7 +1468,7 @@ unpack(common, enc_then_mac, <<?UINT32(P
unpack(aead, _, DecryptedPfx, EncryptedBuffer, AEAD, TotalNeeded,
#ssh{recv_mac_size = MacSize} = Ssh0) ->
%% enough bytes to decode the packet.
- MoreNeeded = TotalNeeded - size(DecryptedPfx) - MacSize,
+ MoreNeeded = TotalNeeded - byte_size(DecryptedPfx) - MacSize,
<<EncryptedSfx:MoreNeeded/binary, Mac:MacSize/binary, NextPacketBytes/binary>> = EncryptedBuffer,
case decrypt(Ssh0, {AEAD,EncryptedSfx,Mac}) of
{Ssh1, error} ->
@@ -1392,7 +1480,7 @@ unpack(aead, _, DecryptedPfx, EncryptedB
%%%----------------------------------------------------------------
get_length(common, rfc4253, EncryptedBuffer, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
- case size(EncryptedBuffer) >= erlang:max(8, BlockSize) of
+ case byte_size(EncryptedBuffer) >= erlang:max(8, BlockSize) of
true ->
<<EncBlock:BlockSize/binary, EncryptedRest/binary>> = EncryptedBuffer,
{Ssh,
@@ -1412,7 +1500,7 @@ get_length(common, enc_then_mac, Encrypt
end;
get_length(aead, _, EncryptedBuffer, Ssh) ->
- case {size(EncryptedBuffer) >= 4, Ssh#ssh.decrypt} of
+ case {byte_size(EncryptedBuffer) >= 4, Ssh#ssh.decrypt} of
{true, 'chacha20-poly1305@openssh.com'} ->
<<EncryptedLen:4/binary, EncryptedRest/binary>> = EncryptedBuffer,
{Ssh1, PacketLenBin} = decrypt(Ssh, {length,EncryptedLen}),
@@ -1446,12 +1534,28 @@ payload(<<PacketLen:32, PaddingLen:8, Pa
Payload.
%%%----------------------------------------------------------------
+%% sign(SigData, SignAlg, Key, Opts) when is_list(SignAlg) ->
+%% sign(SigData, list_to_existing_atom(SignAlg), Key, Opts);
+
+sign(SigData, SignAlg, Key, #ssh{opts=Opts}) when is_atom(SignAlg) ->
+ case lists:member(SignAlg,
+ proplists:get_value(public_key,
+ ?GET_OPT(preferred_algorithms,Opts,[]))) of
+ true ->
+ {ok, sign(SigData, sha(SignAlg), Key)};
+ false ->
+ {error, unsupported_sign_alg}
+ end.
+
sign(SigData, HashAlg, #{algorithm:=dss} = Key) ->
mk_dss_sig(crypto:sign(dss, HashAlg, SigData, Key));
sign(SigData, HashAlg, #{algorithm:=SigAlg} = Key) ->
crypto:sign(SigAlg, HashAlg, SigData, Key);
sign(SigData, HashAlg, #'DSAPrivateKey'{} = Key) ->
mk_dss_sig(public_key:sign(SigData, HashAlg, Key));
+sign(SigData, HashAlg, Key = #'ECPrivateKey'{parameters = {namedCurve, Curve}})
+ when (Curve == ?'id-Ed25519') orelse (Curve == ?'id-Ed448') ->
+ public_key:sign(SigData, HashAlg, 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),
@@ -1465,7 +1569,11 @@ mk_dss_sig(DerSignature) ->
<<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>.
%%%----------------------------------------------------------------
-verify(PlainText, HashAlg, Sig, {_, #'Dss-Parms'{}} = Key, _) ->
+verify(PlainText, Alg, Sig, Key, Ssh) ->
+ do_verify(PlainText, sha(Alg), Sig, Key, Ssh).
+
+
+do_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}),
@@ -1473,7 +1581,7 @@ verify(PlainText, HashAlg, Sig, {_, #'D
_ ->
false
end;
-verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key, _) ->
+do_verify(PlainText, HashAlg, Sig, {#'ECPoint'{},_} = Key, _) when HashAlg =/= undefined ->
case Sig of
<<?UINT32(Rlen),R:Rlen/big-signed-integer-unit:8,
?UINT32(Slen),S:Slen/big-signed-integer-unit:8>> ->
@@ -1484,14 +1592,14 @@ verify(PlainText, HashAlg, Sig, {#'ECPoi
false
end;
-verify(PlainText, HashAlg, Sig, #'RSAPublicKey'{}=Key, #ssh{role = server,
- c_version = "SSH-2.0-OpenSSH_7."++_})
+do_verify(PlainText, HashAlg, Sig, #'RSAPublicKey'{}=Key, #ssh{role = server,
+ c_version = "SSH-2.0-OpenSSH_7."++_})
when HashAlg == sha256; HashAlg == sha512 ->
%% Public key signing bug in in OpenSSH >= 7.2
public_key:verify(PlainText, HashAlg, Sig, Key)
orelse public_key:verify(PlainText, sha, Sig, Key);
-verify(PlainText, HashAlg, Sig, Key, _) ->
+do_verify(PlainText, HashAlg, Sig, Key, _) ->
public_key:verify(PlainText, HashAlg, Sig, Key).
@@ -1741,7 +1849,7 @@ decrypt(#ssh{decrypt = 'chacha20-poly130
%% The length is already decrypted and used to divide the input
%% Check the mac (important that it is timing-safe):
PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, false),
- case crypto:equal_const_time(Ctag, crypto:mac(poly1305, PolyKey, <<AAD/binary,Ctext/binary>>)) of
+ case ssh_lib:comp(Ctag, crypto:mac(poly1305, PolyKey, <<AAD/binary,Ctext/binary>>)) of
true ->
%% MAC is ok, decode
IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>,
@@ -1988,38 +2096,27 @@ valid_key_sha_alg(private, #'RSAPrivateK
valid_key_sha_alg(public, {_, #'Dss-Parms'{}}, 'ssh-dss') -> true;
valid_key_sha_alg(private, #'DSAPrivateKey'{}, 'ssh-dss') -> true;
-valid_key_sha_alg(public, {ed_pub, ed25519,_}, 'ssh-ed25519') -> true;
-valid_key_sha_alg(private, {ed_pri, ed25519,_,_},'ssh-ed25519') -> true;
-valid_key_sha_alg(public, {ed_pub, ed448,_}, 'ssh-ed448') -> true;
-valid_key_sha_alg(private, {ed_pri, ed448,_,_}, 'ssh-ed448') -> true;
-
-valid_key_sha_alg(public, {#'ECPoint'{},{namedCurve,OID}}, Alg) when is_tuple(OID) ->
+valid_key_sha_alg(public, {#'ECPoint'{},{namedCurve,OID}}, Alg) ->
valid_key_sha_alg_ec(OID, Alg);
-valid_key_sha_alg(private, #'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) when is_tuple(OID) ->
+valid_key_sha_alg(private, #'ECPrivateKey'{parameters = {namedCurve,OID}}, Alg) ->
valid_key_sha_alg_ec(OID, Alg);
valid_key_sha_alg(_, _, _) -> false.
-
-valid_key_sha_alg_ec(OID, Alg) ->
- try
- Curve = public_key:oid2ssh_curvename(OID),
- Alg == list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(Curve))
- catch
- _:_ -> false
- end.
+
+
+valid_key_sha_alg_ec(OID, Alg) when is_tuple(OID) ->
+ {SshCurveType, _} = ssh_message:oid2ssh_curvename(OID),
+ Alg == binary_to_atom(SshCurveType);
+valid_key_sha_alg_ec(_, _) -> false.
+
-dialyzer({no_match, public_algo/1}).
public_algo(#'RSAPublicKey'{}) -> 'ssh-rsa'; % FIXME: Not right with draft-curdle-rsa-sha2
public_algo({_, #'Dss-Parms'{}}) -> 'ssh-dss';
-public_algo({ed_pub, ed25519,_}) -> 'ssh-ed25519';
-public_algo({ed_pub, ed448,_}) -> 'ssh-ed448';
public_algo({#'ECPoint'{},{namedCurve,OID}}) when is_tuple(OID) ->
- SshName = public_key:oid2ssh_curvename(OID),
- try list_to_existing_atom("ecdsa-sha2-" ++ binary_to_list(SshName))
- catch
- _:_ -> undefined
- end.
+ {SshCurveType, _} = ssh_message:oid2ssh_curvename(OID),
+ binary_to_atom(SshCurveType).
sha('ssh-rsa') -> sha;
@@ -2104,10 +2201,10 @@ parallell_gen_key(Ssh = #ssh{keyex_key =
Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}.
-generate_key(ecdh = Algorithm, Args) ->
- crypto:generate_key(Algorithm, Args);
-generate_key(Algorithm, Args) ->
- {Public,Private} = crypto:generate_key(Algorithm, Args),
+generate_key(ecdh, Args) ->
+ crypto:generate_key(ecdh, Args);
+generate_key(dh, [P,G,Sz2]) ->
+ {Public,Private} = crypto:generate_key(dh, [P, G, max(Sz2,?MIN_DH_KEY_SIZE)] ),
{crypto:bytes_to_integer(Public), crypto:bytes_to_integer(Private)}.
@@ -2161,6 +2258,14 @@ crypto_name_supported(Tag, CryptoName, S
same(Algs) -> [{client2server,Algs}, {server2client,Algs}].
+maybe_reset_sequence(snd, Ssh = #ssh{kex_strict_negotiated = true}) ->
+ {ok, Ssh#ssh{send_sequence = 0}};
+maybe_reset_sequence(rcv, Ssh = #ssh{kex_strict_negotiated = true}) ->
+ {ok, Ssh#ssh{recv_sequence = 0}};
+maybe_reset_sequence(_Dir, Ssh) ->
+ {ok, Ssh}.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Other utils
@@ -2187,14 +2292,14 @@ ssh_dbg_flags(raw_messages) -> ssh_dbg_f
ssh_dbg_flags(ssh_messages) -> ssh_dbg_flags(hello).
-ssh_dbg_on(alg) -> dbg:tpl(?MODULE,select_algorithm,4,x);
+ssh_dbg_on(alg) -> dbg:tpl(?MODULE,select_algorithm,5,x);
ssh_dbg_on(hello) -> dbg:tp(?MODULE,hello_version_msg,1,x),
dbg:tp(?MODULE,handle_hello_version,1,x);
ssh_dbg_on(raw_messages) -> ssh_dbg_on(hello);
ssh_dbg_on(ssh_messages) -> ssh_dbg_on(hello).
-ssh_dbg_off(alg) -> dbg:ctpl(?MODULE,select_algorithm,4);
+ssh_dbg_off(alg) -> dbg:ctpl(?MODULE,select_algorithm,5);
ssh_dbg_off(hello) -> dbg:ctpg(?MODULE,hello_version_msg,1),
dbg:ctpg(?MODULE,handle_hello_version,1);
ssh_dbg_off(raw_messages) -> ssh_dbg_off(hello);
@@ -2217,9 +2322,9 @@ ssh_dbg_format(hello, {call,{?MODULE,han
ssh_dbg_format(hello, {return_from,{?MODULE,handle_hello_version,1},_Ret}) ->
skip;
-ssh_dbg_format(alg, {call,{?MODULE,select_algorithm,[_,_,_,_]}}) ->
+ssh_dbg_format(alg, {call,{?MODULE,select_algorithm,[_,_,_,_,_]}}) ->
skip;
-ssh_dbg_format(alg, {return_from,{?MODULE,select_algorithm,4},{ok,Alg}}) ->
+ssh_dbg_format(alg, {return_from,{?MODULE,select_algorithm,5},{ok,Alg}}) ->
["Negotiated algorithms:\n",
wr_record(Alg)
];
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_xfer.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_xfer.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_xfer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2024. 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.
@@ -242,7 +242,7 @@ xf_request(XF, Op, Arg) ->
is_list(Arg) ->
?to_binary(Arg)
end,
- Size = 1+size(Data),
+ Size = 1+byte_size(Data),
ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]).
xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) ->
@@ -252,7 +252,7 @@ xf_send_reply(#ssh_xfer{cm = CM, channel
is_list(Arg) ->
?to_binary(Arg)
end,
- Size = 1 + size(Data),
+ Size = 1 + byte_size(Data),
ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]).
xf_send_name(XF, ReqId, Name, Attr) ->
@@ -290,7 +290,7 @@ xf_send_status(#ssh_xfer{cm = CM, channe
LangTag = "en",
ELen = length(ErrorMsg),
TLen = 2, %% length(LangTag),
- Size = 1 + 4 + 4 + 4+ELen + 4+TLen + size(Data),
+ Size = 1 + 4 + 4 + 4+ELen + 4+TLen + byte_size(Data),
ToSend = [<<?UINT32(Size), ?SSH_FXP_STATUS, ?UINT32(ReqId),
?UINT32(ErrorCode)>>,
<<?UINT32(ELen)>>, ErrorMsg,
@@ -300,13 +300,13 @@ xf_send_status(#ssh_xfer{cm = CM, channe
xf_send_attr(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, ReqId, Attr) ->
EncAttr = encode_ATTR(Vsn, Attr),
- ALen = size(EncAttr),
+ ALen = byte_size(EncAttr),
Size = 1 + 4 + ALen,
ToSend = [<<?UINT32(Size), ?SSH_FXP_ATTRS, ?UINT32(ReqId)>>, EncAttr],
ssh_connection:send(CM, Channel, ToSend).
xf_send_data(#ssh_xfer{cm = CM, channel = Channel}, ReqId, Data) ->
- DLen = size(Data),
+ DLen = byte_size(Data),
Size = 1 + 4 + 4+DLen,
ToSend = [<<?UINT32(Size), ?SSH_FXP_DATA, ?UINT32(ReqId), ?UINT32(DLen)>>,
Data],
@@ -660,7 +660,7 @@ encode_As(Vsn, [{AName, X}|As], Flags, A
encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_UIDGID,
[?uint32(X) | Acc]);
ownergroup when Vsn>=5 ->
- X1 = list_to_binary(integer_to_list(X)), % TODO: check owner and group
+ X1 = integer_to_binary(X), % TODO: check owner and group
encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_OWNERGROUP,
[?binary(X1) | Acc]);
permissions ->
@@ -736,13 +736,11 @@ decode_As(Vsn, [{AName, AField}|As], R,
decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
ownergroup when ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP, Flags),Vsn>=5 ->
<<?UINT32(Len), Bin:Len/binary, Tail2/binary>> = Tail,
- X = binary_to_list(Bin),
+ X = binary_to_integer(Bin),
decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
-
permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn>=5->
<<?UINT32(X), Tail2/binary>> = Tail,
decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
-
permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn=<3->
<<?UINT32(X), Tail2/binary>> = Tail,
R1 = setelement(AField, R, X),
@@ -757,7 +755,6 @@ decode_As(Vsn, [{AName, AField}|As], R,
_ -> unknown
end,
decode_As(Vsn, As, R1#ssh_xfer_attr { type=Type}, Flags, Tail2);
-
acmodtime when ?is_set(?SSH_FILEXFER_ATTR_ACMODTIME,Flags),Vsn=<3 ->
<<?UINT32(X), Tail2/binary>> = Tail,
decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
@@ -815,21 +812,21 @@ encode_name(Vsn, {{NameUC,LongNameUC},At
LongName = binary_to_list(unicode:characters_to_binary(LongNameUC)),
LNLen = length(LongName),
EncAttr = encode_ATTR(Vsn, Attr),
- ALen = size(EncAttr),
+ ALen = byte_size(EncAttr),
NewLen = Len + NLen + LNLen + 4 + 4 + ALen,
{[<<?UINT32(NLen)>>, Name, <<?UINT32(LNLen)>>, LongName, EncAttr], NewLen};
encode_name(Vsn, {NameUC,Attr}, Len) when Vsn =< 3 ->
Name = binary_to_list(unicode:characters_to_binary(NameUC)),
NLen = length(Name),
EncAttr = encode_ATTR(Vsn, Attr),
- ALen = size(EncAttr),
+ ALen = byte_size(EncAttr),
NewLen = Len + NLen*2 + 4 + 4 + ALen,
{[<<?UINT32(NLen)>>, Name, <<?UINT32(NLen)>>, Name, EncAttr], NewLen};
encode_name(Vsn, {NameUC,Attr}, Len) when Vsn >= 4 ->
Name = binary_to_list(unicode:characters_to_binary(NameUC)),
NLen = length(Name),
EncAttr = encode_ATTR(Vsn, Attr),
- ALen = size(EncAttr),
+ ALen = byte_size(EncAttr),
{[<<?UINT32(NLen)>>, Name, EncAttr],
Len + 4 + NLen + ALen}.
Index: otp-OTP-23.3.4.19/lib/ssh/test/.gitignore
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/.gitignore
@@ -0,0 +1,7 @@
+*COVER.html
+
+ssh_sftp_SUITE_data/test_data*
+
+property_test/ssh_eqc_client_server_dirs/system
+property_test/ssh_eqc_client_server_dirs/user
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_connection_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_connection_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_connection_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,11 +21,10 @@
%%
-module(ssh_connection_SUITE).
--include_lib("common_test/include/ct.hrl").
-include("ssh_connect.hrl").
-include("ssh_test_lib.hrl").
-
-
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
-export([
suite/0,
@@ -43,10 +42,24 @@
big_cat/1,
connect_sock_not_passive/1,
connect_sock_not_tcp/1,
+ connect2_invalid_options/1,
+ connect_invalid_port/1,
+ connect_invalid_timeout_0/1,
+ connect_invalid_timeout_1/1,
+ connect_invalid_options/1,
+ connect3_invalid_port/1,
+ connect3_invalid_options/1,
+ connect3_invalid_timeout_0/1,
+ connect3_invalid_timeout_1/1,
+ connect3_invalid_both/1,
+ connect4_invalid_two_0/1,
+ connect4_invalid_two_1/1,
+ connect4_invalid_two_2/1,
+ connect4_invalid_three/1,
connect_timeout/1,
daemon_sock_not_passive/1,
daemon_sock_not_tcp/1,
- do_interrupted_send/3,
+ do_interrupted_send/4,
do_simple_exec/1,
encode_decode_pty_opts/1,
exec_disabled/1,
@@ -69,6 +82,7 @@
send_after_exit/1,
simple_eval/1,
simple_exec/1,
+ simple_exec_more_data/1,
simple_exec_sock/1,
simple_exec_two_socks/1,
small_cat/1,
@@ -76,6 +90,8 @@
start_exec_direct_fun1_read_write/1,
start_exec_direct_fun1_read_write_advanced/1,
start_shell/1,
+ new_shell_dumb_term/1,
+ new_shell_xterm_term/1,
start_shell_pty/1,
start_shell_exec/1,
start_shell_exec_direct_fun/1,
@@ -83,6 +99,7 @@
start_shell_exec_direct_fun1_error_type/1,
start_shell_exec_direct_fun2/1,
start_shell_exec_direct_fun3/1,
+ start_shell_exec_direct_fun_more_data/1,
start_shell_exec_fun/1,
start_shell_exec_fun2/1,
start_shell_exec_fun3/1,
@@ -91,10 +108,12 @@
start_shell_sock_exec_fun/1,
start_subsystem_on_closed_channel/1,
stop_listener/1,
+ trap_exit_connect/1,
+ trap_exit_daemon/1,
+ handler_down_before_open/1,
ssh_exec_echo/2 % called as an MFA
]).
--define(SSH_DEFAULT_PORT, 22).
-define(EXEC_TIMEOUT, 10000).
%%--------------------------------------------------------------------
@@ -117,6 +136,10 @@ all() ->
exec_disabled,
exec_shell_disabled,
start_shell,
+ new_shell_dumb_term,
+ new_shell_xterm_term,
+ trap_exit_connect,
+ trap_exit_daemon,
start_shell_pty,
start_shell_exec,
start_shell_exec_fun,
@@ -125,6 +148,7 @@ all() ->
start_shell_exec_direct_fun,
start_shell_exec_direct_fun2,
start_shell_exec_direct_fun3,
+ start_shell_exec_direct_fun_more_data,
start_shell_exec_direct_fun1_error,
start_shell_exec_direct_fun1_error_type,
start_exec_direct_fun1_read_write,
@@ -134,6 +158,20 @@ all() ->
start_shell_sock_daemon_exec_multi,
encode_decode_pty_opts,
connect_sock_not_tcp,
+ connect2_invalid_options,
+ connect_invalid_port,
+ connect_invalid_options,
+ connect_invalid_timeout_0,
+ connect_invalid_timeout_1,
+ connect3_invalid_port,
+ connect3_invalid_options,
+ connect3_invalid_timeout_0,
+ connect3_invalid_timeout_1,
+ connect3_invalid_both,
+ connect4_invalid_two_0,
+ connect4_invalid_two_1,
+ connect4_invalid_two_2,
+ connect4_invalid_three,
connect_timeout,
daemon_sock_not_tcp,
gracefull_invalid_version,
@@ -144,13 +182,15 @@ all() ->
stop_listener,
no_sensitive_leak,
start_subsystem_on_closed_channel,
- max_channels_option
+ max_channels_option,
+ handler_down_before_open
].
groups() ->
[{openssh, [], payload() ++ ptty() ++ sock()}].
payload() ->
[simple_exec,
+ simple_exec_more_data,
simple_exec_sock,
simple_exec_two_socks,
small_cat,
@@ -180,9 +220,9 @@ end_per_suite(_Config) ->
%%--------------------------------------------------------------------
init_per_group(openssh, Config) ->
- case ssh_test_lib:gen_tcp_connect("localhost", 22, []) of
+ case ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []) of
{error,econnrefused} ->
- {skip,"No openssh deamon (econnrefused)"};
+ {skip,"No openssh daemon (econnrefused)"};
{ok, Socket} ->
gen_tcp:close(Socket),
ssh_test_lib:openssh_sanity_check(Config)
@@ -199,7 +239,7 @@ init_per_testcase(_TestCase, Config) ->
%% end_per_testcase will be run!
end_per_testcase(any, Config),
ssh:start(),
- Config.
+ ssh_test_lib:verify_sanity_check(Config).
end_per_testcase(_TestCase, _Config) ->
ssh:stop().
@@ -208,15 +248,20 @@ end_per_testcase(_TestCase, _Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
simple_exec(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
do_simple_exec(ConnectionRef).
+simple_exec_more_data(Config) when is_list(Config) ->
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
+ %% more data received, SSH window adjust needs to be sent by client
+ do_simple_exec(ConnectionRef, 60000).
%%--------------------------------------------------------------------
simple_exec_sock(_Config) ->
- {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}]),
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, [{active,false}]),
+ {ok, ConnectionRef} = ssh:connect(Sock, [{save_accepted_host, false},
+ {silently_accept_hosts, true},
+ {user_interaction, true}
+ ]),
do_simple_exec(ConnectionRef).
%%--------------------------------------------------------------------
@@ -225,9 +270,10 @@ simple_exec_two_socks(_Config) ->
F = fun() ->
spawn_link(
fun() ->
- {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}]),
+ {ok, Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, [{active,false}]),
+ {ok, ConnectionRef} = ssh:connect(Sock, [{save_accepted_host, false},
+ {silently_accept_hosts, true},
+ {user_interaction, true}]),
Parent ! {self(),do_simple_exec(ConnectionRef)}
end)
end,
@@ -241,9 +287,144 @@ simple_exec_two_socks(_Config) ->
end.
%%--------------------------------------------------------------------
+connect2_invalid_options(_Config) ->
+ {error, invalid_options} = ssh:connect(bogus_socket, {bad, option}).
+
+connect3_invalid_port(_Config) ->
+ {error, invalid_port} = ssh:connect(bogus_host, noport, [{key, value}]).
+
+connect3_invalid_options(_Config) ->
+ {error, invalid_options} = ssh:connect(bogus_host, 1337, bad_options).
+
+connect3_invalid_timeout_0(_Config) ->
+ {error, invalid_timeout} =
+ ssh:connect(bogus_socket, [{key, value}], short).
+
+connect3_invalid_timeout_1(_Config) ->
+ {error, invalid_timeout} =
+ ssh:connect(bogus_socket, [{key, value}], -1).
+
+connect3_invalid_both(_Config) ->
+ %% The actual reason is implementation dependent.
+ {error, _Reason} =
+ ssh:connect(bogus, no_list_or_port, no_list_or_timeout).
+
+connect_invalid_port(Config) ->
+ {Pid, Host, _Port, UserDir} = daemon_start(Config),
+
+ {error, invalid_port} =
+ ssh:connect(Host, undefined,
+ [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}],
+ infinity),
+
+ ssh:stop_daemon(Pid).
+
+connect_invalid_timeout_0(Config) ->
+ {Pid, Host, Port, UserDir} = daemon_start(Config),
+
+ {error, invalid_timeout} =
+ ssh:connect(Host, Port,
+ [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}],
+ longer),
+
+ ssh:stop_daemon(Pid).
+
+connect_invalid_timeout_1(Config) ->
+ {Pid, Host, Port, UserDir} = daemon_start(Config),
+
+ {error, invalid_timeout} =
+ ssh:connect(Host, Port,
+ [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}],
+ -1),
+
+ ssh:stop_daemon(Pid).
+
+connect_invalid_options(Config) ->
+ {Pid, Host, Port, _UserDir} = daemon_start(Config),
+
+ {error, invalid_options} =
+ ssh:connect(Host, Port,
+ {user, "foo"},
+ infinity),
+
+ ssh:stop_daemon(Pid).
+
+%% Two out three arguments incorrect. Three possibilities.
+connect4_invalid_two_0(Config) ->
+ {Pid, Host, _Port, _UserDir} = daemon_start(Config),
+
+ %% Actual error implementation dependent
+ {error, _} =
+ ssh:connect(Host, noport,
+ {user, "foo"},
+ infinity),
+
+ ssh:stop_daemon(Pid).
+
+connect4_invalid_two_1(Config) ->
+ {Pid, Host, Port, _UserDir} = daemon_start(Config),
+
+ %% Actual error implementation dependent
+ {error, _} =
+ ssh:connect(Host, Port,
+ {user, "foo"},
+ short),
+
+ ssh:stop_daemon(Pid).
+
+connect4_invalid_two_2(Config) ->
+ {Pid, Host, _Port, _UserDir} = daemon_start(Config),
+
+ %% Actual error implementation dependent
+ {error, _} =
+ ssh:connect(Host, newport,
+ [{user, "foo"}],
+ -1),
+
+ ssh:stop_daemon(Pid).
+
+%% All three args incorrect
+connect4_invalid_three(Config) ->
+ {Pid, Host, _Port, _UserDir} = daemon_start(Config),
+
+ %% Actual error implementation dependent
+ {error, _} =
+ ssh:connect(Host, teleport,
+ {user, "foo"},
+ fortnight),
+
+ ssh:stop_daemon(Pid).
+
+daemon_start(Config) ->
+ 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),
+
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {exec, fun ssh_exec_echo/1}]),
+ {Pid, Host, Port, UserDir}.
+
+%%--------------------------------------------------------------------
connect_sock_not_tcp(_Config) ->
{ok,Sock} = gen_udp:open(0, []),
- {error, not_tcp_socket} = ssh:connect(Sock, []),
+ {error, not_tcp_socket} = ssh:connect(Sock, [{save_accepted_host, false},
+ {silently_accept_hosts, true},
+ {user_interaction, true}]),
gen_udp:close(Sock).
%%--------------------------------------------------------------------
@@ -263,21 +444,21 @@ daemon_sock_not_tcp(_Config) ->
%%--------------------------------------------------------------------
connect_sock_not_passive(_Config) ->
- {ok,Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, []),
+ {ok,Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []),
{error, not_passive_mode} = ssh:connect(Sock, [{save_accepted_host, false},
- {silently_accept_hosts, true}]),
+ {silently_accept_hosts, true},
+ {user_interaction, true}]),
gen_tcp:close(Sock).
%%--------------------------------------------------------------------
daemon_sock_not_passive(_Config) ->
- {ok,Sock} = ssh_test_lib:gen_tcp_connect("localhost", ?SSH_DEFAULT_PORT, []),
+ {ok,Sock} = ssh_test_lib:gen_tcp_connect(?SSH_DEFAULT_PORT, []),
{error, not_passive_mode} = ssh:daemon(Sock),
gen_tcp:close(Sock).
%%--------------------------------------------------------------------
small_cat(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"cat", infinity),
@@ -365,8 +546,7 @@ big_cat(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
send_after_exit(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
Data = <<"I like spaghetti squash">>,
@@ -430,8 +610,7 @@ encode_decode_pty_opts(_Config) ->
%%--------------------------------------------------------------------
ptty_alloc_default(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
Expect = case proplists:get_value(ptty_supported, Config) of
true -> success;
@@ -442,8 +621,7 @@ ptty_alloc_default(Config) when is_list(
%%--------------------------------------------------------------------
ptty_alloc(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
Expect = case proplists:get_value(ptty_supported, Config) of
true -> success;
@@ -456,8 +634,7 @@ ptty_alloc(Config) when is_list(Config)
%%--------------------------------------------------------------------
ptty_alloc_pixel(Config) when is_list(Config) ->
- ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
Expect = case proplists:get_value(ptty_supported, Config) of
true -> success;
@@ -468,15 +645,24 @@ ptty_alloc_pixel(Config) when is_list(Co
ssh:close(ConnectionRef).
%%--------------------------------------------------------------------
-small_interrupted_send(Config) ->
+%%- small_interrupted_send is interrupted by ssh_echo_server which is
+%% done with transferring data towards client and terminates the
+%% channel (this results with {error, closed} return value from
+%% ssh_connection:send on the client side)
+%%- interrupted_send is interrupted when ssh_echo_server ran
+%% out of ssh data window and closed channel
+small_interrupted_send(Config) ->
K = 1024,
- M = K*K,
- do_interrupted_send(Config, 10*M, 4*K).
+ SendSize = 10 * K * K,
+ EchoSize = 4 * K,
+ do_interrupted_send(Config, SendSize, EchoSize, {error, closed}).
interrupted_send(Config) ->
- M = 1024*1024,
- do_interrupted_send(Config, 10*M, 4*M).
+ K = 1024,
+ SendSize = 10 * K * K,
+ EchoSize = 4 * K * K,
+ do_interrupted_send(Config, SendSize, EchoSize, ok).
-do_interrupted_send(Config, SendSize, EchoSize) ->
+do_interrupted_send(Config, SendSize, EchoSize, SenderResult) ->
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),
@@ -486,7 +672,6 @@ do_interrupted_send(Config, SendSize, Ec
{user_dir, UserDir},
{password, "morot"},
{subsystems, [{"echo_n",EchoSS_spec}]}]),
-
ct:log("~p:~p connect", [?MODULE,?LINE]),
ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -494,32 +679,28 @@ do_interrupted_send(Config, SendSize, Ec
{user_interaction, false},
{user_dir, UserDir}]),
ct:log("~p:~p connected", [?MODULE,?LINE]),
-
%% build big binary
Data = << <<X:32>> || X <- lists:seq(1,SendSize div 4)>>,
-
%% expect remote end to send us EchoSize back
<<ExpectedData:EchoSize/binary, _/binary>> = Data,
-
%% Spawn listener. Otherwise we could get a deadlock due to filled buffers
Parent = self(),
ResultPid = spawn(
fun() ->
ct:log("~p:~p open channel",[?MODULE,?LINE]),
{ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
- ct:log("~p:~p start subsystem", [?MODULE,?LINE]),
+ ct:log("~p:~p start ssh subsystem", [?MODULE,?LINE]),
case ssh_connection:subsystem(ConnectionRef, ChannelId, "echo_n", infinity) of
success ->
Parent ! {self(), channelId, ChannelId},
-
- Result =
+ Result =
try collect_data(ConnectionRef, ChannelId, EchoSize)
of
- ExpectedData ->
+ ExpectedData ->
ct:log("~p:~p got expected data",[?MODULE,?LINE]),
ok;
Other ->
- ct:log("~p:~p unexpect: ~p", [?MODULE,?LINE,Other]),
+ ct:log("~p:~p unexpected: ~p", [?MODULE,?LINE,Other]),
{fail,"unexpected result in listener"}
catch
Class:Exception ->
@@ -530,40 +711,40 @@ do_interrupted_send(Config, SendSize, Ec
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} ->
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),
+ ct:log("SenderPid = ~p", [SenderPid]),
receive
{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),
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]),
+ {SenderPid, SenderResult} ->
+ ct:log("~p:~p ~p - That's what we expect :)",
+ [?MODULE,?LINE, SenderResult]),
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]),
+ ct:log("~p:~p ~p - That's what we expect, "
+ "but client channel handler has not reported yet",
+ [?MODULE,?LINE, SenderResult]),
receive
{ResultPid, result, Result} ->
ct:log("~p:~p Now got the result: ~p", [?MODULE,?LINE,Result]),
@@ -574,7 +755,6 @@ do_interrupted_send(Config, SendSize, Ec
ct:log("~p:~p Got an unexpected msg ~p",[?MODULE,?LINE,Msg]),
{fail, "Un-expected msg"}
end;
-
Msg ->
ct:log("~p:~p Got unexpected ~p",[?MODULE,?LINE,Msg]),
{fail, "Unexpected msg"}
@@ -602,6 +782,76 @@ start_shell(Config) when is_list(Config)
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
+%%--------------------------------------------------------------------
+new_shell_dumb_term(Config) when is_list(Config) ->
+ new_shell_helper(#{term => "dumb",
+ cmds => ["one_atom_please.\n",
+ "\^R" % attempt to trigger history search
+ ],
+ exp_output =>
+ [<<"Enter command\r\n">>,
+ <<"1> ">>,
+ <<"one_atom_please.\r\n">>,
+ <<"{simple_eval,one_atom_please}\r\n">>,
+ <<"2> ">>],
+ unexp_output =>
+ [<<"\e[;1;4msearch:\e[0m ">>]},
+ Config).
+
+%%--------------------------------------------------------------------
+new_shell_xterm_term(Config) when is_list(Config) ->
+ new_shell_helper(#{term => "xterm",
+ cmds => ["one_atom_please.\n",
+ "\^R" % attempt to trigger history search
+ ],
+ exp_output =>
+ [<<"Enter command\r\n">>,
+ <<"1> ">>,
+ <<"one_atom_please.\r\n\e[1022D\e[1B">>,
+ <<"{simple_eval,one_atom_please}\r\n">>,
+ <<"2> ">>,
+ <<"\e[3D\e[J">>,
+ <<"\e[;1;4msearch:\e[0m ">>,
+ <<"\r\n one_atom_please.">>]},
+ Config).
+
+new_shell_helper(#{term := Term, cmds := Cmds,
+ exp_output := ExpectedOutput} = Settings, Config) ->
+ UnexpectedOutput = maps:get(unexp_output, Settings, []),
+ 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),
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {subsystems, []},
+ {keepalive, true},
+ {nodelay, true},
+ {shell, fun(U, H) ->
+ start_our_shell2(U, H)
+ end}
+ ]),
+ ConnectionRef = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_dir, UserDir}]),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success =
+ ssh_connection:ptty_alloc(ConnectionRef, ChannelId,
+ [{term, Term}, {hight, 24}, {width,1023}],
+ infinity),
+ ok = ssh_connection:shell(ConnectionRef,ChannelId),
+ [ssh_connection:send(ConnectionRef, ChannelId, C) || C <- Cmds],
+ GetTuple = fun(Bin) -> {ssh_cm, ConnectionRef, {data,ChannelId,0,Bin}} end,
+ Msgs = [GetTuple(B) || B <- ExpectedOutput],
+ expected = ssh_test_lib:receive_exec_result(Msgs),
+ UnexpectedMsgs = [GetTuple(C) || C <- UnexpectedOutput],
+ flush_msgs(UnexpectedMsgs),
+
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
+
%%-------------------------------------------------------------------
start_shell_pty(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
@@ -773,6 +1023,24 @@ start_shell_exec_direct_fun3(Config) ->
"testing", <<"echo foo testing">>, 0,
Config).
+start_shell_exec_direct_fun_more_data(Config) ->
+ N = 60000,
+ ExpectedBin = <<"testing\n">>,
+ ReceiveFun =
+ fun(ConnectionRef, ChannelId, _Expect, _ExpectType) ->
+ receive_bytes(ConnectionRef, ChannelId,
+ N * byte_size(ExpectedBin), 0)
+ end,
+ do_start_shell_exec_fun({direct,
+ fun(_Cmd) ->
+ {ok,
+ [io_lib:format("testing~n",[]) ||
+ _ <- lists:seq(1, N)]}
+ end},
+ "not_relevant", <<"not_used\n">>, 0,
+ ReceiveFun,
+ Config).
+
start_shell_exec_direct_fun1_error(Config) ->
do_start_shell_exec_fun({direct, fun(_Cmd) -> {error, {bad}} end},
"testing", <<"**Error** {bad}">>, 1,
@@ -907,6 +1175,28 @@ simple_eval(Inp) -> {simple_eval,Inp}.
do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, Config) ->
+ DefaultReceiveFun =
+ fun(ConnectionRef, ChannelId, _Expect, _ExpectType) ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId, ExpectType, Expect}} ->
+ ok
+ after 5000 ->
+ receive
+ Other ->
+ ct:log("Received other:~n~p~nExpected: ~p~n",
+ [Other,
+ {ssh_cm, ConnectionRef,
+ {data, ChannelId, ExpectType, Expect}}]),
+ %% {data, '_ChannelId', ExpectType, Expect}}]),
+ ct:fail("Unexpected response")
+ after 0 ->
+ ct:fail("Exec Timeout")
+ end
+ end
+ end,
+ do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, DefaultReceiveFun, Config).
+
+do_start_shell_exec_fun(Fun, Command, Expect, ExpectType, ReceiveFun, Config) ->
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),
@@ -922,26 +1212,64 @@ do_start_shell_exec_fun(Fun, Command, Ex
{user_interaction, true},
{user_dir, UserDir}]),
- {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+ {ok, ChannelId} = ssh_connection:session_channel(ConnectionRef, infinity),
+ success = ssh_connection:exec(ConnectionRef, ChannelId, Command, infinity),
+ ReceiveFun(ConnectionRef, ChannelId, Expect, ExpectType),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid).
- success = ssh_connection:exec(ConnectionRef, ChannelId0, Command, infinity),
+%%--------------------------------------------------------------------
+%% Issue GH-8223
+trap_exit_connect(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey),
+ 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"}]),
+ %% Fake an EXIT message
+ ExitMsg = {'EXIT', self(), make_ref()},
+ self() ! ExitMsg,
+ {ok, ConnectionRef} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, true},
+ {user_dir, UserDir}]),
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid),
+
+ %% Ensure the EXIT message is still there
receive
- {ssh_cm, ConnectionRef, {data, _ChannelId, ExpectType, Expect}} ->
- ok
- after 5000 ->
- receive
- Other ->
- ct:log("Received other:~n~p~nExpected: ~p~n",
- [Other, {ssh_cm, ConnectionRef, {data, '_ChannelId', ExpectType, Expect}} ]),
- ct:fail("Unexpected response")
- after 0 ->
- ct:fail("Exec Timeout")
- end
- end,
+ ExitMsg -> ok
+ after 0 ->
+ ct:fail("No EXIT message")
+ end.
- ssh:close(ConnectionRef),
- ssh:stop_daemon(Pid).
+%%--------------------------------------------------------------------
+%% Issue GH-8223
+trap_exit_daemon(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ UserDir = filename:join(PrivDir, nopubkey),
+ file:make_dir(UserDir),
+ SysDir = proplists:get_value(data_dir, Config),
+
+ %% Fake an EXIT message
+ ExitMsg = {'EXIT', self(), make_ref()},
+ self() ! ExitMsg,
+
+ {ok, DaemonRef} = ssh:daemon(0, [{system_dir, SysDir},
+ {user_dir, UserDir}]),
+ ssh:stop_daemon(DaemonRef),
+
+ %% Ensure the EXIT message is still there
+ receive
+ ExitMsg -> ok
+ after 0 ->
+ ct:fail("No EXIT message")
+ end.
%%--------------------------------------------------------------------
start_shell_sock_exec_fun(Config) when is_list(Config) ->
@@ -957,6 +1285,7 @@ start_shell_sock_exec_fun(Config) when i
{ok, Sock} = ssh_test_lib:gen_tcp_connect(Host, Port, [{active,false}]),
{ok,ConnectionRef} = ssh:connect(Sock, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
@@ -990,7 +1319,7 @@ start_shell_sock_daemon_exec(Config) ->
%% A server tcp-contects to the listening socket and starts an ssh daemon
spawn_link(fun() ->
- {ok,Ss} = ssh_test_lib:gen_tcp_connect("localhost", Port, [{active,false}]),
+ {ok,Ss} = ssh_test_lib:gen_tcp_connect(Port, [{active,false}]),
{ok, _Pid} = ssh:daemon(Ss, [{system_dir, SysDir},
{user_dir, UserDir},
{password, "morot"},
@@ -1000,6 +1329,7 @@ start_shell_sock_daemon_exec(Config) ->
%% The client accepts the tcp connection from the server and ssh-connects to it
{ok,Sc} = gen_tcp:accept(Sl),
{ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
@@ -1041,7 +1371,7 @@ start_shell_sock_daemon_exec_multi(Confi
%% Servers tcp-contects to the listening socket and starts an ssh daemon
Pids =
[spawn_link(fun() ->
- {ok,Ss} = ssh_test_lib:gen_tcp_connect("localhost", Port, [{active,false}]),
+ {ok,Ss} = ssh_test_lib:gen_tcp_connect(Port, [{active,false}]),
{ok, _Pid} = ssh:daemon(Ss, DaemonOpts)
end)
|| _ <- lists:seq(1,NumConcurent)],
@@ -1052,6 +1382,7 @@ start_shell_sock_daemon_exec_multi(Confi
[begin
{ok,Sc} = gen_tcp:accept(Sl),
{ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
@@ -1073,7 +1404,7 @@ start_shell_sock_daemon_exec_multi(Confi
receive
{ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"echo testing\n">>}} ->
Parent ! {answer_received,self()},
- ct:log("~p:~p: recevied result on connection ~p", [?MODULE,?LINE,ConnectionRef])
+ ct:log("~p:~p: received result on connection ~p", [?MODULE,?LINE,ConnectionRef])
after 5000 ->
ct:fail("Exec Timeout")
end
@@ -1182,6 +1513,8 @@ gracefull_invalid_long_start_no_nl(Confi
end.
kex_error(Config) ->
+ #{level := Level} = logger:get_primary_config(),
+ ok = logger:set_primary_config(level, debug),
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),
@@ -1202,6 +1535,10 @@ kex_error(Config) ->
ok % Other msg
end,
self()),
+ Cleanup = fun() ->
+ ok = logger:remove_handler(kex_error),
+ ok = logger:set_primary_config(level, Level)
+ end,
try
ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user, "foo"},
@@ -1219,7 +1556,7 @@ kex_error(Config) ->
%% ok
receive
{Ref, ErrMsgTxt} ->
- ok = logger:remove_handler(kex_error),
+ Cleanup(),
ct:log("ErrMsgTxt = ~n~s", [ErrMsgTxt]),
Lines = lists:map(fun string:trim/1, string:tokens(ErrMsgTxt, "\n")),
OK = (lists:all(fun(S) -> lists:member(S,Lines) end,
@@ -1237,18 +1574,19 @@ kex_error(Config) ->
ct:fail("unexpected error text msg", [])
end
after 20000 ->
- ok = logger:remove_handler(kex_error),
+ Cleanup(),
ct:fail("timeout", [])
end;
error:{badmatch,{error,_}} ->
- ok = logger:remove_handler(kex_error),
+ Cleanup(),
ct:fail("unexpected error msg", [])
end.
stop_listener(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
- UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
+ %% to make sure we don't use public-key-auth
+ UserDir = filename:join(PrivDir, nopubkey),
file:make_dir(UserDir),
SysDir = proplists:get_value(data_dir, Config),
@@ -1257,17 +1595,20 @@ stop_listener(Config) when is_list(Confi
{password, "morot"},
{exec, fun ssh_exec_echo/1}]),
- ConnectionRef0 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
- {user, "foo"},
- {password, "morot"},
- {user_interaction, true},
- {user_dir, UserDir}]),
-
- {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef0, infinity),
+ ConnectionRef0 = ssh_test_lib:connect(Host, Port,
+ [{silently_accept_hosts, true},
+ {user, "foo"},
+ {password, "morot"},
+ {user_interaction, false},
+ {user_dir, UserDir}]),
+ {ok, ChannelId0} =
+ ssh_connection:session_channel(ConnectionRef0, infinity),
ssh:stop_listener(Host, Port),
{error, _} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
@@ -1275,7 +1616,8 @@ stop_listener(Config) when is_list(Confi
success = ssh_connection:exec(ConnectionRef0, ChannelId0,
"testing", infinity),
receive
- {ssh_cm, ConnectionRef0, {data, ChannelId0, 0, <<"echo testing\n">>}} ->
+ {ssh_cm, ConnectionRef0,
+ {data, ChannelId0, 0, <<"echo testing\n">>}} ->
ok
after 5000 ->
ct:fail("Exec Timeout")
@@ -1286,12 +1628,14 @@ stop_listener(Config) when is_list(Confi
{password, "potatis"},
{exec, fun ssh_exec_echo/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}]),
+ 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},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, true},
@@ -1333,7 +1677,7 @@ no_sensitive_leak(Config) ->
%% Install the test handler:
Hname = no_sensitive_leak,
ok = ssh_log_h:add_fun(Hname,
- fun(#{msg := {report, #{report := Rep}}}, Pid) ->
+ fun(#{msg := {report,#{report := Rep}}}, Pid) ->
true = (erlang:process_info(Pid, status) =/= undefined), % remove handler if we are dead
Pid ! {Ref,ssh_log_h:sensitive_in_opt(Rep),Rep};
(_,Pid) ->
@@ -1354,8 +1698,9 @@ no_sensitive_leak(Config) ->
{user_dir, UserDir}]),
%% Kill acceptor to make it restart:
[true|_] =
- [exit(Pacc,kill) || {{server,ssh_system_sup,_,_,default},P1,supervisor,_} <- supervisor:which_children(sshd_sup),
- {{ssh_acceptor_sup,_,_,default},Pacc,supervisor,_} <- supervisor:which_children(P1)],
+ [exit(Pacc,kill) || {{ssh_system_sup,_},P1,supervisor,_} <- supervisor:which_children(sshd_sup),
+ {{ssh_acceptor_sup,_},P2,supervisor,_} <- supervisor:which_children(P1),
+ {{ssh_acceptor_sup,_},Pacc,worker,_} <- supervisor:which_children(P2)],
%% Remove the test handler and reset the logger level:
timer:sleep(500),
@@ -1486,47 +1831,206 @@ max_channels_option(Config) when is_list
ssh:close(ConnectionRef),
ssh:stop_daemon(Pid).
+handler_down_before_open(Config) ->
+ %% Start echo subsystem with a delay in init() - until a signal is received
+ %% One client opens a channel on the connection
+ %% the other client requests the echo subsystem on the second channel and then immediately goes down
+ %% the test monitors the client and when receiving 'DOWN' signals 'echo' to proceed
+ %% a) there should be no crash after 'channel-open-confirmation'
+ %% b) there should be proper 'channel-close' exchange
+ %% c) the 'exec' channel should not be affected after the 'echo' channel goes down
+ 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(),
+ EchoSS_spec = {ssh_echo_server, [8, [{dbg, true}, {parent, Parent}]]},
+ {Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {password, "morot"},
+ {exec, fun ssh_exec_echo/1},
+ {subsystems, [{"echo_n",EchoSS_spec}]}]),
+ 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("~p:~p connected", [?MODULE,?LINE]),
+
+ ExecChannelPid =
+ spawn(
+ fun() ->
+ {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
+
+ %% This is to get peer's connection handler PID ({conn_peer ...} below) and suspend it
+ {ok, ChannelId1} = ssh_connection:session_channel(ConnectionRef, infinity),
+ ssh_connection:subsystem(ConnectionRef, ChannelId1, "echo_n", infinity),
+ ssh_connection:close(ConnectionRef, ChannelId1),
+ receive
+ {ssh_cm, ConnectionRef, {closed, 1}} -> ok
+ end,
+
+ Parent ! {self(), channelId, ChannelId0},
+ Result = receive
+ cmd ->
+ ct:log("~p:~p Channel ~p executing", [?MODULE, ?LINE, ChannelId0]),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0, "testing", infinity),
+ Expect = <<"echo testing\n">>,
+ ExpSz = size(Expect),
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0,
+ <<Expect:ExpSz/binary, _/binary>>}} = R ->
+ ct:log("~p:~p Got expected ~p",[?MODULE,?LINE, R]),
+ ok;
+ Other ->
+ ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
+ [?MODULE,?LINE, Other, {ssh_cm, ConnectionRef,
+ {data, ChannelId0, 0, Expect}}]),
+ {fail, "Unexpected data"}
+ after 5000 ->
+ {fail, "Exec Timeout"}
+ end;
+ stop -> {fail, "Stopped"}
+ end,
+ Parent ! {self(), Result}
+ end),
+ try
+ receive
+ {ExecChannelPid, channelId, ExId} ->
+ ct:log("~p:~p Channel that should stay: ~p pid ~p",
+ [?MODULE, ?LINE, ExId, ExecChannelPid]),
+ %% This is sent by the echo subsystem as a reaction to channel1 above
+ ConnPeer = receive {conn_peer, CM} -> CM end,
+ %% The sole purpose of this channel is to go down
+ %% before the opening procedure is complete
+ DownChannelPid = spawn(
+ fun() ->
+ ct:log("~p:~p open channel (incomplete)",[?MODULE,?LINE]),
+ Parent ! {self(), channelId, ok},
+ %% This is to prevent the peer from answering our 'channel-open' in time
+ sys:suspend(ConnPeer),
+ {ok, _} = ssh_connection:session_channel(ConnectionRef, infinity)
+ end),
+ MonRef = erlang:monitor(process, DownChannelPid),
+ receive
+ {DownChannelPid, channelId, ok} ->
+ ct:log("~p:~p Channel handler that won't continue: pid ~p",
+ [?MODULE, ?LINE, DownChannelPid]),
+ ensure_channels(ConnectionRef, 2),
+ channel_down_sequence(DownChannelPid, ExecChannelPid,
+ ExId, MonRef, ConnectionRef, ConnPeer)
+ end
+ end,
+ ensure_channels(ConnectionRef, 0)
+ after
+ ssh:close(ConnectionRef),
+ ssh:stop_daemon(Pid)
+ end.
+
+ensure_channels(ConnRef, Expected) ->
+ {ok, ChannelList} = ssh_connection_handler:info(ConnRef),
+ do_ensure_channels(ConnRef, Expected, length(ChannelList)).
+
+do_ensure_channels(_ConnRef, NumExpected, NumExpected) ->
+ ok;
+do_ensure_channels(ConnRef, NumExpected, _ChannelListLen) ->
+ ct:sleep(100),
+ {ok, ChannelList} = ssh_connection_handler:info(ConnRef),
+ do_ensure_channels(ConnRef, NumExpected, length(ChannelList)).
+
+channel_down_sequence(DownChannelPid, ExecChannelPid, ExecChannelId, MonRef, ConnRef, Peer) ->
+ ct:log("~p:~p sending order to ~p to go down", [?MODULE, ?LINE, DownChannelPid]),
+ exit(DownChannelPid, die),
+ receive {'DOWN', MonRef, _, _, _} -> ok end,
+ ct:log("~p:~p order executed, sending order to ~p to proceed", [?MODULE, ?LINE, Peer]),
+ %% Resume the peer connection to let it clean up among its channels
+ sys:resume(Peer),
+ ensure_channels(ConnRef, 1),
+ ExecChannelPid ! cmd,
+ try
+ receive
+ {ExecChannelPid, ok} ->
+ ct:log("~p:~p expected exec result: ~p", [?MODULE, ?LINE, ok]),
+ ok;
+ {ExecChannelPid, Result} ->
+ ct:log("~p:~p Unexpected exec result: ~p", [?MODULE, ?LINE, Result]),
+ {fail, "Unexpected exec result"}
+ after 5000 ->
+ {fail, "Exec result timeout"}
+ end
+ after
+ ssh_connection:close(ConnRef, ExecChannelId)
+ end.
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
-
do_simple_exec(ConnectionRef) ->
+ do_simple_exec(ConnectionRef, 1).
+
+do_simple_exec(ConnectionRef, N) ->
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
- success = ssh_connection:exec(ConnectionRef, ChannelId0,
- "echo testing", infinity),
- %% receive response to input
- receive
- {ssh_cm, ConnectionRef, {data, ChannelId0, 0, <<"testing\n">>}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ Cmd = "yes testing | head -n " ++ integer_to_list(N),
+ ct:log("Cmd to be invoked over SSH shell: ~p", [Cmd]),
+ success = ssh_connection:exec(ConnectionRef, ChannelId0, Cmd, infinity),
+ ExpectedBin = <<"testing\n">>,
+ case N of
+ 1 ->
+ %% receive response to input
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, ExpectedBin}} ->
+ ok
+ after
+ 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end;
+ _ ->
+ receive_bytes(ConnectionRef, ChannelId0, N * byte_size(ExpectedBin), 0)
end,
-
%% receive close messages
- receive
- {ssh_cm, ConnectionRef, {eof, ChannelId0}} ->
- ok
+ CloseMessages =
+ [{ssh_cm, ConnectionRef, {eof, ChannelId0}},
+ {ssh_cm, ConnectionRef, {closed, ChannelId0}}],
+ Timeout = 10000,
+ [receive
+ M ->
+ ct:log("Received M = ~w", [M]),
+ ok
+ after
+ Timeout ->
+ ct:log("M = ~w not found !", [M]),
+ ct:log("Messages in queue =~n~p", [process_info(self(), messages)]),
+ ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end || M <- CloseMessages],
+ receive
+ %% 141 is exit status of `yes testing | head -n 1` on tcsh
+ %% other shells return 0
+ ExitMsg = {ssh_cm, ConnectionRef, {exit_status, ChannelId0, ExitStatus}}
+ when ExitStatus == 0; ExitStatus == 141 ->
+ ct:log("Received M = ~w", [ExitMsg]),
+ ok
after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ Timeout ->
+ ct:log("Acceptable exit status not received"),
+ ct:log("Messages in queue =~n~p", [process_info(self(), messages)]),
+ ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end,
- receive
- {ssh_cm, ConnectionRef, {exit_status, ChannelId0, 0}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end,
- receive
- {ssh_cm, ConnectionRef,{closed, ChannelId0}} ->
- ok
- after
- 10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
- end.
+ ok.
%%--------------------------------------------------------------------
flush_msgs() ->
+ flush_msgs([]).
+
+flush_msgs(Unexpected) ->
receive
- _ -> flush_msgs()
+ M ->
+ case lists:member(M, Unexpected) of
+ true ->
+ ct:fail("Unexpected message found: ~p", [M]);
+ _ ->
+ flush_msgs()
+ end
after
500 -> ok
end.
@@ -1614,6 +2118,7 @@ test_exec_is_enabled(ConnectionRef, Exec
{ssh_cm, ConnectionRef, {data, ChannelId, 0, <<Expect:ExpSz/binary, _/binary>>}} = R ->
ct:log("~p:~p Got expected ~p",[?MODULE,?LINE,R]);
Other ->
+ %% FIXME - should this testcase fail when unexpected data is received?
ct:log("~p:~p Got unexpected ~p~nExpect: ~p~n",
[?MODULE,?LINE, Other, {ssh_cm, ConnectionRef, {data, ChannelId, 0, Expect}} ])
after 5000 ->
@@ -1627,8 +2132,6 @@ big_cat_rx(ConnectionRef, ChannelId) ->
big_cat_rx(ConnectionRef, ChannelId, Acc) ->
receive
{ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} ->
- %% ssh_connection:adjust_window(ConnectionRef, ChannelId, size(Data)),
- %% window was pre-adjusted, don't adjust again here
big_cat_rx(ConnectionRef, ChannelId, [Data | Acc]);
{ssh_cm, ConnectionRef, {eof, ChannelId}} ->
{ok, iolist_to_binary(lists:reverse(Acc))}
@@ -1637,7 +2140,8 @@ big_cat_rx(ConnectionRef, ChannelId, Acc
end.
collect_data(ConnectionRef, ChannelId, EchoSize) ->
- ct:log("~p:~p Listener ~p running! ConnectionRef=~p, ChannelId=~p",[?MODULE,?LINE,self(),ConnectionRef,ChannelId]),
+ 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, EchoSize, Acc, Sum) ->
@@ -1646,18 +2150,15 @@ collect_data(ConnectionRef, ChannelId, E
{ssh_cm, ConnectionRef, {data, ChannelId, 0, Data}} when is_binary(Data) ->
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)),
+ 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("~p:~p collect_data: ***** unexpected message *****~n~p",[?MODULE,?LINE,Msg]),
collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
-
after TO ->
ct:log("~p:~p collect_data: ----- Nothing received for ~p seconds -----~n",[?MODULE,?LINE,TO]),
collect_data(ConnectionRef, ChannelId, EchoSize, Acc, Sum)
@@ -1686,6 +2187,11 @@ start_our_shell(_User, _Peer) ->
%% Don't actually loop, just exit
end).
+start_our_shell2(_User, _Peer) ->
+ spawn(fun() ->
+ io:format("Enter command\n"),
+ read_write_loop1("> ", 1)
+ end).
ssh_exec_echo(Cmd) ->
spawn(fun() ->
@@ -1696,3 +2202,24 @@ ssh_exec_echo(Cmd, User) ->
spawn(fun() ->
io:format("echo ~s ~s\n",[User,Cmd])
end).
+%% FIXME - upon refactoring this test suite, check if function below is reduntant to collect_data
+receive_bytes(_, _, 0, _) ->
+ ct:log("ALL DATA RECEIVED Budget = 0"),
+ ct:log("================================ ExpectBudget = 0 (reception completed)"),
+ ok;
+receive_bytes(ConnectionRef, ChannelId0, Budget, AccSize) when Budget > 0 ->
+ receive
+ {ssh_cm, ConnectionRef, {data, ChannelId0, 0, D}} ->
+ Fmt = "================================ ExpectBudget = "
+ "~p bytes Received/Total = ~p/~p bytes",
+ Args = [Budget, byte_size(D), AccSize + byte_size(D)],
+ ct:log(Fmt, Args),
+ ssh_connection:adjust_window(ConnectionRef, ChannelId0, size(D)),
+ receive_bytes(ConnectionRef, ChannelId0,
+ Budget - byte_size(D), AccSize + byte_size(D))
+ after
+ 10000 ->
+ ct:log("process_info(self(), messages) = ~p",
+ [process_info(self(), messages)]),
+ ct:fail("timeout ~p:~p",[?MODULE,?LINE])
+ end.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh.cover
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh.cover
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh.cover
@@ -6,8 +6,8 @@
ssh_app,
%% %% Supervisors
- %% ssh_acceptor_sup, ssh_channel_sup, ssh_connection_sup,
- %% sshc_sup, sshd_sup, ssh_subsystem_sup, ssh_sup,
+ %% ssh_acceptor_sup, ssh_channel_sup,
+ %% sshc_sup, sshd_sup, ssh_connection_sup, ssh_sup,
%% ssh_system_sup, ssh_tcpip_forward_acceptor_sup,
%% Test and/or info modules:
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_dbg_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_dbg_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_dbg_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2024. 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.
@@ -336,6 +336,12 @@ dbg_ssh_messages(Config) ->
?DBG_RECEIVE("Received SSH_MSG_KEXINIT:", Ref, C, Pid),
case atom_to_list( (ssh_connection_handler:alg(C))#alg.kex ) of
+ "curve"++_ ->
+ ?DBG_RECEIVE("Going to send SSH_MSG_KEX_ECDH_INIT:", Ref, C, Pid),
+ ?DBG_RECEIVE("Received SSH_MSG_KEX_ECDH_INIT:", Ref, D, Pid),
+ ?DBG_RECEIVE("Going to send SSH_MSG_KEX_ECDH_REPLY:", Ref, D, Pid),
+ ?DBG_RECEIVE("Received SSH_MSG_KEX_ECDH_REPLY:", Ref, C, Pid);
+
"ecdh-"++_ ->
?DBG_RECEIVE("Going to send SSH_MSG_KEX_ECDH_INIT:", Ref, C, Pid),
?DBG_RECEIVE("Received SSH_MSG_KEX_ECDH_INIT:", Ref, D, Pid),
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_echo_server.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_echo_server.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_echo_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2025. 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,7 +27,8 @@
n,
id,
cm,
- dbg = false
+ dbg = false,
+ parent
}).
-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
@@ -42,13 +43,19 @@ init([N]) ->
{ok, #state{n = N}};
init([N,Opts]) ->
State = #state{n = N,
- dbg = proplists:get_value(dbg,Opts,false)
+ dbg = proplists:get_value(dbg,Opts,false),
+ parent = proplists:get_value(parent, Opts)
},
?DBG(State, "init([~p])",[N]),
{ok, State}.
handle_msg({ssh_channel_up, ChannelId, ConnectionManager}, State) ->
?DBG(State, "ssh_channel_up Cid=~p ConnMngr=~p",[ChannelId,ConnectionManager]),
+ Pid = State#state.parent,
+ if Pid /= undefined ->
+ Pid ! {conn_peer, ConnectionManager};
+ true -> ok
+ end,
{ok, State#state{id = ChannelId,
cm = ConnectionManager}}.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_limited.cover
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_limited.cover
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_limited.cover
@@ -8,8 +8,8 @@
ssh_app,
%% Supervisors
- ssh_acceptor_sup, ssh_channel_sup, ssh_connection_sup,
- sshc_sup, sshd_sup, ssh_subsystem_sup, ssh_sup,
+ ssh_acceptor_sup, ssh_channel_sup,
+ sshc_sup, sshd_sup, ssh_connection_sup, ssh_sup,
ssh_system_sup, ssh_tcpip_forward_acceptor_sup,
%% Test and/or info modules:
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_options_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_options_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_options_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. 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,6 +34,7 @@
auth_method_kb_interactive_data_tuple/1,
auth_method_kb_interactive_data_fun3/1,
auth_method_kb_interactive_data_fun4/1,
+ auth_none/1,
connectfun_disconnectfun_client/1,
disconnectfun_option_client/1,
disconnectfun_option_server/1,
@@ -83,7 +84,12 @@
save_accepted_host_option/1,
raw_option/1,
config_file/1,
- config_file_modify_algorithms_order/1
+ config_file_modify_algorithms_order/1,
+ daemon_replace_options_simple/1,
+ daemon_replace_options_algs/1,
+ daemon_replace_options_algs_connect/1,
+ daemon_replace_options_algs_conf_file/1,
+ daemon_replace_options_not_found/1
]).
%%% Common test callbacks
@@ -93,6 +99,10 @@
init_per_testcase/2, end_per_testcase/2
]).
+%%% For test nodes
+-export([get_preferred_algorithms/2
+ ]).
+
-define(NEWLINE, <<"\r\n">>).
%%--------------------------------------------------------------------
@@ -115,6 +125,7 @@ all() ->
auth_method_kb_interactive_data_tuple,
auth_method_kb_interactive_data_fun3,
auth_method_kb_interactive_data_fun4,
+ auth_none,
{group, dir_options},
ssh_connect_timeout,
ssh_connect_arg4_timeout,
@@ -145,6 +156,11 @@ all() ->
raw_option,
config_file,
config_file_modify_algorithms_order,
+ daemon_replace_options_simple,
+ daemon_replace_options_algs,
+ daemon_replace_options_algs_connect,
+ daemon_replace_options_algs_conf_file,
+ daemon_replace_options_not_found,
{group, hardening_tests}
].
@@ -260,6 +276,7 @@ server_password_option(Config) when is_l
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "vego"},
{password, "foo"},
{user_interaction, false},
@@ -292,12 +309,14 @@ server_userpassword_option(Config) when
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, false},
{user_dir, UserDir}]),
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "vego"},
{password, "foo"},
{user_interaction, false},
@@ -327,12 +346,14 @@ server_pwdfun_option(Config) ->
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, false},
{user_dir, UserDir}]),
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "vego"},
{password, "foo"},
{user_interaction, false},
@@ -373,12 +394,14 @@ server_pwdfun_4_option(Config) ->
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "foo"},
{password, "morot"},
{user_interaction, false},
{user_dir, UserDir}]),
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "fie"},
{password, "morot"},
{user_interaction, false},
@@ -392,6 +415,7 @@ server_pwdfun_4_option(Config) ->
{error, Reason} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "bandit"},
{password, "pwd breaking"},
{user_interaction, false},
@@ -568,6 +592,30 @@ amkid(Config, {ExpectName,ExpectInstr,Ex
{"bar",2}]).
%%--------------------------------------------------------------------
+auth_none(Config) ->
+ 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),
+ {DaemonRef, Host, Port} =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {auth_methods, "password"}, % to make even more sure we don't use public-key-auth
+ {user_passwords, [{"foo","somepwd"}]}, % Not to be used
+ {no_auth_needed, true} % we test this
+ ]),
+ ClientConnRef1 =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user, "some-other-user"},
+ {password, "wrong-pwd"},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ "some-other-user" =
+ proplists:get_value(user, ssh:connection_info(ClientConnRef1, [user])),
+ ok = ssh:close(ClientConnRef1),
+ ok = ssh:stop_daemon(DaemonRef).
+
+%%--------------------------------------------------------------------
system_dir_option(Config) ->
DirUnread = proplists:get_value(unreadable_dir,Config),
FileRead = proplists:get_value(readable_file,Config),
@@ -595,14 +643,16 @@ user_dir_option(Config) ->
%% Any port will do (beware, implementation knowledge!):
Port = 65535,
- case ssh:connect("localhost", Port, [{user_dir, DirUnread}]) of
+ case ssh:connect("localhost", Port, [{user_dir, DirUnread},
+ {save_accepted_host, false}]) of
{error,{eoptions,{{user_dir,DirUnread},eacces}}} ->
ok;
{error,econnrefused} ->
ct:fail("Didn't detect that dir is unreadable", [])
end,
- case ssh:connect("localhost", Port, [{user_dir, FileRead}]) of
+ case ssh:connect("localhost", Port, [{user_dir, FileRead},
+ {save_accepted_host, false}]) of
{error,{eoptions,{{user_dir,FileRead},enotdir}}} ->
ok;
{error,econnrefused} ->
@@ -972,13 +1022,13 @@ do_hostkey_fingerprint_check(Config, Has
true ->
really_do_hostkey_fingerprint_check(Config, HashAlg);
false when HashAlg == old ->
- {skip,{unsupported_hash,md5}};% Happen to know that public_key:ssh_hostkey_fingerprint/1 uses md5...
+ {skip,{unsupported_hash,md5}};% Happen to know that ssh:hostkey_fingerprint/1 uses md5...
false ->
{skip,{unsupported_hash,HashAlg}}
end.
supported_hash(old) ->
- supported_hash(md5); % Happen to know that public_key:ssh_hostkey_fingerprint/1 uses md5...
+ supported_hash(md5); % Happen to know that ssh:hostkey_fingerprint/1 uses md5...
supported_hash(HashAlg) ->
Hs = if is_atom(HashAlg) -> [HashAlg];
is_list(HashAlg) -> HashAlg
@@ -990,11 +1040,11 @@ really_do_hostkey_fingerprint_check(Conf
UserDir = proplists:get_value(user_dir, Config),
SysDir = proplists:get_value(data_dir, Config),
- %% All host key fingerprints. Trust that public_key has checked the ssh_hostkey_fingerprint
+ %% All host key fingerprints. Trust that public_key has checked the 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)
+ old -> ssh:hostkey_fingerprint(Key);
+ _ -> ssh:hostkey_fingerprint(HashAlg, Key)
end
|| FileCandidate <- begin
{ok,KeyFileCands} = file:list_dir(SysDir),
@@ -1003,7 +1053,7 @@ really_do_hostkey_fingerprint_check(Conf
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)
+ try ssh_file:decode(Bin, public_key)
catch
_:_ -> []
end
@@ -1050,6 +1100,7 @@ ssh_connect_timeout(_Config) ->
{error,{faked_transport,connect,TimeoutToTransport}} =
ssh:connect("localhost", 12345,
[{transport,{tcp,?MODULE,tcp_closed}},
+ {save_accepted_host, false},
{connect_timeout,ConnTimeout}],
1000),
case TimeoutToTransport of
@@ -1088,7 +1139,7 @@ ssh_connect_arg4_timeout(_Config) ->
%% try to connect with a timeout, but "supervise" it
Client = spawn(fun() ->
T0 = erlang:monotonic_time(),
- Rc = ssh:connect("localhost",Port,[],Timeout),
+ Rc = ssh:connect("localhost",Port,[{save_accepted_host, false}],Timeout),
ct:log("Client ssh:connect got ~p",[Rc]),
Parent ! {done,self(),Rc,T0}
end),
@@ -1156,7 +1207,7 @@ ssh_daemon_minimal_remote_max_packet_siz
%% This test try every algorithm by connecting to an Erlang server
id_string_no_opt_client(Config) ->
{Server, _Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect("localhost", Port, [], 1000),
+ {error,_} = ssh:connect("localhost", Port, [{save_accepted_host, false}], 1000),
receive
{id,Server,"SSH-2.0-Erlang/"++Vsn} ->
true = expected_ssh_vsn(Vsn);
@@ -1169,7 +1220,9 @@ id_string_no_opt_client(Config) ->
%%--------------------------------------------------------------------
id_string_own_string_client(Config) ->
{Server, _Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"}], 1000),
+ {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle"},
+ {save_accepted_host, false}
+ ], 1000),
receive
{id,Server,"SSH-2.0-Pelle\r\n"} ->
ok;
@@ -1182,7 +1235,8 @@ id_string_own_string_client(Config) ->
%%--------------------------------------------------------------------
id_string_own_string_client_trail_space(Config) ->
{Server, _Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle "}], 1000),
+ {error,_} = ssh:connect("localhost", Port, [{id_string,"Pelle "},
+ {save_accepted_host, false}], 1000),
receive
{id,Server,"SSH-2.0-Pelle \r\n"} ->
ok;
@@ -1195,7 +1249,8 @@ id_string_own_string_client_trail_space(
%%--------------------------------------------------------------------
id_string_random_client(Config) ->
{Server, _Host, Port} = fake_daemon(Config),
- {error,_} = ssh:connect("localhost", Port, [{id_string,random}], 1000),
+ {error,_} = ssh:connect("localhost", Port, [{id_string,random},
+ {save_accepted_host, false}], 1000),
receive
{id,Server,Id="SSH-2.0-Erlang"++_} ->
ct:fail("Unexpected id: ~s.",[Id]);
@@ -1376,12 +1431,14 @@ max_log_item_len(Config) ->
{ok, Reports} = ssh_eqc_event_handler:get_reports(ReportHandlerPid),
ct:log("~p:~p ssh:connect -> ~p~n~p", [?MODULE,?LINE,R,Reports]),
- [ok,ok] =
- [check_skip_part(
- string:tokens(
- lists:flatten(io_lib:format(Fmt,Args)),
- " \n"))
- || {info_msg,_,{_,Fmt,Args}} <- Reports].
+ [ok] =
+ lists:usort(
+ [check_skip_part(
+ string:tokens(
+ lists:flatten(io_lib:format(Fmt,Args)),
+ " \n"))
+ || {info_msg,_,{_,Fmt,Args}} <- Reports]
+ ).
check_skip_part(["Disconnect","...","("++_NumSkipped, "bytes","skipped)"]) ->
@@ -1426,6 +1483,7 @@ connect_fun(ssh_sftp__start_channel, _Co
{ok,_Pid,ConnRef} =
ssh_sftp:start_channel(Host, Port,
[{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "carni"},
{password, "meat"}
]),
@@ -1457,7 +1515,7 @@ max_sessions(Config, ParallelLogin, Conn
ct:log("Connections up: ~p",[Connections]),
[_|_] = Connections,
- %% N w try one more than alowed:
+ %% N w try one more than allowed:
ct:pal("Info Report expected here (if not disabled) ...",[]),
try Connect(Host,Port)
of
@@ -1487,7 +1545,7 @@ try_to_connect(Connect, Host, Port, Pid,
of
_ConnectionRef1 ->
timer:cancel(Tref),
- ct:log("Step 3 ok: could set up one more connection after killing one. Thats good.",[]),
+ ct:log("Step 3 ok: could set up one more connection after killing one. That's good.",[]),
ssh:stop_daemon(Pid),
receive % flush.
timeout_no_connection -> ok
@@ -1515,7 +1573,7 @@ max_sessions_drops_tcp_connects(Config)
FloodSessions = 1000,
ParallelLogin = true,
NegTimeOut = 8*1000,
- HelloTimeOut = 1*1000,
+ HelloTimeOut = 200,
%% Start a test daemon
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
@@ -1538,6 +1596,7 @@ max_sessions_drops_tcp_connects(Config)
SSHconnect = fun(N) ->
R = ssh:connect(Host, Port,
[{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_dir, proplists:get_value(priv_dir,Config)},
{user_interaction, false},
{user, "carni"},
@@ -1600,10 +1659,10 @@ save_accepted_host_option(Config) ->
{error,enoent} = file:read_file(KnownHosts),
{ok,_C1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user, "vego"},
{password, "morot"},
{user_interaction, false},
- {save_accepted_host, false},
{user_dir, UserDir}]),
{error,enoent} = file:read_file(KnownHosts),
@@ -1630,6 +1689,18 @@ config_file(Config) ->
ct:log("ServerAlgs =~n~p~n~nOurAlgs =~n~p~n~nCommonAlgs =~n~p",[ServerAlgs,OurAlgs,CommonAlgs]),
Nkex = length(proplists:get_value(kex, CommonAlgs, [])),
+ %% Adjust for very old ssh daemons that only supports ssh-rsa and ssh-dss:
+ AdjustClient =
+ case proplists:get_value(public_key,ServerAlgs,[]) -- ['ssh-rsa','ssh-dss'] of
+ [] ->
+ %% Old, let the client support them also:
+ ct:log("Adjust the client's public_key set", []),
+ [{public_key, ['ssh-rsa','ssh-dss']}];
+ [_|_] ->
+ %% Ok, let the client be un-modified:
+ []
+ end,
+
case {ServerAlgs, ssh_test_lib:some_empty(CommonAlgs)} of
{[],_} ->
{skip, "No server algorithms found"};
@@ -1643,30 +1714,24 @@ config_file(Config) ->
[{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs),
%% Make config file:
- Contents =
- [{ssh, [{preferred_algorithms,
- [{cipher, [Ch1]},
- {kex, [K1a]}
- ]},
- {client_options,
- [{modify_algorithms,
- [{rm, [{kex, [K1a]}]},
- {append, [{kex, [K1b]}]}
+ {ok,ConfFile} =
+ make_config_file_in_privdir(
+ "c2.config", Config,
+ [{ssh, [{preferred_algorithms,
+ [{cipher, [Ch1]},
+ {kex, [K1a]}
+ ] ++ AdjustClient},
+ {client_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1a]}]},
+ {append, [{kex, [K1b]}]}
+ ]}
]}
]}
- ]}
- ],
- %% write the file:
- PrivDir = proplists:get_value(priv_dir, Config),
- ConfFile = filename:join(PrivDir,"c2.config"),
- {ok,D} = file:open(ConfFile, [write]),
- io:format(D, "~p.~n", [Contents]),
- file:close(D),
- {ok,Cnfs} = file:read_file(ConfFile),
- ct:log("c2.config:~n~s", [Cnfs]),
+ ]),
%% Start the slave node with the configuration just made:
- {ok,Node} = start_node(random_node_name(?MODULE), ConfFile),
+ {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]),
R0 = rpc:call(Node, ssh, default_algorithms, []),
ct:log("R0 = ~p",[R0]),
@@ -1683,9 +1748,11 @@ config_file(Config) ->
{_,[Ch1]}]} | _] = R1,
%% First connection. The client_options should be applied:
- {ok,C1} = rpc:call(Node, ssh, connect, [loopback, 22, [{silently_accept_hosts, true},
- {user_interaction, false}
- ]]),
+ {ok,C1} = rpc:call(Node, ssh, connect, [loopback, ?SSH_DEFAULT_PORT,
+ [{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user_interaction, false}
+ ]]),
ct:log("C1 = ~n~p", [C1]),
{algorithms,As1} = rpc:call(Node, ssh, connection_info, [C1, algorithms]),
K1b = proplists:get_value(kex, As1),
@@ -1698,9 +1765,10 @@ config_file(Config) ->
C2_Opts = [{modify_algorithms,[{rm,[{kex,[K1b]}]}, % N.B.
{append, [{kex,[K2a]}]}]},
{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_interaction, false}
],
- {ok,C2} = rpc:call(Node, ssh, connect, [loopback, 22, C2_Opts]),
+ {ok,C2} = rpc:call(Node, ssh, connect, [loopback, ?SSH_DEFAULT_PORT, C2_Opts]),
{algorithms,As2} = rpc:call(Node, ssh, connection_info, [C2, algorithms]),
K2a = proplists:get_value(kex, As2),
Ch1 = proplists:get_value(encrypt, As2),
@@ -1708,7 +1776,7 @@ config_file(Config) ->
{options,Os2} = rpc:call(Node, ssh, connection_info, [C2, options]),
ct:log("C2 opts:~n~p~n~nalgorithms:~n~p~n~noptions:~n~p", [C2_Opts,As2,Os2]),
- stop_node_nice(Node)
+ peer:stop(Peer)
end.
%%%----------------------------------------------------------------
@@ -1732,36 +1800,30 @@ config_file_modify_algorithms_order(Conf
[{_,[Ch1|_]}|_] = proplists:get_value(cipher, CommonAlgs),
%% Make config file:
- Contents =
- [{ssh, [{preferred_algorithms,
- [{cipher, [Ch1]},
- {kex, [K1]}
- ]},
- {server_options,
- [{modify_algorithms,
- [{rm, [{kex, [K1]}]},
- {append, [{kex, [K2]}]}
- ]}
- ]},
- {client_options,
- [{modify_algorithms,
- [{rm, [{kex, [K1]}]},
- {append, [{kex, [K3]}]}
+ {ok, ConfFile} =
+ make_config_file_in_privdir(
+ "c3.config", Config,
+ [{ssh, [{preferred_algorithms,
+ [{cipher, [Ch1]},
+ {kex, [K1]}
+ ]},
+ {server_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1]}]},
+ {append, [{kex, [K2]}]}
+ ]}
+ ]},
+ {client_options,
+ [{modify_algorithms,
+ [{rm, [{kex, [K1]}]},
+ {append, [{kex, [K3]}]}
+ ]}
]}
]}
- ]}
- ],
- %% write the file:
- PrivDir = proplists:get_value(priv_dir, Config),
- ConfFile = filename:join(PrivDir,"c3.config"),
- {ok,D} = file:open(ConfFile, [write]),
- io:format(D, "~p.~n", [Contents]),
- file:close(D),
- {ok,Cnfs} = file:read_file(ConfFile),
- ct:log("c3.config:~n~s", [Cnfs]),
+ ]),
%% Start the slave node with the configuration just made:
- {ok,Node} = start_node(random_node_name(?MODULE), ConfFile),
+ {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]),
R0 = rpc:call(Node, ssh, default_algorithms, []),
ct:log("R0 = ~p",[R0]),
@@ -1798,32 +1860,192 @@ config_file_modify_algorithms_order(Conf
ConnOptions = proplists:get_value(options, ConnInfo),
ConnPrefAlgs = proplists:get_value(preferred_algorithms, ConnOptions),
- %% And now, are all levels appied in right order:
+ %% And now, are all levels applied in right order:
[K3,K2] = proplists:get_value(kex, ConnPrefAlgs),
- stop_node_nice(Node)
+ peer:stop(Peer)
end.
%%--------------------------------------------------------------------
-%% Internal functions ------------------------------------------------
+daemon_replace_options_simple(Config) ->
+ SysDir = proplists:get_value(data_dir, Config),
+
+ UserDir1 = proplists:get_value(user_dir, Config),
+ UserDir2 = filename:join(UserDir1, "foo"),
+ file:make_dir(UserDir2),
+
+ {Pid, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir1}
+ ]),
+ {ok,Opts1} = ssh:daemon_info(Pid),
+ UserDir1 = proplists:get_value(user_dir, proplists:get_value(options,Opts1,[])),
+
+ {ok, Pid} = ssh:daemon_replace_options(Pid, [{user_dir,UserDir2}]),
+ {ok,Opts2} = ssh:daemon_info(Pid),
+ case proplists:get_value(user_dir, proplists:get_value(options,Opts2,[])) of
+ UserDir2 ->
+ ok;
+ UserDir1 ->
+ ct:log("~p:~p Got old value ~p~nExpected ~p", [?MODULE,?LINE,UserDir1,UserDir2]),
+ {fail, "Not changed"};
+ Other ->
+ ct:log("~p:~p Got ~p~nExpected ~p", [?MODULE,?LINE,Other,UserDir2]),
+ {fail, "Strange value"}
+ end.
+
%%--------------------------------------------------------------------
+daemon_replace_options_algs(Config) ->
+ SysDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(user_dir, Config),
-start_node(Name, ConfigFile) ->
- Pa = filename:dirname(code:which(?MODULE)),
- test_server:start_node(Name, slave, [{args,
- " -pa " ++ Pa ++
- " -config " ++ ConfigFile}]).
-
-stop_node_nice(Node) when is_atom(Node) ->
- test_server:stop_node(Node).
-
-random_node_name(BaseName) ->
- L = integer_to_list(erlang:unique_integer([positive])),
- lists:concat([BaseName,"___",L]).
+ DefaultKex =
+ ssh_transport:default_algorithms(kex),
+ NonDefaultKex =
+ ssh_transport:supported_algorithms(kex) -- DefaultKex,
+
+ case NonDefaultKex of
+ [A1|_] ->
+ [A2,A3|_] = DefaultKex,
+ {Pid, _Host, _Port} = ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, UserDir},
+ {preferred_algorithms,[{kex,[A1]}]}
+ ]),
+ [A1] = get_preferred_algorithms(Pid, kex),
+ {ok, Pid} =
+ ssh:daemon_replace_options(Pid, [{modify_algorithms,
+ [{prepend,[{kex,[A2]}]}]
+ }
+ ]),
+ [A2,A1] = get_preferred_algorithms(Pid, kex),
+
+ {ok, Pid} =
+ ssh:daemon_replace_options(Pid, [{preferred_algorithms,[{kex,[A3]}]
+ }
+ ]),
+ [A2,A3] = get_preferred_algorithms(Pid, kex)
+ ;
+ [] ->
+ {skip, "No non-default kex"}
+ end.
+
+%%--------------------------------------------------------------------
+daemon_replace_options_algs_connect(Config) ->
+ [A1,A2|_] =
+ ssh_transport:default_algorithms(kex),
+
+ {Pid, Host, Port} =
+ ssh_test_lib:std_daemon(Config,
+ [{preferred_algorithms,[{kex,[A1]}]}
+ ]),
+ [A1] = get_preferred_algorithms(Pid, kex),
+
+ %% Open a connection with A1 as kex and test it
+ C1 =
+ ssh_test_lib:std_connect(Config, Host, Port,
+ [{preferred_algorithms,[{kex,[A1]}]}
+ ]),
+ ok = test_connection(C1),
+ ok = test_not_connect(Config, Host, Port,
+ [{preferred_algorithms,[{kex,[A2]}]}
+ ]),
+
+ %% Change kex to A2
+ {ok, Pid} =
+ ssh:daemon_replace_options(Pid,
+ [{preferred_algorithms,[{kex,[A2]}]}]),
+ [A2] = get_preferred_algorithms(Pid, kex),
+
+ %% and open the second connection with this kex, and test it
+ C2 =
+ ssh_test_lib:std_connect(Config, Host, Port,
+ [{preferred_algorithms,[{kex,[A2]}]}
+ ]),
+ ok = test_connection(C2),
+ ok = test_not_connect(Config, Host, Port,
+ [{preferred_algorithms,[{kex,[A1]}]}
+ ]),
+
+ %% Test that the first connection is still alive:
+ ok = test_connection(C1),
+
+ ssh:close(C1),
+ ssh:close(C2),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
+daemon_replace_options_algs_conf_file(Config) ->
+ SysDir = proplists:get_value(data_dir, Config),
+ UserDir = proplists:get_value(user_dir, Config),
+
+ DefaultKex =
+ ssh_transport:default_algorithms(kex),
+ NonDefaultKex =
+ ssh_transport:supported_algorithms(kex) -- DefaultKex,
+
+ case NonDefaultKex of
+ [A0,A1|_] ->
+ %% Make config file:
+ {ok,ConfFile} =
+ make_config_file_in_privdir(
+ "c4.config", Config,
+ [{ssh, [{modify_algorithms,
+ %% Whatever happens, always put A0 first in the kex list:
+ [{prepend, [{kex, [A0]}]}
+ ]}
+ ]}
+ ]),
+
+ [A2|_] = DefaultKex,
+ ct:log("[A0, A1, A2] = ~p", [[A0, A1, A2]]),
+
+ %% Start the slave node with the configuration just made:
+ {ok, Peer, Node} = ?CT_PEER(["-config", ConfFile]),
+
+ %% Start ssh on the slave. This should apply the ConfFile:
+ rpc:call(Node, ssh, start, []),
+
+ {Pid, _Host, _Port} =
+ rpc:call(Node, ssh_test_lib, daemon,
+ [
+ [{system_dir, SysDir},
+ {user_dir, UserDir},
+ {preferred_algorithms,[{kex,[A1]}]}
+ ]
+ ]),
+
+ [A0,A1] =
+ rpc:call(Node, ?MODULE, get_preferred_algorithms, [Pid, kex]),
+ {ok, Pid} =
+ rpc:call(Node, ssh, daemon_replace_options,
+ [Pid,
+ [{modify_algorithms,
+ [{prepend,[{kex,[A2]}]}]
+ }
+ ]
+ ]),
+
+ %% Check that the precedens order is fulfilled:
+ [A2,A0,A1] =
+ rpc:call(Node, ?MODULE, get_preferred_algorithms, [Pid, kex]),
+
+ peer:stop(Peer);
+ [] ->
+ {skip, "No non-default kex"}
+ end.
+
+%%--------------------------------------------------------------------
+daemon_replace_options_not_found(_Config) ->
+ %% when the daemon doesn't exist the error should be the same
+ %% in daemon_info and daemon_replace_options
+ %% which is {error, bad_daemon_ref}
+ Error = ssh:daemon_info(self()),
+ Error = ssh:daemon_replace_options(self(), []).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
-%%%----
-
expected_ssh_vsn(Str) ->
try
{ok,L} = application:get_all_key(ssh),
@@ -1833,7 +2055,7 @@ expected_ssh_vsn(Str) ->
"\r\n" -> true;
_ -> false
catch
- _:_ -> true %% ssh not started so we dont't know
+ _:_ -> true %% ssh not started so we don't know
end.
@@ -1860,3 +2082,42 @@ fake_daemon(_Config) ->
after
10000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
end.
+
+
+make_config_file_in_privdir(FileName, Config, Contents) ->
+ %% write the file:
+ PrivDir = proplists:get_value(priv_dir, Config),
+ ConfFile = filename:join(PrivDir, FileName),
+ {ok,D} = file:open(ConfFile, [write]),
+ io:format(D, "~p.~n", [Contents]),
+ file:close(D),
+ {ok,Cnfs} = file:read_file(ConfFile),
+ ct:log("Config file ~p :~n~s", [ConfFile,Cnfs]),
+ {ok,ConfFile}.
+
+
+get_preferred_algorithms(Pid, Type) ->
+ {ok,#{preferred_algorithms:=As}} = ssh_system_sup:get_acceptor_options(Pid),
+ proplists:get_value(Type, As).
+
+test_connection(C) ->
+ {ok, Ch} = ssh_connection:session_channel(C, infinity),
+ A = rand:uniform(100),
+ B = rand:uniform(100),
+ A_plus_B = lists:concat([A,"+",B,"."]),
+ Sum = integer_to_binary(A+B),
+ success = ssh_connection:exec(C, Ch, A_plus_B, infinity),
+ expected = ssh_test_lib:receive_exec_result(
+ {ssh_cm, C, {data, Ch, 0, Sum}} ),
+ ssh_test_lib:receive_exec_end(C, Ch),
+ ok.
+
+test_not_connect(Config, Host, Port, Opts) ->
+ try
+ ssh_test_lib:std_connect(Config, Host, Port, Opts)
+ of
+ Cx when is_pid(Cx) -> {error, connected}
+ catch
+ error:{badmatch, {error,_}} -> ok
+ end.
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_protocol_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_protocol_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_protocol_SUITE.erl
@@ -1,18 +1,19 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2022. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2025. All Rights Reserved.
%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
+%% 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
%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
+%% 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%
%%
@@ -25,6 +26,7 @@
-include_lib("kernel/include/inet.hrl").
-include("ssh.hrl"). % ?UINT32, ?BYTE, #ssh{} ...
-include("ssh_transport.hrl").
+-include("ssh_connect.hrl").
-include("ssh_auth.hrl").
-include("ssh_test_lib.hrl").
@@ -47,12 +49,19 @@
bad_service_name_then_correct/1,
bad_very_long_service_name/1,
client_handles_keyboard_interactive_0_pwds/1,
+ client_handles_banner_keyboard_interactive/1,
client_info_line/1,
do_gex_client_init/3,
do_gex_client_init_old/3,
empty_service_name/1,
ext_info_c/1,
ext_info_s/1,
+ kex_strict_negotiated/1,
+ kex_strict_violation_key_exchange/1,
+ kex_strict_violation_new_keys/1,
+ kex_strict_violation/1,
+ kex_strict_violation_2/1,
+ kex_strict_msg_unknown/1,
gex_client_init_option_groups/1,
gex_client_init_option_groups_file/1,
gex_client_init_option_groups_moduli_file/1,
@@ -69,6 +78,8 @@
modify_rm/1,
no_common_alg_client_disconnects/1,
no_common_alg_server_disconnects/1,
+ custom_kexinit/1,
+ early_rce/1,
no_ext_info_s1/1,
no_ext_info_s2/1,
packet_length_too_large/1,
@@ -76,7 +87,9 @@
preferred_algorithms/1,
service_name_length_too_large/1,
service_name_length_too_short/1,
- client_close_after_hello/1
+ client_close_after_hello/1,
+ channel_close_timeout/1,
+ extra_ssh_msg_service_request/1
]).
-define(NEWLINE, <<"\r\n">>).
@@ -90,11 +103,19 @@
[{client2server,Ciphs}, {server2client,Ciphs}]
end)()
).
-
-
-define(v(Key, Config), proplists:get_value(Key, Config)).
-define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)).
-
+-define(HARDCODED_KEXDH_REPLY,
+ #ssh_msg_kexdh_reply{
+ public_host_key = {{{'ECPoint',<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ {namedCurve,{1,3,101,112}}},
+ 'ssh-ed25519'},
+ f = 18504393053016436370762156176197081926381112956345797067569792020930728564439992620494295053804030674742529174859108487694089045521619258420515443400605141150065440678508889060925968846155921972385560196703381004650914261218463420313738628465563288022895912907728767735629532940627575655703806353550720122093175255090704443612257683903495753071530605378193139909567971489952258218767352348904221407081210633467414579377014704081235998044497191940270966762124544755076128392259615566530695493013708460088312025006678879288856957348606386230195080105197251789635675011844976120745546472873505352732719507783227210178188,
+ h_sig = <<90,247,44,240,136,196,82,215,56,165,53,33,230,101,253,
+ 34,112,201,21,131,162,169,10,129,174,14,69,25,39,174,
+ 92,210,130,249,103,2,215,245,7,213,110,235,136,134,11,
+ 124,248,139,79,17,225,77,125,182,204,84,137,167,99,186,
+ 167,42,192,10>>}).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -107,6 +128,7 @@ suite() ->
all() ->
[{group,tool_tests},
client_info_line,
+ early_rce,
{group,kex},
{group,service_requests},
{group,authentication},
@@ -114,7 +136,8 @@ all() ->
{group,field_size_error},
{group,ext_info},
{group,preferred_algorithms},
- {group,client_close_early}
+ {group,client_close_early},
+ {group,channel_close}
].
groups() ->
@@ -125,26 +148,32 @@ groups() ->
]},
{packet_size_error, [], [packet_length_too_large,
packet_length_too_short]},
-
{field_size_error, [], [service_name_length_too_large,
service_name_length_too_short]},
-
- {kex, [], [no_common_alg_server_disconnects,
+ {kex, [], [custom_kexinit,
+ no_common_alg_server_disconnects,
no_common_alg_client_disconnects,
gex_client_init_option_groups,
gex_server_gex_limit,
gex_client_init_option_groups_moduli_file,
gex_client_init_option_groups_file,
gex_client_old_request_exact,
- gex_client_old_request_noexact
- ]},
+ gex_client_old_request_noexact,
+ kex_strict_negotiated,
+ kex_strict_violation_key_exchange,
+ kex_strict_violation_new_keys,
+ kex_strict_violation,
+ kex_strict_violation_2,
+ kex_strict_msg_unknown]},
{service_requests, [], [bad_service_name,
bad_long_service_name,
bad_very_long_service_name,
empty_service_name,
- bad_service_name_then_correct
+ bad_service_name_then_correct,
+ extra_ssh_msg_service_request
]},
- {authentication, [], [client_handles_keyboard_interactive_0_pwds
+ {authentication, [], [client_handles_keyboard_interactive_0_pwds,
+ client_handles_banner_keyboard_interactive
]},
{ext_info, [], [no_ext_info_s1,
no_ext_info_s2,
@@ -157,24 +186,24 @@ groups() ->
modify_rm,
modify_combo
]},
- {client_close_early, [], [client_close_after_hello
- ]}
+ {client_close_early, [], [client_close_after_hello]},
+ {channel_close, [], [channel_close_timeout]}
].
init_per_suite(Config) ->
?CHECK_CRYPTO(start_std_daemon( setup_dirs( start_apps(Config)))).
-
+
end_per_suite(Config) ->
stop_apps(Config).
-
-
-init_per_testcase(no_common_alg_server_disconnects, Config) ->
+init_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects;
+ Tc == custom_kexinit ->
start_std_daemon(Config, [{preferred_algorithms,[{public_key,['ssh-rsa']},
{cipher,?DEFAULT_CIPHERS}
]}]);
-
+init_per_testcase(kex_strict_negotiated, Config) ->
+ Config;
init_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
TC == gex_client_init_option_groups_moduli_file ;
TC == gex_client_init_option_groups_file ;
@@ -215,8 +244,11 @@ init_per_testcase(TC, Config) when TC ==
init_per_testcase(_TestCase, Config) ->
check_std_daemon_works(Config, ?LINE).
-end_per_testcase(no_common_alg_server_disconnects, Config) ->
+end_per_testcase(Tc, Config) when Tc == no_common_alg_server_disconnects;
+ Tc == custom_kexinit ->
stop_std_daemon(Config);
+end_per_testcase(kex_strict_negotiated, Config) ->
+ Config;
end_per_testcase(TC, Config) when TC == gex_client_init_option_groups ;
TC == gex_client_init_option_groups_moduli_file ;
TC == gex_client_init_option_groups_file ;
@@ -374,6 +406,90 @@ no_common_alg_server_disconnects(Config)
]
).
+early_rce(Config) ->
+ {ok,InitialState} =
+ ssh_trpt_test_lib:exec([{set_options, [print_ops, print_seqnums, print_messages]}]),
+ TypeOpen = "session",
+ ChannelId = 0,
+ WinSz = 425984,
+ PktSz = 65536,
+ DataOpen = <<>>,
+ SshMsgChannelOpen = ssh_connection:channel_open_msg(TypeOpen, ChannelId, WinSz, PktSz, DataOpen),
+
+ Id = 0,
+ TypeReq = "exec",
+ WantReply = true,
+ DataReq = <<?STRING(<<"lists:seq(1,10).">>)>>,
+ SshMsgChannelRequest =
+ ssh_connection:channel_request_msg(Id, TypeReq, WantReply, DataReq),
+ {ok, _AfterKexState} =
+ ssh_trpt_test_lib:exec(
+ [{connect,
+ server_host(Config),server_port(Config),
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {silently_accept_hosts, true},
+ {recv_ext_info, false},
+ {user_dir, user_dir(Config)},
+ {user_interaction, false}
+ | proplists:get_value(extra_options,Config,[])]},
+ receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, SshMsgChannelOpen},
+ {send, SshMsgChannelRequest},
+ {match, disconnect(), receive_msg}
+ ], InitialState),
+ ok.
+
+custom_kexinit(Config) ->
+ %% 16#C0 value causes unicode:characters_to_list to return a big error value
+ Trash = lists:duplicate(260_000, 16#C0),
+ FunnyAlg = "curve25519-sha256",
+ KexInit =
+ #ssh_msg_kexinit{cookie = <<"Ã/Ï!9zñKá:ñÀv¿JÜ">>,
+ kex_algorithms =
+ [FunnyAlg ++ Trash],
+ server_host_key_algorithms = ["ssh-rsa"],
+ encryption_algorithms_client_to_server =
+ ["aes256-ctr","aes192-ctr","aes128-ctr","aes128-cbc","3des-cbc"],
+ encryption_algorithms_server_to_client =
+ ["aes256-ctr","aes192-ctr","aes128-ctr","aes128-cbc","3des-cbc"],
+ mac_algorithms_client_to_server =
+ ["hmac-sha2-512-etm@openssh.com","hmac-sha2-256-etm@openssh.com",
+ "hmac-sha2-512","hmac-sha2-256","hmac-sha1-etm@openssh.com","hmac-sha1"],
+ mac_algorithms_server_to_client =
+ ["hmac-sha2-512-etm@openssh.com","hmac-sha2-256-etm@openssh.com",
+ "hmac-sha2-512","hmac-sha2-256","hmac-sha1-etm@openssh.com","hmac-sha1"],
+ compression_algorithms_client_to_server = ["none","zlib@openssh.com","zlib"],
+ compression_algorithms_server_to_client = ["none","zlib@openssh.com","zlib"],
+ languages_client_to_server = [],
+ languages_server_to_client = [],
+ first_kex_packet_follows = false,
+ reserved = 0
+ },
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, {print_messages,detail}]},
+ {connect,
+ server_host(Config),server_port(Config),
+ [{silently_accept_hosts, true},
+ {user_dir, user_dir(Config)},
+ {user_interaction, false},
+ {preferred_algorithms,[{public_key,['ssh-rsa']},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}
+ ]},
+ receive_hello,
+ {send, hello},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, KexInit}, % with server unsupported 'ssh-dss' !
+ {match, disconnect(), receive_msg}
+ ]
+ ).
+
%%--------------------------------------------------------------------
%%% Algo negotiation fail. This should result in a ssh_msg_disconnect
%%% being sent from the client.
@@ -682,7 +798,82 @@ client_handles_keyboard_interactive_0_pw
]}]
).
+%%%--------------------------------------------------------------------
+%%% SSH_MSG_USERAUTH_BANNER can be sent at any time during user auth.
+%%% The following test mimics a SSH server implementation that sends the banner
+%%% immediately before sending SSH_MSG_USERAUTH_SUCCESS.
+client_handles_banner_keyboard_interactive(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),
+
+ %% Start a process handling one connection on the server side:
+ spawn_link(
+ fun() ->
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_messages]},
+ {accept, [{system_dir, system_dir(Config)},
+ {user_dir, user_dir(Config)}]},
+ 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_service_request{name="ssh-userauth"}, receive_msg},
+ {send, #ssh_msg_service_accept{name="ssh-userauth"}},
+
+ {match, #ssh_msg_userauth_request{service="ssh-connection",
+ method="none",
+ user=User,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_failure{authentications = "keyboard-interactive",
+ partial_success = false}},
+
+ {match, #ssh_msg_userauth_request{service="ssh-connection",
+ method="keyboard-interactive",
+ user=User,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 1,
+ data = <<0,0,0,10,80,97,115,115,119,111,114,100,58,32,0>>
+ }},
+ {match, #ssh_msg_userauth_info_response{num_responses = 1,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_info_request{name = "",
+ instruction = "",
+ language_tag = "",
+ num_prompts = 0,
+ data = <<>>
+ }},
+ {match, #ssh_msg_userauth_info_response{num_responses = 0,
+ data = <<>>,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_banner{message = "Banner\n"}},
+ {send, #ssh_msg_userauth_success{}},
+ close_socket,
+ print_state
+ ],
+ InitialState)
+ end),
+
+ %% and finally connect to it with a regular Erlang SSH client:
+ {ok,_} = std_connect(HostPort, Config,
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]}]
+ ).
%%%--------------------------------------------------------------------
client_info_line(Config) ->
@@ -717,7 +908,7 @@ client_info_line(Config) ->
%%% 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
+ %% Start the daemon
Server = {Pid,_,_} = ssh_test_lib:daemon([{send_ext_info,true},
{system_dir, system_dir(Config)}]),
{ok,AfterKexState} = connect_and_kex([{server,Server}|Config]),
@@ -732,7 +923,7 @@ no_ext_info_s1(Config) ->
%%% The server does not send the extension because
%%% the server is not configured to send it
no_ext_info_s2(Config) ->
- %% Start the dameon
+ %% Start the daemon
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}]},
@@ -748,7 +939,7 @@ no_ext_info_s2(Config) ->
%%%--------------------------------------------------------------------
%%% The server sends the extension
ext_info_s(Config) ->
- %% Start the dameon
+ %% Start the daemon
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}]},
@@ -818,6 +1009,226 @@ ext_info_c(Config) ->
{result, Pid, Error} -> ct:fail("Error: ~p",[Error])
end.
+%%%--------------------------------------------------------------------
+%%%
+kex_strict_negotiated(Config0) ->
+ {ok, TestRef} = ssh_test_lib:add_log_handler(),
+ Config = start_std_daemon(Config0, []),
+ {Server, Host, Port} = proplists:get_value(server, Config),
+ Level = ssh_test_lib:get_log_level(),
+ ssh_test_lib:set_log_level(debug),
+ {ok, ConnRef} = std_connect({Host, Port}, Config, []),
+ {algorithms, _A} = ssh:connection_info(ConnRef, algorithms),
+ ssh:stop_daemon(Server),
+ {ok, Events} = ssh_test_lib:get_log_events(TestRef),
+ true = ssh_test_lib:kex_strict_negotiated(client, Events),
+ true = ssh_test_lib:kex_strict_negotiated(server, Events),
+ ssh_test_lib:set_log_level(Level),
+ ssh_test_lib:rm_log_handler(),
+ ok.
+
+%% Connect to an erlang server and inject unexpected SSH message
+%% ssh_fsm_kexinit in key_exchange state
+kex_strict_violation_key_exchange(Config) ->
+ ExpectedReason = "KEX strict violation",
+ Injections = [ssh_msg_ignore, ssh_msg_debug, ssh_msg_unimplemented],
+ TestProcedure =
+ fun(M) ->
+ ct:log(
+ "=================== START: ~p Message: ~p Expected Fail =================================",
+ [?FUNCTION_NAME, M]),
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, M},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]
+ end,
+ [kex_strict_helper(Config, TestProcedure(Msg), ExpectedReason) ||
+ Msg <- Injections],
+ ct:log("========== END ========"),
+ ok.
+
+%% Connect to an erlang server and inject unexpected SSH message
+%% ssh_fsm_kexinit in new_keys state
+kex_strict_violation_new_keys(Config) ->
+ ExpectedReason = "KEX strict violation",
+ Injections = [ssh_msg_ignore, ssh_msg_debug, ssh_msg_unimplemented],
+ TestProcedure =
+ fun(M) ->
+ ct:log(
+ "=================== START: ~p Message: ~p Expected Fail =================================",
+ [?FUNCTION_NAME, M]),
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init},
+ {send, M},
+ {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]
+ end,
+ [kex_strict_helper(Config, TestProcedure(Msg), ExpectedReason) ||
+ Msg <- Injections],
+ ct:log("========== END ========"),
+ ok.
+
+%% Connect to an erlang server and inject unexpected SSH message
+%% duplicated KEXINIT
+kex_strict_violation(Config) ->
+ TestFlows =
+ [{kexinit, "KEX strict violation",
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexinit},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
+ {ssh_msg_kexdh_init, "KEX strict violation",
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init_dup},
+ {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
+ {new_keys, "Message ssh_msg_newkeys in wrong state",
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init},
+ {match,# ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ {send, #ssh_msg_newkeys{}},
+ {match, #ssh_msg_newkeys{_='_'}, receive_msg},
+ {send, #ssh_msg_newkeys{}},
+ {match, disconnect(?SSH_DISCONNECT_PROTOCOL_ERROR), receive_msg}]},
+ {ssh_msg_unexpected_dh_gex, "KEX strict violation",
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ %% dh_alg is expected but dh_gex_alg is provided
+ {send, #ssh_msg_kex_dh_gex_request{min = 1000, n = 3000, max = 4000}},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]},
+ {wrong_role, "KEX strict violation",
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ %% client should not send message below
+ {send, ?HARDCODED_KEXDH_REPLY},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}]}],
+ TestProcedure =
+ fun({Msg, _, P}) ->
+ ct:log(
+ "==== START: ~p (duplicated ~p) Expected Fail ====~n~p",
+ [?FUNCTION_NAME, Msg, P]),
+ P
+ end,
+ [kex_strict_helper(Config, TestProcedure(Procedure), Reason) ||
+ Procedure = {_, Reason, _} <- TestFlows],
+ ct:log("==== END ====="),
+ ok.
+
+kex_strict_violation_2(Config) ->
+ ExpectedReason = "KEX strict violation",
+ {ok, TestRef} = ssh_test_lib:add_log_handler(),
+ Level = ssh_test_lib:get_log_level(),
+ ssh_test_lib:set_log_level(debug),
+ %% Connect and negotiate keys
+ {ok, InitialState} = ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]}]),
+ {ok, UpToUnexpectedKexDHReply} =
+ ssh_trpt_test_lib:exec(
+ [{connect,
+ server_host(Config),server_port(Config),
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {silently_accept_hosts, true},
+ {recv_ext_info, false},
+ {user_dir, user_dir(Config)},
+ {user_interaction, false}
+ | proplists:get_value(extra_options,Config,[])
+ ]}] ++
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init},
+ {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ %% client should not send message below
+ {send, ?HARDCODED_KEXDH_REPLY},
+ {match, {'or', [#ssh_msg_newkeys{_='_'},
+ disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED)]},
+ receive_msg}],
+ InitialState),
+ case ssh_trpt_test_lib:return_value(UpToUnexpectedKexDHReply) of
+ {ssh_msg_newkeys} ->
+ ct:log("1st flow - extra match for disconnect needed"),
+ ssh_trpt_test_lib:exec(
+ [{match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+ UpToUnexpectedKexDHReply);
+ _ ->
+ ct:log("2nd flow disconnect already received")
+ end,
+ ct:sleep(100),
+ {ok, Events} = ssh_test_lib:get_log_events(TestRef),
+ ssh_test_lib:rm_log_handler(),
+ ct:log("Events = ~p", [Events]),
+ true = ssh_test_lib:kex_strict_negotiated(client, Events),
+ true = ssh_test_lib:kex_strict_negotiated(server, Events),
+ true = ssh_test_lib:event_logged(server, Events, ExpectedReason),
+ ssh_test_lib:set_log_level(Level),
+ ok.
+
+%% Connect to an erlang server and inject unexpected non-SSH binary
+kex_strict_msg_unknown(Config) ->
+ ct:log("START: ~p~n=================================", [?FUNCTION_NAME]),
+ ExpectedReason = "Bad packet: Size",
+ TestMessages =
+ [receive_hello,
+ {send, hello},
+ {send, ssh_msg_kexinit},
+ {match, #ssh_msg_kexinit{_='_'}, receive_msg},
+ {send, ssh_msg_kexdh_init},
+ {send, ssh_msg_unknown},
+ {match, #ssh_msg_kexdh_reply{_='_'}, receive_msg},
+ {match, disconnect(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED), receive_msg}],
+ kex_strict_helper(Config, TestMessages, ExpectedReason).
+
+kex_strict_helper(Config, TestMessages, ExpectedReason) ->
+ {ok, TestRef} = ssh_test_lib:add_log_handler(),
+ Level = ssh_test_lib:get_log_level(),
+ ssh_test_lib:set_log_level(debug),
+ %% Connect and negotiate keys
+ {ok, InitialState} = ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]}]),
+ {ok, _AfterKexState} =
+ ssh_trpt_test_lib:exec(
+ [{connect,
+ server_host(Config),server_port(Config),
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {silently_accept_hosts, true},
+ {recv_ext_info, false},
+ {user_dir, user_dir(Config)},
+ {user_interaction, false}
+ | proplists:get_value(extra_options,Config,[])
+ ]}] ++
+ TestMessages,
+ InitialState),
+ ct:sleep(100),
+ {ok, Events} = ssh_test_lib:get_log_events(TestRef),
+ ssh_test_lib:rm_log_handler(),
+ ct:log("Events = ~p", [Events]),
+ true = ssh_test_lib:kex_strict_negotiated(client, Events),
+ true = ssh_test_lib:kex_strict_negotiated(server, Events),
+ true = ssh_test_lib:event_logged(server, Events, ExpectedReason),
+ ssh_test_lib:set_log_level(Level),
+ ok.
%%%----------------------------------------------------------------
%%%
@@ -839,7 +1250,7 @@ modify_append(Config) ->
Ciphers = filter_supported(cipher, ?CIPHERS),
{ok,_} =
chk_pref_algs(Config,
- [?DEFAULT_KEX, ?EXTRA_KEX],
+ [?DEFAULT_KEX, ?EXTRA_KEX, list_to_atom(?kex_strict_s)],
Ciphers,
[{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
{cipher,Ciphers}
@@ -853,7 +1264,7 @@ modify_prepend(Config) ->
Ciphers = filter_supported(cipher, ?CIPHERS),
{ok,_} =
chk_pref_algs(Config,
- [?EXTRA_KEX, ?DEFAULT_KEX],
+ [?EXTRA_KEX, ?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
Ciphers,
[{preferred_algorithms, [{kex,[?DEFAULT_KEX]},
{cipher,Ciphers}
@@ -867,7 +1278,7 @@ modify_rm(Config) ->
Ciphers = filter_supported(cipher, ?CIPHERS),
{ok,_} =
chk_pref_algs(Config,
- [?DEFAULT_KEX],
+ [?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
tl(Ciphers),
[{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
{cipher,Ciphers}
@@ -886,7 +1297,7 @@ modify_combo(Config) ->
LastC = lists:last(Ciphers),
{ok,_} =
chk_pref_algs(Config,
- [?DEFAULT_KEX],
+ [?DEFAULT_KEX, list_to_atom(?kex_strict_s)],
[LastC] ++ (tl(Ciphers)--[LastC]) ++ [hd(Ciphers)],
[{preferred_algorithms, [{kex,[?DEFAULT_KEX,?EXTRA_KEX]},
{cipher,Ciphers}
@@ -903,8 +1314,6 @@ modify_combo(Config) ->
%%%----------------------------------------------------------------
%%%
-client_close_after_hello() -> [{timetrap,{seconds,80}}].
-
client_close_after_hello(Config0) ->
MaxSessions = 20,
SleepSec = 15,
@@ -913,7 +1322,7 @@ client_close_after_hello(Config0) ->
{negotiation_timeout,SleepSec*1000}
]),
- {Parents0, Conns0, []} = find_handshake_parent(server_port(Config)),
+ {_Parents0, Conns0, []} = find_handshake_parent(server_port(Config)),
Cs =
[ssh_trpt_test_lib:exec(
@@ -975,6 +1384,44 @@ client_close_after_hello(Config0) ->
{fail, no_handshakers}
end.
+%%% Connect to an erlang server and pretend client sending extra
+%%% ssh_msg_service_request (Paramiko client behavior)
+extra_ssh_msg_service_request(Config) ->
+ %% Connect and negotiate keys
+ {ok,InitialState} = ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_seqnums, print_messages]}]
+ ),
+ {ok,AfterKexState} = connect_and_kex(Config, InitialState),
+ %% Do the authentcation
+ {User,Pwd} = server_user_password(Config),
+ UserAuthFlow =
+ fun(P) ->
+ [{send, #ssh_msg_service_request{name = "ssh-userauth"}},
+ {match, #ssh_msg_service_accept{name = "ssh-userauth"}, receive_msg},
+ {send, #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "password",
+ data = <<?BOOLEAN(?FALSE),
+ ?STRING(unicode:characters_to_binary(P))>>
+ }}]
+ end,
+ {ok,EndState} =
+ ssh_trpt_test_lib:exec(
+ UserAuthFlow("WRONG") ++
+ [{match, #ssh_msg_userauth_failure{_='_'}, receive_msg}] ++
+ UserAuthFlow(Pwd) ++
+ [{match, #ssh_msg_userauth_success{_='_'}, receive_msg}],
+ AfterKexState),
+ %% Disconnect
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{send, #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "End of the fun",
+ language = ""
+ }},
+ close_socket
+ ], EndState),
+ ok.
%%%================================================================
%%%==== Internal functions ========================================
@@ -984,7 +1431,7 @@ chk_pref_algs(Config,
ExpectedKex,
ExpectedCiphers,
ServerPrefOpts) ->
- %% Start the dameon
+ %% Start the daemon
case ssh_test_lib:daemon(
[{send_ext_info,false},
{recv_ext_info,false},
@@ -1103,9 +1550,10 @@ std_connect({Host,Port}, Config, Opts) -
std_connect(Host, Port, Config, Opts) ->
{User,Pwd} = server_user_password(Config),
ssh:connect(Host, Port,
- %% Prefere User's Opts to the default opts
+ %% Prefer User's Opts to the default opts
[O || O = {Tag,_} <- [{user,User},{password,Pwd},
{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_dir, user_dir(Config)},
{user_interaction, false}],
not lists:keymember(Tag, 1, Opts)
@@ -1140,6 +1588,84 @@ connect_and_kex(Config, InitialState) ->
],
InitialState).
+channel_close_timeout(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),
+ %% Start a process handling one connection on the server side:
+ spawn_link(
+ fun() ->
+ {ok,_} =
+ ssh_trpt_test_lib:exec(
+ [{set_options, [print_ops, print_messages]},
+ {accept, [{system_dir, system_dir(Config)},
+ {user_dir, user_dir(Config)},
+ {idle_time, 50000}]},
+ 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_service_request{name="ssh-userauth"}, receive_msg},
+ {send, #ssh_msg_service_accept{name="ssh-userauth"}},
+ {match, #ssh_msg_userauth_request{service="ssh-connection",
+ method="none",
+ user=User,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_failure{authentications = "password",
+ partial_success = false}},
+ {match, #ssh_msg_userauth_request{service="ssh-connection",
+ method="password",
+ user=User,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_userauth_success{}},
+ {match, #ssh_msg_channel_open{channel_type="session",
+ sender_channel=0,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_channel_open_confirmation{recipient_channel= 0,
+ sender_channel = 0,
+ initial_window_size = 64*1024,
+ maximum_packet_size = 32*1024
+ }},
+ {match, #ssh_msg_channel_open{channel_type="session",
+ sender_channel=1,
+ _='_'}, receive_msg},
+ {send, #ssh_msg_channel_open_confirmation{recipient_channel= 1,
+ sender_channel = 1,
+ initial_window_size = 64*1024,
+ maximum_packet_size = 32*1024}},
+ {match, #ssh_msg_channel_close{recipient_channel = 0}, receive_msg},
+ {match, disconnect(), receive_msg},
+ print_state],
+ InitialState)
+ end),
+ %% connect to it with a regular Erlang SSH client:
+ ChannelCloseTimeout = 3000,
+ {ok, ConnRef} = std_connect(HostPort, Config,
+ [{preferred_algorithms,[{kex,[?DEFAULT_KEX]},
+ {cipher,?DEFAULT_CIPHERS}
+ ]},
+ {channel_close_timeout, ChannelCloseTimeout},
+ {idle_time, 50000}
+ ]
+ ),
+ {ok, Channel0} = ssh_connection:session_channel(ConnRef, 50000),
+ {ok, _Channel1} = ssh_connection:session_channel(ConnRef, 50000),
+ %% Close the channel from client side, the server does not reply with 'channel-close'
+ %% After the timeout, the client should drop the cache entry
+ _ = ssh_connection:close(ConnRef, Channel0),
+ receive
+ after ChannelCloseTimeout + 1000 ->
+ {channels, Channels} = ssh:connection_info(ConnRef, channels),
+ ct:log("Channel entries ~p", [Channels]),
+ %% Only one channel entry should be present, the other one should be dropped
+ 1 = length(Channels),
+ ssh:close(ConnRef)
+ end.
%%%----------------------------------------------------------------
%%% For matching peer disconnection
@@ -1159,29 +1685,34 @@ find_handshake_parent(Port) ->
find_handshake_parent(supervisor:which_children(sshd_sup), Port, Acc).
-find_handshake_parent([{{server,ssh_system_sup,_,Port,default},
+find_handshake_parent([{{ssh_system_sup,{address,_,Port,_}},
Pid,supervisor, [ssh_system_sup]}|_],
Port, Acc) ->
find_handshake_parent(supervisor:which_children(Pid), Port, Acc);
-find_handshake_parent([{{ssh_acceptor_sup,_,Port,default},
+find_handshake_parent([{{ssh_acceptor_sup,{address,_,Port,_}},
PidS,supervisor,[ssh_acceptor_sup]}|T],
Port, {AccP,AccC,AccH}) ->
ParentHandshakers =
- [{PidW,PidH} || {{ssh_acceptor_sup,_,Port1,default}, PidW, worker, [ssh_acceptor]} <- supervisor:which_children(PidS),
- Port1 == Port,
- PidH <- element(2, process_info(PidW,links)),
- is_pid(PidH),
- process_info(PidH,current_function) == {current_function,{ssh_connection_handler,handshake,3}}],
+ [{PidW,PidH} ||
+ {{ssh_acceptor_sup,{address,_,Port1,_}}, PidW, worker,
+ [ssh_acceptor]} <- supervisor:which_children(PidS),
+ Port1 == Port,
+ PidH <- element(2, process_info(PidW,links)),
+ is_pid(PidH),
+ process_info(PidH,current_function) ==
+ {current_function,
+ {ssh_connection_handler,handshake,4}}],
{Parents,Handshakers} = lists:unzip(ParentHandshakers),
find_handshake_parent(T, Port, {AccP++Parents, AccC, AccH++Handshakers});
-find_handshake_parent([{_Ref,PidS,supervisor,[ssh_subsystem_sup]}|T], Port, {AccP,AccC,AccH}) ->
+find_handshake_parent([{_Ref,PidS,supervisor,[ssh_connection_sup]}|T],
+ Port, {AccP,AccC,AccH}) ->
Connections =
- [P || {{server,ssh_connection_sup,_,Port1}, Pid, supervisor, [ssh_connection_sup]} <- supervisor:which_children(PidS),
- Port == Port1,
- {undefined,P,worker,[ssh_connection_handler]} <- supervisor:which_children(Pid)],
- find_handshake_parent(T, Port, {AccP, AccC++Connections, AccH});
+ [Pid ||
+ {connection,Pid,worker,[ssh_connection_handler]} <-
+ supervisor:which_children(PidS)],
+ find_handshake_parent(T, Port, {AccP, AccC++Connections, AccH});
find_handshake_parent([_|T], Port, Acc) ->
find_handshake_parent(T, Port, Acc);
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftpd_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_sftpd_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftpd_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2025. 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.
@@ -43,6 +43,7 @@
open_file_dir_v6/1,
read_dir/1,
read_file/1,
+ max_path/1,
real_path/1,
relative_path/1,
relpath/1,
@@ -51,7 +52,6 @@
retrieve_attributes/1,
root_with_cwd/1,
set_attributes/1,
- sshd_read_file/1,
ver3_open_flags/1,
ver3_rename/1,
ver6_basic/1,
@@ -60,6 +60,7 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
+-include_lib("stdlib/include/assert.hrl").
-include("ssh_xfer.hrl").
-include("ssh.hrl").
-include("ssh_test_lib.hrl").
@@ -71,9 +72,9 @@
-define(SSH_TIMEOUT, 10000).
-define(REG_ATTERS, <<0,0,0,0,1>>).
-define(UNIX_EPOCH, 62167219200).
-
--define(is_set(F, Bits),
- ((F) band (Bits)) == (F)).
+-define(MAX_HANDLES, 10).
+-define(MAX_PATH, 200).
+-define(is_set(F, Bits), ((F) band (Bits)) == (F)).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -86,6 +87,7 @@ all() ->
[open_close_file,
open_close_dir,
read_file,
+ max_path,
read_dir,
write_file,
rename_file,
@@ -97,8 +99,7 @@ all() ->
links,
ver3_rename,
ver3_open_flags,
- relpath,
- sshd_read_file,
+ relpath,
ver6_basic,
access_outside_root,
root_with_cwd,
@@ -180,7 +181,9 @@ init_per_testcase(TestCase, Config) ->
{sftpd_vsn, 6}])],
ssh:daemon(0, [{subsystems, SubSystems}|Options]);
_ ->
- SubSystems = [ssh_sftpd:subsystem_spec([])],
+ SubSystems = [ssh_sftpd:subsystem_spec(
+ [{max_handles, ?MAX_HANDLES},
+ {max_path, ?MAX_PATH}])],
ssh:daemon(0, [{subsystems, SubSystems}|Options])
end,
@@ -316,33 +319,62 @@ open_close_dir(Config) when is_list(Conf
read_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
+ {Cm, Channel} = proplists:get_value(sftp, Config),
+ [begin
+ R1 = req_id(),
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(R1), Handle/binary>>, _} =
+ open_file(FileName, Cm, Channel, R1, ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ R2 = req_id(),
+ {ok, <<?SSH_FXP_DATA, ?UINT32(R2), ?UINT32(_Length), Data/binary>>, _} =
+ read_file(Handle, 100, 0, Cm, Channel, R2),
+ {ok, Data} = file:read_file(FileName)
+ end || _I <- lists:seq(0, ?MAX_HANDLES-1)],
+ ReqId = req_id(),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(?SSH_FX_FAILURE),
+ ?UINT32(MsgLen), Msg:MsgLen/binary,
+ ?UINT32(LangTagLen), _LangTag:LangTagLen/binary>>, _} =
+ open_file(FileName, Cm, Channel, ReqId, ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ ct:log("Message: ~s", [Msg]),
+ ok.
- ReqId = 0,
+%%--------------------------------------------------------------------
+max_path(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ FileName = filename:join(PrivDir, "test.txt"),
{Cm, Channel} = proplists:get_value(sftp, Config),
-
- {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
- open_file(FileName, Cm, Channel, ReqId,
+ %% verify max_path limit
+ LongFileName =
+ filename:join(PrivDir,
+ "t" ++ lists:flatten(lists:duplicate(?MAX_PATH, "e")) ++ "st.txt"),
+ {ok, _} = file:copy(FileName, LongFileName),
+ ReqId1 = req_id(),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId1), ?UINT32(?SSH_FX_NO_SUCH_PATH),
+ _/binary>>, _} =
+ open_file(LongFileName, Cm, Channel, ReqId1,
?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
- ?SSH_FXF_OPEN_EXISTING),
-
- NewReqId = 1,
-
- {ok, <<?SSH_FXP_DATA, ?UINT32(NewReqId), ?UINT32(_Length),
- Data/binary>>, _} =
- read_file(Handle, 100, 0, Cm, Channel, NewReqId),
-
- {ok, Data} = file:read_file(FileName).
+ ?SSH_FXF_OPEN_EXISTING).
%%--------------------------------------------------------------------
read_dir(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
{Cm, Channel} = proplists:get_value(sftp, Config),
- ReqId = 0,
- {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
- open_dir(PrivDir, Cm, Channel, ReqId),
- ok = read_dir(Handle, Cm, Channel, ReqId).
+ [begin
+ R1 = req_id(),
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(R1), Handle/binary>>, _} =
+ open_dir(PrivDir, Cm, Channel, R1),
+ R2 = req_id(),
+ ok = read_dir(Handle, Cm, Channel, R2)
+ end || _I <- lists:seq(0, ?MAX_HANDLES-1)],
+ ReqId = req_id(),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(?SSH_FX_FAILURE),
+ ?UINT32(MsgLen), Msg:MsgLen/binary,
+ ?UINT32(LangTagLen), _LangTag:LangTagLen/binary>>, _} =
+ open_dir(PrivDir, Cm, Channel, ReqId),
+ ct:log("Message: ~s", [Msg]),
+ ok.
-%%--------------------------------------------------------------------
write_file(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
@@ -388,35 +420,33 @@ rename_file(Config) when is_list(Config)
PrivDir = proplists:get_value(priv_dir, Config),
FileName = filename:join(PrivDir, "test.txt"),
NewFileName = filename:join(PrivDir, "test1.txt"),
- ReqId = 0,
+ LongFileName =
+ filename:join(PrivDir,
+ "t" ++ lists:flatten(lists:duplicate(?MAX_PATH, "e")) ++ "st.txt"),
{Cm, Channel} = proplists:get_value(sftp, Config),
-
- {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId),
- ?UINT32(?SSH_FX_OK), _/binary>>, _} =
- rename(FileName, NewFileName, Cm, Channel, ReqId, 6, 0),
-
- NewReqId = ReqId + 1,
-
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId),
- ?UINT32(?SSH_FX_OK), _/binary>>, _} =
- rename(NewFileName, FileName, Cm, Channel, NewReqId, 6,
- ?SSH_FXP_RENAME_OVERWRITE),
-
- NewReqId1 = NewReqId + 1,
- file:copy(FileName, NewFileName),
-
- %% No owerwrite
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId1),
- ?UINT32(?SSH_FX_FILE_ALREADY_EXISTS), _/binary>>, _} =
- rename(FileName, NewFileName, Cm, Channel, NewReqId1, 6,
- ?SSH_FXP_RENAME_NATIVE),
-
- NewReqId2 = NewReqId1 + 1,
-
- {ok, <<?SSH_FXP_STATUS, ?UINT32(NewReqId2),
- ?UINT32(?SSH_FX_OP_UNSUPPORTED), _/binary>>, _} =
- rename(FileName, NewFileName, Cm, Channel, NewReqId2, 6,
- ?SSH_FXP_RENAME_ATOMIC).
+ Version = 6,
+ [begin
+ case Action of
+ {Code, AFile, BFile, Flags} ->
+ ReqId = req_id(),
+ ct:log("ReqId = ~p,~nCode = ~p,~nAFile = ~p,~nBFile = ~p,~nFlags = ~p",
+ [ReqId, Code, AFile, BFile, Flags]),
+ {ok, <<?SSH_FXP_STATUS, ?UINT32(ReqId), ?UINT32(Code), _/binary>>, _} =
+ rename(AFile, BFile, Cm, Channel, ReqId, Version, Flags);
+ {file_copy, AFile, BFile} ->
+ {ok, _} = file:copy(AFile, BFile)
+ end
+ end ||
+ Action <-
+ [{?SSH_FX_OK, FileName, NewFileName, 0},
+ {?SSH_FX_OK, NewFileName, FileName, ?SSH_FXP_RENAME_OVERWRITE},
+ {file_copy, FileName, NewFileName},
+ %% no overwrite
+ {?SSH_FX_FILE_ALREADY_EXISTS, FileName, NewFileName, ?SSH_FXP_RENAME_NATIVE},
+ {?SSH_FX_OP_UNSUPPORTED, FileName, NewFileName, ?SSH_FXP_RENAME_ATOMIC},
+ %% max_path
+ {?SSH_FX_NO_SUCH_PATH, FileName, LongFileName, 0}]],
+ ok.
%%--------------------------------------------------------------------
mk_rm_dir(Config) when is_list(Config) ->
@@ -644,27 +674,6 @@ relpath(Config) when is_list(Config) ->
Root = Path
end.
-%%--------------------------------------------------------------------
-sshd_read_file(Config) when is_list(Config) ->
- PrivDir = proplists:get_value(priv_dir, Config),
- FileName = filename:join(PrivDir, "test.txt"),
-
- ReqId = 0,
- {Cm, Channel} = proplists:get_value(sftp, Config),
-
- {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
- open_file(FileName, Cm, Channel, ReqId,
- ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
- ?SSH_FXF_OPEN_EXISTING),
-
- NewReqId = 1,
-
- {ok, <<?SSH_FXP_DATA, ?UINT32(NewReqId), ?UINT32(_Length),
- Data/binary>>, _} =
- read_file(Handle, 100, 0, Cm, Channel, NewReqId),
-
- {ok, Data} = file:read_file(FileName).
-%%--------------------------------------------------------------------
ver6_basic(Config) when is_list(Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
%FileName = filename:join(PrivDir, "test.txt"),
@@ -685,7 +694,7 @@ access_outside_root(Config) when is_list
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 to access a file parallel 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).
@@ -717,7 +726,7 @@ try_access(Path, Cm, Channel, ReqId) ->
end
end;
_ ->
- ct:fail("Completly unexpected return: ~p", [Return])
+ ct:fail("Completely unexpected return: ~p", [Return])
end.
%%--------------------------------------------------------------------
@@ -728,25 +737,33 @@ root_with_cwd(Config) when is_list(Confi
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).
+
+ %% repeat procedure to make sure uniq file handles are generated
+ FileHandles =
+ [begin
+ ReqIdStr = integer_to_list(ReqId),
+ ok = file:write_file(FilePath ++ ReqIdStr, <<>>),
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(ReqId), Handle/binary>>, _} =
+ open_file(FileName ++ ReqIdStr, Cm, Channel, ReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ Handle
+ end ||
+ ReqId <- lists:seq(0,2)],
+ ?assertEqual(length(FileHandles),
+ length(lists:uniq(FileHandles))),
+ %% create a gap in file handles
+ [_, MiddleHandle, _] = FileHandles,
+ close(MiddleHandle, 3, Cm, Channel),
+
+ %% check that gap in file handles is is re-used
+ GapReqId = 4,
+ {ok, <<?SSH_FXP_HANDLE, ?UINT32(GapReqId), MiddleHandle/binary>>, _} =
+ open_file(FileName ++ integer_to_list(1), Cm, Channel, GapReqId,
+ ?ACE4_READ_DATA bor ?ACE4_READ_ATTRIBUTES,
+ ?SSH_FXF_OPEN_EXISTING),
+ ok.
%%--------------------------------------------------------------------
relative_path(Config) when is_list(Config) ->
@@ -1078,3 +1095,12 @@ encode_file_type(Type) ->
not_default_permissions() ->
8#600. %% User read-write-only
+
+req_id() ->
+ ReqId =
+ case get(req_id) of
+ undefined -> 0;
+ I -> I
+ end,
+ put(req_id, ReqId + 1),
+ ReqId.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftp_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_sftp_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftp_SUITE.erl
@@ -1,7 +1,7 @@
%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2025. 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.
@@ -55,6 +55,7 @@
pos_read/1,
pos_write/1,
position/1,
+ read_6GB/1,
read_crypto_tar/1,
read_dir/1,
read_file/1,
@@ -81,8 +82,10 @@
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
- % Default timetrap timeout
--define(default_timeout, ?t:minutes(1)).
+-include_lib("stdlib/include/assert.hrl").
+
+%% Default timetrap timeout
+-define(default_timeout, test_server:minutes(1)).
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
@@ -119,7 +122,9 @@ groups() ->
{unicode, [], [{group,erlang_server},
{group,openssh_server},
- sftp_nonexistent_subsystem]},
+ read_6GB,
+ sftp_nonexistent_subsystem
+ ]},
{big_recvpkt_size, [], [{group,erlang_server},
{group,openssh_server}]},
@@ -227,22 +232,7 @@ init_per_group(erlang_server, Config) ->
[{peer, {fmt_host(HostX),PortX}}, {group, erlang_server}, {sftpd, Sftpd} | Config];
init_per_group(openssh_server, Config) ->
- ct:comment("Begin ~p",[grps(Config)]),
- Host = ssh_test_lib:hostname(),
- case (catch ssh_sftp:start_channel(Host,
- [{user_interaction, false},
- {silently_accept_hosts, true}])) of
- {ok, _ChannelPid, Connection} ->
- [{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]),
- ssh:close(Connection),
- [{w2l, fun w2l/1},
- {peer, {fmt_host(IPx),Portx}}, {group, openssh_server} | Config];
- {error,"Key exchange failed"} ->
- {skip, "openssh server doesn't support the tested kex algorithm"};
- Other ->
- ct:log("No openssh server. Cause:~n~p~n",[Other]),
- {skip, "No openssh daemon (see log in testcase)"}
- end;
+ verify_openssh(Config);
init_per_group(remote_tar, Config) ->
ct:comment("Begin ~p",[grps(Config)]),
@@ -250,16 +240,16 @@ init_per_group(remote_tar, Config) ->
ct:log("Server (~p) at ~p:~p",[proplists:get_value(group,Config),Host,Port]),
User = proplists:get_value(user, Config),
Passwd = proplists:get_value(passwd, Config),
- {ok, Connection} =
+ Connection =
case proplists:get_value(group, Config) of
erlang_server ->
- ssh:connect(Host, Port,
+ ssh_test_lib:connect(Host, Port,
[{user, User},
{password, Passwd},
{user_interaction, false},
{silently_accept_hosts, true}]);
openssh_server ->
- ssh:connect(Host, Port,
+ ssh_test_lib:connect(Host, Port,
[{user_interaction, false},
{silently_accept_hosts, true}])
end,
@@ -284,7 +274,18 @@ end_per_group(_, Config) ->
Config.
%%--------------------------------------------------------------------
-
+init_per_testcase(read_6GB, Config) ->
+ case verify_openssh(Config) of
+ Result = {skip, _} ->
+ Result;
+ _ ->
+ case os:type() of
+ {win32, _} ->
+ {skip, "/dev/zero not available on Windws"};
+ _ ->
+ init_per_testcase(read_6GB_prepare_openssh_server, Config)
+ end
+ end;
init_per_testcase(sftp_nonexistent_subsystem, Config) ->
PrivDir = proplists:get_value(priv_dir, Config),
SysDir = proplists:get_value(data_dir, Config),
@@ -297,7 +298,6 @@ init_per_testcase(sftp_nonexistent_subsy
[{User, Passwd}]}
]),
[{sftpd, Sftpd} | Config];
-
init_per_testcase(version_option, Config0) ->
Config = prepare(Config0),
TmpConfig0 = lists:keydelete(watchdog, 1, Config),
@@ -312,10 +312,11 @@ init_per_testcase(version_option, Config
{user, User},
{password, Passwd},
{user_interaction, false},
- {silently_accept_hosts, true}]),
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
+ ]),
Sftp = {ChannelPid, Connection},
[{sftp,Sftp}, {watchdog, Dog} | TmpConfig];
-
init_per_testcase(Case, Config00) ->
Config0 = prepare(Config00),
Config1 = lists:keydelete(watchdog, 1, Config0),
@@ -327,16 +328,30 @@ init_per_testcase(Case, Config00) ->
undefined -> [];
Sz -> [{packet_size,Sz}]
end,
+ PrepareOpenSSHServer =
+ fun() ->
+ Host = ssh_test_lib:hostname(),
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host,
+ [{user_interaction, false},
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
+ | PktSzOpt
+ ]),
+ Sftp = {ChannelPid, Connection},
+ [{sftp, Sftp}, {watchdog, Dog} | Config2]
+ end,
Config =
case proplists:get_value(group,Config2) of
erlang_server ->
- {_,Host, Port} = proplists:get_value(sftpd, Config2),
- {ok, ChannelPid, Connection} =
+ {_,Host, Port} = proplists:get_value(sftpd, Config2),
+ {ok, ChannelPid, Connection} =
ssh_sftp:start_channel(Host, Port,
[{user, User},
{password, Passwd},
{user_interaction, false},
- {silently_accept_hosts, true}
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
| PktSzOpt
]
),
@@ -345,17 +360,10 @@ init_per_testcase(Case, Config00) ->
openssh_server when Case == links ->
{skip, "known bug in openssh"};
openssh_server ->
- Host = ssh_test_lib:hostname(),
- {ok, ChannelPid, Connection} =
- ssh_sftp:start_channel(Host,
- [{user_interaction, false},
- {silently_accept_hosts, true}
- | PktSzOpt
- ]),
- Sftp = {ChannelPid, Connection},
- [{sftp, Sftp}, {watchdog, Dog} | Config2]
+ PrepareOpenSSHServer();
+ _ when Case == read_6GB_prepare_openssh_server ->
+ PrepareOpenSSHServer()
end,
-
case catch proplists:get_value(remote_tar,Config) of
%% The 'catch' is for the case of Config={skip,...}
true ->
@@ -556,26 +564,62 @@ links(Config) when is_list(Config) ->
retrieve_attributes(Config) when is_list(Config) ->
FileName = proplists:get_value(filename, Config),
SftpFileName = w2l(Config, FileName),
-
{Sftp, _} = proplists:get_value(sftp, Config),
{ok, FileInfo} = ssh_sftp:read_file_info(Sftp, SftpFileName),
{ok, NewFileInfo} = file:read_file_info(FileName),
-
- %% TODO comparison. There are some differences now is that ok?
- ct:log("SFTP: ~p FILE: ~p~n", [FileInfo, NewFileInfo]).
+ ct:log("ssh_sftp:read_file_info(~p): ~p~n"
+ "file:read_file_info(~p): ~p",
+ [SftpFileName, FileInfo, FileName, NewFileInfo]),
+ {ExpectedUid, ExpectedGid} =
+ case {os:type(), proplists:get_value(group,Config)} of
+ {{win32, _}, openssh_server} ->
+ %% Windows compiled Erlang is expected will return 0;
+ %% but when Erlang(Windows) client interacts with
+ %% OpenSSH server - value 1000 is received by client
+ %% over SFTP (because OpenSSH is compiled for Linux
+ %% and runs on WSL)
+ {1000, 1000};
+ _ ->
+ {FileInfo#file_info.uid, FileInfo#file_info.gid}
+ end,
+ ?assertEqual(ExpectedUid, NewFileInfo#file_info.uid),
+ ?assertEqual(ExpectedGid, NewFileInfo#file_info.gid),
+ ok.
%%--------------------------------------------------------------------
set_attributes(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
SftpFileName = w2l(Config, FileName),
-
{Sftp, _} = proplists:get_value(sftp, Config),
{ok,Fd} = file:open(FileName, write),
io:put_chars(Fd,"foo"),
- ok = ssh_sftp:write_file_info(Sftp, SftpFileName, #file_info{mode=8#400}),
- {error, eacces} = file:write_file(FileName, "hello again"),
- ok = ssh_sftp:write_file_info(Sftp, SftpFileName, #file_info{mode=8#600}),
- ok = file:write_file(FileName, "hello again").
+ TestWriting =
+ fun(FInfo) ->
+ ok = ssh_sftp:write_file_info(Sftp, SftpFileName,
+ FInfo#file_info{mode=8#400}),
+ {error, eacces} = file:write_file(FileName, "hello again"),
+ ok = ssh_sftp:write_file_info(Sftp, SftpFileName,
+ FInfo#file_info{mode=8#600}),
+ ok = file:write_file(FileName, "hello again")
+ end,
+ TestWriting(#file_info{}),
+ IsErlangServer =
+ fun() ->
+ TcGroupPath = proplists:get_value(tc_group_path, Config),
+ {_, Path} = lists:unzip(lists:flatten(TcGroupPath)),
+ lists:member(erlang_server, Path)
+ end,
+ case IsErlangServer() of
+ true ->
+ ct:log("Testing with writing a complete #file_info record"),
+ {ok, FileInfo} = file:read_file_info(SftpFileName),
+ TestWriting(FileInfo);
+ _ ->
+ %% with OpenSSH daemon started by other user above instruction end
+ %% up with permission denied
+ ok
+ end,
+ ok.
%%--------------------------------------------------------------------
file_owner_access(Config) when is_list(Config) ->
@@ -669,6 +713,29 @@ position(Config) when is_list(Config) ->
{ok, 1} = ssh_sftp:position(Sftp, Handle, cur),
{ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).
+read_6GB(Config) when is_list(Config) ->
+ ct:timetrap(16*?default_timeout),
+ FileName = "/dev/zero",
+ SftpFileName = w2l(Config, FileName),
+ {SftpChannel, _ConnectionRef} = proplists:get_value(sftp, Config),
+ ChunkSize = 65535,
+ N = 100000,
+ {ok, Handle} = ssh_sftp:open(SftpChannel, SftpFileName, [read]),
+ ExpectedList = lists:duplicate(ChunkSize, 0),
+ [begin
+ MBTransferred = io_lib:format("~.2f", [I * ChunkSize / 1048576.0]),
+ case ssh_sftp:read(SftpChannel, Handle, ChunkSize, timer:minutes(1)) of
+ {ok, ExpectedList} ->
+ [ct:log("~n~s MB read~n", [MBTransferred]) || I rem 10000 == 0];
+ Result ->
+ ct:log("## After reading ~s MB~n## Unexpected result received = ~p",
+ [MBTransferred, Result]),
+ ct:fail(unexpected_reason)
+ end
+ end ||
+ I <- lists:seq(0, N)],
+ ok.
+
%%--------------------------------------------------------------------
pos_read(Config) when is_list(Config) ->
FileName = proplists:get_value(testfile, Config),
@@ -736,7 +803,8 @@ start_channel_sock(Config) ->
end,
Opts = [{user_interaction, false},
- {silently_accept_hosts, true}
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
| LoginOpts],
{Host,Port} = proplists:get_value(peer, Config),
@@ -775,7 +843,7 @@ start_channel_sock(Config) ->
%% Test that the socket is closed when the Connection closes
ok = ssh:close(Conn),
timer:sleep(400), %% Until the stop sequence is fixed
- {error,einval} = inet:getopts(Sock, [active]),
+ {error,_} = inet:getopts(Sock, [active]),
ok.
@@ -789,7 +857,9 @@ sftp_nonexistent_subsystem(Config) when
[{user_interaction, false},
{user, User},
{password, Passwd},
- {silently_accept_hosts, true}]).
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
+ ]).
%%--------------------------------------------------------------------
version_option(Config) when is_list(Config) ->
@@ -1224,4 +1294,22 @@ w2l(Config, P) ->
W2L = proplists:get_value(w2l, Config, fun(X) -> X end),
W2L(P).
-
+verify_openssh(Config) ->
+ ct:comment("Begin ~p",[grps(Config)]),
+ Host = ssh_test_lib:hostname(),
+ case (catch ssh_sftp:start_channel(Host,
+ [{user_interaction, false},
+ {silently_accept_hosts, true},
+ {save_accepted_host, false}
+ ])) of
+ {ok, _ChannelPid, Connection} ->
+ [{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]),
+ ssh:close(Connection),
+ [{w2l, fun w2l/1},
+ {peer, {fmt_host(IPx),Portx}}, {group, openssh_server} | Config];
+ {error,"Key exchange failed"} ->
+ {skip, "openssh server doesn't support the tested kex algorithm"};
+ Other ->
+ ct:log("No openssh server. Cause:~n~p~n",[Other]),
+ {skip, "No openssh daemon (see log in testcase)"}
+ end.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_sup_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_sup_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_sup_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2024. 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.
@@ -24,8 +24,7 @@
-include("ssh.hrl").
-include("ssh_test_lib.hrl").
--export([
- suite/0,
+-export([suite/0,
all/0,
groups/0,
init_per_suite/1,
@@ -33,38 +32,42 @@
init_per_group/2,
end_per_group/2,
init_per_testcase/2,
- end_per_testcase/2
- ]).
+ end_per_testcase/2]).
--export([
- default_tree/1,
+-export([default_tree/1,
killed_acceptor_restarts/1,
shell_channel_tree/1,
sshc_subtree/1,
sshd_subtree/1,
- sshd_subtree_profile/1
- ]).
+ sshd_subtree_profile/1]).
-define(USER, "Alladin").
-define(PASSWD, "Sesame").
-define(WAIT_FOR_SHUTDOWN, 500).
+-define(SSHC_SUP(Pid), {sshc_sup, Pid, supervisor, [supervisor]}).
+-define(SSHD_SUP(Pid), {sshd_sup, Pid, supervisor, [supervisor]}).
+-define(SYSTEM_SUP(Pid,Address),
+ {{ssh_system_sup, Address}, Pid, supervisor,[ssh_system_sup]}).
+-define(CONNECTION_SUP(Pid), {_,Pid, supervisor,[ssh_connection_sup]}).
+-define(ACCEPTOR_SUP(Pid,Address),
+ {{ssh_acceptor_sup,Address},Pid,supervisor,[ssh_acceptor_sup]}).
+-define(ACCEPTOR_WORKER(Pid,Address),
+ {{ssh_acceptor_sup,Address},Pid,worker,[ssh_acceptor]}).
+
%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
-
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap,{seconds,100}}].
-all() ->
+all() ->
[default_tree, sshc_subtree, sshd_subtree, sshd_subtree_profile,
- killed_acceptor_restarts,
- shell_channel_tree
- ].
+ killed_acceptor_restarts, shell_channel_tree].
-groups() ->
+groups() ->
[].
init_per_group(_GroupName, Config) ->
@@ -85,73 +88,67 @@ init_per_suite(Config) ->
end_per_suite(_) ->
ok.
-init_per_testcase(sshc_subtree, Config) ->
+init_per_testcase(sshc_subtree, Config) ->
ssh:start(),
SystemDir = proplists:get_value(data_dir, Config),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
- {failfun, fun ssh_test_lib:failfun/2},
- {user_passwords,
- [{?USER, ?PASSWD}]}]),
+ {failfun, fun ssh_test_lib:failfun/2},
+ {user_passwords,
+ [{?USER, ?PASSWD}]}]),
[{server, {Pid, Host, Port}} | Config];
init_per_testcase(Case, Config) ->
end_per_testcase(Case, Config),
ssh:start(),
Config.
end_per_testcase(sshc_subtree, Config) ->
- {Pid,_,_} = proplists:get_value(server, Config),
+ {Pid,_,_} = proplists:get_value(server, Config),
ssh:stop_daemon(Pid),
ssh:stop();
end_per_testcase(_, _Config) ->
ssh:stop().
%%-------------------------------------------------------------------------
-%% Test cases
+%% Test cases
%%-------------------------------------------------------------------------
default_tree(Config) when is_list(Config) ->
TopSupChildren = supervisor:which_children(ssh_sup),
2 = length(TopSupChildren),
- {value, {sshc_sup, _, supervisor,[sshc_sup]}} =
- lists:keysearch(sshc_sup, 1, TopSupChildren),
- {value, {sshd_sup, _,supervisor,[sshd_sup]}} =
- lists:keysearch(sshd_sup, 1, TopSupChildren),
- ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)),
+ {value, ?SSHC_SUP(_)} = lists:keysearch(sshc_sup, 1, TopSupChildren),
+ {value, ?SSHD_SUP(_)} = lists:keysearch(sshd_sup, 1, TopSupChildren),
+ ?wait_match([], supervisor:which_children(sshc_sup)),
?wait_match([], supervisor:which_children(sshd_sup)).
%%-------------------------------------------------------------------------
sshc_subtree(Config) when is_list(Config) ->
{_Pid, Host, Port} = proplists:get_value(server, Config),
UserDir = proplists:get_value(userdir, Config),
-
- ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)),
-
- {ok, Pid1} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
- {user_interaction, false},
- {user, ?USER}, {password, ?PASSWD},{user_dir, UserDir}]),
-
- ?wait_match([{{client,ssh_system_sup, LocalIP, LocalPort, ?DEFAULT_PROFILE},
- SysSup, supervisor,[ssh_system_sup]},
- {client_controller,_,worker,_}
- ],
+ ?wait_match([], supervisor:which_children(sshc_sup)),
+ Pid1 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
+ ?wait_match([?CONNECTION_SUP(ConnectionSup)],
supervisor:which_children(sshc_sup),
- [SysSup, LocalIP, LocalPort]),
- check_sshc_system_tree(SysSup, Pid1, LocalIP, LocalPort, Config),
-
- {ok, Pid2} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
- {user_interaction, false},
- {user, ?USER}, {password, ?PASSWD}, {user_dir, UserDir}]),
- ?wait_match([{_, _,supervisor,[ssh_system_sup]},
- {_, _,supervisor,[ssh_system_sup]},
- {client_controller,_,worker,_}
+ [ConnectionSup]),
+ check_sshc_system_tree(ConnectionSup, Pid1, Config),
+ Pid2 = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
+ ?wait_match([?CONNECTION_SUP(_),
+ ?CONNECTION_SUP(_)
],
supervisor:which_children(sshc_sup)),
-
ssh:close(Pid1),
- ?wait_match([{_, _,supervisor,[ssh_system_sup]},
- {client_controller,_,worker,_}
+ ?wait_match([?CONNECTION_SUP(_)
],
supervisor:which_children(sshc_sup)),
ssh:close(Pid2),
- ?wait_match([{client_controller,_,worker,_}], supervisor:which_children(sshc_sup)).
+ ?wait_match([], supervisor:which_children(sshc_sup)).
%%-------------------------------------------------------------------------
sshd_subtree(Config) when is_list(Config) ->
@@ -160,11 +157,10 @@ sshd_subtree(Config) when is_list(Config
{failfun, fun ssh_test_lib:failfun/2},
{user_passwords,
[{?USER, ?PASSWD}]}]),
-
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]}],
+ ?wait_match([?SYSTEM_SUP(Daemon, #address{address=ListenIP,
+ port=Port,
+ profile=?DEFAULT_PROFILE})],
supervisor:which_children(sshd_sup),
[ListenIP,Daemon]),
true = ssh_test_lib:match_ip(HostIP, ListenIP),
@@ -175,18 +171,17 @@ sshd_subtree(Config) when is_list(Config
%%-------------------------------------------------------------------------
sshd_subtree_profile(Config) when is_list(Config) ->
- Profile = proplists:get_value(profile, Config),
+ Profile = proplists:get_value(profile, Config),
SystemDir = proplists:get_value(data_dir, Config),
-
{Daemon, HostIP, Port} = ssh_test_lib:daemon([{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]}],
+ ?wait_match([?SYSTEM_SUP(Daemon, #address{address=ListenIP,
+ port=Port,
+ profile=Profile})],
supervisor:which_children(sshd_sup),
[ListenIP,Daemon]),
true = ssh_test_lib:match_ip(HostIP, ListenIP),
@@ -197,7 +192,7 @@ sshd_subtree_profile(Config) when is_lis
%%-------------------------------------------------------------------------
killed_acceptor_restarts(Config) ->
- Profile = proplists:get_value(profile, 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},
@@ -220,7 +215,8 @@ killed_acceptor_restarts(Config) ->
true = (AccPid /= AccPid2),
%% Connect first client and check it is alive:
- {ok,C1} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
+ C1 = ssh_test_lib:connect("localhost", Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_interaction, false},
{user, ?USER},
{password, ?PASSWD},
@@ -247,6 +243,7 @@ killed_acceptor_restarts(Config) ->
%% Connect second client and check it is alive:
C2 =
case ssh:connect("localhost", Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_interaction, false},
{user, ?USER},
{password, ?PASSWD},
@@ -259,17 +256,17 @@ killed_acceptor_restarts(Config) ->
end,
[{client_version,_}] = ssh:connection_info(C2,[client_version]),
-
+
ct:log("~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),
?wait_match(undefined, process_info(DaemonPid2), 1000, 30),
[{client_version,_}] = ssh:connection_info(C1,[client_version]),
[{client_version,_}] = ssh:connection_info(C2,[client_version]),
-
+
ok = ssh:stop_daemon(DaemonPid),
?wait_match(undefined, process_info(DaemonPid), 1000, 30),
?wait_match({error,closed}, ssh:connection_info(C1,[client_version]), 1000, 5),
@@ -301,25 +298,36 @@ shell_channel_tree(Config) ->
{user_interaction, true},
{user_dir, UserDir}]),
- [ChannelSup|_] = Sups0 = chk_empty_con_daemon(Daemon),
-
+ [ConnectionSup,_ChPid|_] = Sups0 = chk_empty_con_daemon(Daemon),
+
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh_connection:shell(ConnectionRef,ChannelId0),
success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId0, [{pty_opts,[{onlcr,1}]}]),
- ?wait_match([{_, GroupPid,worker,[ssh_server_channel]}],
- supervisor:which_children(ChannelSup),
- [GroupPid]),
+ ?wait_match([{_,_, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor,[ssh_channel_sup]},
+ {connection,_,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup]),
+ ?wait_match([{_,GroupPid,worker,[ssh_server_channel]}
+ ],
+ supervisor:which_children(ChSup),
+ [GroupPid]),
+
+
{links,GroupLinks} = erlang:process_info(GroupPid, links),
- [ShellPid] = GroupLinks--[ChannelSup],
+ ct:log("GroupPid = ~p, GroupLinks = ~p Sups0 = ~p",[GroupPid,GroupLinks,Sups0]),
+ [ShellPid] = GroupLinks--[ChSup],
ct:log("GroupPid = ~p, ShellPid = ~p",[GroupPid,ShellPid]),
receive
- {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"TimeoutShell started!\r\n">>}} ->
+ {ssh_cm,ConnectionRef, {data, ChannelId0, 0, <<"TimeoutShell started!",Rest/binary>>}} ->
+ ct:log("TimeoutShell started. Rest = ~p", [Rest]),
receive
- %%---- wait for the subsystem to terminate
+ %%---- wait for the connection to terminate
{ssh_cm,ConnectionRef,{closed,ChannelId0}} ->
- ct:log("Subsystem terminated",[]),
+ ct:log("Connection terminated",[]),
case {chk_empty_con_daemon(Daemon),
process_info(GroupPid),
process_info(ShellPid)} of
@@ -337,123 +345,137 @@ shell_channel_tree(Config) ->
ct:fail("Sup tree changed!")
end
after 10000 ->
+ ct:log("~p:~p Flush unexpected: ~p", [?MODULE,?LINE,flush_rest()]),
ssh:close(ConnectionRef),
ssh:stop_daemon(Daemon),
- ct:fail("CLI Timeout")
+ ct:fail("CLI Timeout 1")
end
after 10000 ->
+ ct:log("~p:~p Flush unexpected: ~p", [?MODULE,?LINE,flush_rest()]),
ssh:close(ConnectionRef),
ssh:stop_daemon(Daemon),
- ct:fail("CLI Timeout")
+ ct:fail("CLI Timeout 2")
end.
-
chk_empty_con_daemon(Daemon) ->
- ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
- {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
+ ?wait_match([?CONNECTION_SUP(ConnectionSup),
+ ?ACCEPTOR_SUP(AccSup,_)
+ ],
supervisor:which_children(Daemon),
- [SubSysSup,AccSup]),
- ?wait_match([{_,_, supervisor,
- [ssh_tcpip_forward_acceptor_sup]},
- {{server,ssh_connection_sup, _,_},
- ConnectionSup, supervisor,
- [ssh_connection_sup]},
- {{server,ssh_channel_sup,_ ,_},
- ChannelSup,supervisor,
- [ssh_channel_sup]}],
- supervisor:which_children(SubSysSup),
- [ConnectionSup,ChannelSup]),
- ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
- supervisor:which_children(AccSup)),
- ?wait_match([{_, _, worker,[ssh_connection_handler]}],
- supervisor:which_children(ConnectionSup)),
- ?wait_match([], supervisor:which_children(ChannelSup)),
- [ChannelSup, ConnectionSup, SubSysSup, AccSup].
+ [ConnectionSup,AccSup]),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor,[ssh_channel_sup]},
+ {connection,ServerConnPid,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,FwdAccSup,ServerConnPid]),
+ ?wait_match([], supervisor:which_children(FwdAccSup)),
+ ?wait_match([], supervisor:which_children(ChSup)),
+ ?wait_match([?ACCEPTOR_WORKER(_,_)],
+ supervisor:which_children(AccSup),
+ []),
+ [ConnectionSup, ChSup, ServerConnPid, AccSup, FwdAccSup].
%%-------------------------------------------------------------------------
%% Help functions
%%-------------------------------------------------------------------------
-check_sshd_system_tree(Daemon, Host, Port, Config) ->
+check_sshd_system_tree(Daemon, Host, 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}]),
-
- ?wait_match([{_,SubSysSup, supervisor,[ssh_subsystem_sup]},
- {{ssh_acceptor_sup,_,_,_}, AccSup, supervisor,[ssh_acceptor_sup]}],
+ ClientConn = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {user_dir, UserDir}]),
+ ?wait_match([?CONNECTION_SUP(ConnectionSup),
+ ?ACCEPTOR_SUP(AccSup,_)],
supervisor:which_children(Daemon),
- [SubSysSup,AccSup]),
-
- ?wait_match([{_,
- _AcceptorSup, supervisor,
- [ssh_tcpip_forward_acceptor_sup]},
- {{server,ssh_connection_sup, _,_},
- ConnectionSup, supervisor,
- [ssh_connection_sup]},
- {{server,ssh_channel_sup,_ ,_},
- ChannelSup,supervisor,
- [ssh_channel_sup]}],
- supervisor:which_children(SubSysSup),
- [ConnectionSup,ChannelSup]),
-
- ?wait_match([{{ssh_acceptor_sup,_,_,_},_,worker,[ssh_acceptor]}],
- supervisor:which_children(AccSup)),
-
- ?wait_match([{_, _, worker,[ssh_connection_handler]}],
- supervisor:which_children(ConnectionSup)),
-
- ?wait_match([], supervisor:which_children(ChannelSup)),
-
- {ok,PidC} = ssh_sftp:start_channel(Client),
+ [ConnectionSup,AccSup]),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,_,supervisor,[ssh_channel_sup]},
+ {connection,ServerConn,worker,[ssh_connection_handler]}],
+ supervisor:which_children(ConnectionSup),
+ [FwdAccSup,ServerConn]),
+ ?wait_match([], supervisor:which_children(FwdAccSup)),
+ ?wait_match([?ACCEPTOR_WORKER(_,_)], supervisor:which_children(AccSup)),
+ {ok,PidC} = ssh_sftp:start_channel(ClientConn),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor,[ssh_channel_sup]},
+ {connection,ServerConn,worker,[ssh_connection_handler]}],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,ServerConn]),
- ?wait_match([{_, PidS,worker,[ssh_server_channel]}],
- supervisor:which_children(ChannelSup),
+ ?wait_match([{_,PidS,worker,[ssh_server_channel]}],
+ supervisor:which_children(ChSup),
[PidS]),
true = (PidS =/= PidC),
-
- ssh:close(Client).
+ ?wait_match([], supervisor:which_children(FwdAccSup)),
+ ssh:close(ClientConn).
-check_sshc_system_tree(SysSup, Connection, LocalIP, LocalPort, _Config) ->
- ?wait_match([{_,SubSysSup,supervisor,[ssh_subsystem_sup]}],
- supervisor:which_children(SysSup),
- [SubSysSup]),
- ?wait_match([{_,FwdAccSup, supervisor,
- [ssh_tcpip_forward_acceptor_sup]},
- {{client,ssh_connection_sup, LocalIP, LocalPort},
- ConnectionSup, supervisor,
- [ssh_connection_sup]},
- {{client,ssh_channel_sup, LocalIP, LocalPort},
- ChannelSup,supervisor,
- [ssh_channel_sup]}],
- supervisor:which_children(SubSysSup),
- [ConnectionSup,ChannelSup,FwdAccSup]),
- ?wait_match([{_, Connection, worker,[ssh_connection_handler]}],
- supervisor:which_children(ConnectionSup)),
- ?wait_match([], supervisor:which_children(ChannelSup)),
+check_sshc_system_tree(ConnectionSup, Connection, _Config) ->
+ ?wait_match([?CONNECTION_SUP(ConnectionSup)],
+ supervisor:which_children(sshc_sup),
+ [ConnectionSup]),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,_,supervisor,[ssh_channel_sup]},
+ {connection,Connection,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [FwdAccSup]),
?wait_match([], supervisor:which_children(FwdAccSup)),
{ok,ChPid1} = ssh_sftp:start_channel(Connection),
- ?wait_match([{_,ChPid1,worker,[ssh_client_channel]}],
- supervisor:which_children(ChannelSup)),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor, [ssh_channel_sup]},
+ {connection,Connection,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,FwdAccSup]),
+
+ ?wait_match([{_,ChPid1,worker,[ssh_client_channel]}
+ ],
+ supervisor:which_children(ChSup),
+ [ChPid1]),
{ok,ChPid2} = ssh_sftp:start_channel(Connection),
- ?wait_match([{_,ChPidA,worker,[ssh_client_channel]},
- {_,ChPidB,worker,[ssh_client_channel]}],
- supervisor:which_children(ChannelSup),
- [ChPidA, ChPidB]),
- true = (lists:sort([ChPidA, ChPidB]) == lists:sort([ChPid1, ChPid2])),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor, [ssh_channel_sup]},
+ {connection,Connection,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,FwdAccSup]),
+
+ ?wait_match([{_,ChPid2,worker,[ssh_client_channel]},
+ {_,ChPid1,worker,[ssh_client_channel]}
+ ],
+ supervisor:which_children(ChSup),
+ [ChPid1,ChPid2]),
ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid1]),
exit(ChPid1, kill),
- ?wait_match([{_,ChPid2,worker,[ssh_client_channel]}],
- supervisor:which_children(ChannelSup)),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor, [ssh_channel_sup]},
+ {connection,Connection,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,FwdAccSup]),
+
+ ?wait_match([{_,ChPid2,worker,[ssh_client_channel]}
+ ],
+ supervisor:which_children(ChSup),
+ [ChPid2]),
ct:pal("Expect a SUPERVISOR REPORT with offender {pid,~p}....~n", [ChPid2]),
exit(ChPid2, kill),
- ?wait_match([], supervisor:which_children(ChannelSup)),
+ ?wait_match([{_,FwdAccSup, supervisor,[ssh_tcpip_forward_acceptor_sup]},
+ {_,ChSup,supervisor, [ssh_channel_sup]},
+ {connection,Connection,worker,[ssh_connection_handler]}
+ ],
+ supervisor:which_children(ConnectionSup),
+ [ChSup,FwdAccSup]),
+
+ ?wait_match([], supervisor:which_children(ChSup)),
+
ct:pal("... now there should not be any SUPERVISOR REPORT.~n", []).
@@ -464,21 +486,17 @@ acceptor_pid(DaemonPid) ->
Parent ! {self(), supsearch,
[{AccPid,ListenAddr,Port}
- || {{server,ssh_system_sup,ListenAddr,Port,NS},
- DPid,supervisor,
- [ssh_system_sup]} <- supervisor:which_children(sshd_sup),
+ || ?SYSTEM_SUP(DPid,#address{address=ListenAddr,port=Port,profile=NS})
+ <- 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),
+ ?ACCEPTOR_SUP(AccSupPid,_)
+ <- supervisor:which_children(DaemonPid),
+
+ ?ACCEPTOR_WORKER(AccPid, #address{address=L2,
+ port=P2,
+ profile=NS2})
+ <- supervisor:which_children(AccSupPid),
L2 == ListenAddr,
P2 == Port,
NS2 == NS]}
@@ -487,3 +505,10 @@ acceptor_pid(DaemonPid) ->
after 2000 -> timeout
end.
+%%%----------------------------------------------------------------
+flush_rest() -> lists:reverse(flush_rest([])).
+
+flush_rest(Acc) ->
+ receive Any -> [Any|Acc]
+ after 0 -> Acc
+ end.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_test_lib.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_test_lib.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_test_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2025. 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.
@@ -30,6 +30,7 @@ daemon/2,
daemon/3,
daemon_port/1,
daemon_port/2,
+gen_tcp_connect/2,
gen_tcp_connect/3,
open_sshc/3,
open_sshc/4,
@@ -63,6 +64,7 @@ del_dirs/1,
del_dir_contents/1,
do_del_files/2,
openssh_sanity_check/1,
+verify_sanity_check/1,
default_algorithms/1,
default_algorithms/3,
default_algorithms/2,
@@ -120,26 +122,70 @@ setup_host_key_create_dir/3,
setup_host_key/3,
setup_known_host/3,
get_addr_str/0,
-file_base_name/2
+file_base_name/2,
+kex_strict_negotiated/2,
+event_logged/3
]).
+%% logger callbacks and related helpers
+-export([log/2,
+ get_log_level/0, set_log_level/1, add_log_handler/0,
+ rm_log_handler/0, get_log_events/1]).
-include_lib("common_test/include/ct.hrl").
-include("ssh_transport.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
+-define(SANITY_CHECK_NOTE,
+ "For enabling test, make sure following commands work:~n"
+ "ok = ssh:start(), "
+ "{ok, _} = ssh:connect(\"localhost\", 22, "
+ "[{password,\"\"},{silently_accept_hosts, true}, "
+ "{save_accepted_host, false}, {user_interaction, false}]).").
+
%%%----------------------------------------------------------------
connect(Port, Options) when is_integer(Port) ->
connect(hostname(), Port, Options).
connect(any, Port, Options) ->
connect(hostname(), Port, Options);
-connect(Host, Port, Options) ->
+
+connect(Host, ?SSH_DEFAULT_PORT, Options0) ->
+ Options =
+ set_opts_if_not_set([{silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user_interaction, false}
+ ], Options0),
+ do_connect(Host, ?SSH_DEFAULT_PORT, Options);
+
+connect(Host, Port, Options0) ->
+ Options =
+ case proplists:get_value(user_dir,Options0) of
+ undefined ->
+ %% Avoid uppdating the known_hosts if it is the default one
+ set_opts_if_not_set([{save_accepted_host, false}], Options0);
+ _ ->
+ Options0
+ end,
+ do_connect(Host, Port, Options).
+
+
+do_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.
+set_opts_if_not_set(OptsToSet, Options0) ->
+ lists:foldl(fun({K,V}, Opts) ->
+ case proplists:get_value(K, Opts) of
+ undefined ->
+ [{K,V} | Opts];
+ _ ->
+ Opts
+ end
+ end, Options0, OptsToSet).
+
%%%----------------------------------------------------------------
daemon(Options) ->
daemon(any, 0, Options).
@@ -174,6 +220,9 @@ daemon_port(0, Pid) -> {ok,Dinf} = ssh:d
daemon_port(Port, _) -> Port.
%%%----------------------------------------------------------------
+gen_tcp_connect(Port, Options) ->
+ gen_tcp_connect("localhost", Port, Options).
+
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",
@@ -244,8 +293,7 @@ std_simple_sftp(Host, Port, Config, Opts
Data = crypto:strong_rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)),
ok = ssh_sftp:write_file(ChannelRef, DataFile, Data),
{ok,ReadData} = file:read_file(DataFile),
- ok = ssh:close(ConnectionRef),
- Data == ReadData.
+ {Data == ReadData, ConnectionRef}.
%%%----------------------------------------------------------------
std_simple_exec(Host, Port, Config) ->
@@ -284,7 +332,9 @@ start_shell(Port, IOServer, ExtraOptions
ct:log("~p:~p:~p ssh_test_lib:start_shell(~p, ~p, ~p)",
[?MODULE,?LINE,self(), Port, IOServer, ExtraOptions]),
Options = [{user_interaction, false},
- {silently_accept_hosts,true} | ExtraOptions],
+ {silently_accept_hosts,true},
+ {save_accepted_host,false}
+ | ExtraOptions],
try
group_leader(IOServer, self()),
case Port of
@@ -298,17 +348,17 @@ start_shell(Port, IOServer, ExtraOptions
ct:log("is_integer(Port) Call ssh:shell(~p, ~p, ~p)",
[Host, Port, Options]),
ssh:shell(Host, Port, Options);
- Socket when is_port(Socket) ->
+ ConnRef when is_pid(ConnRef) ->
+ ct:log("is_pid(ConnRef) Call ssh:shell(~p)",
+ [ConnRef]),
+ ssh:shell(ConnRef); % Options were given in ssh:connect
+ Socket ->
receive
start -> ok
end,
- ct:log("is_port(Socket) Call ssh:shell(~p, ~p)",
+ ct:log("Socket Call ssh:shell(~p, ~p)",
[Socket, Options]),
- ssh:shell(Socket, Options);
- ConnRef when is_pid(ConnRef) ->
- ct:log("is_pid(ConnRef) Call ssh:shell(~p)",
- [ConnRef]),
- ssh:shell(ConnRef) % Options were given in ssh:connect
+ ssh:shell(Socket, Options)
end
of
R ->
@@ -448,7 +498,7 @@ receive_exec_result(Msgs) when is_list(M
receive_exec_result(Msgs);
Other ->
ct:log("~p:~p unexpected Other ~p", [?MODULE,?FUNCTION_NAME,Other]),
- {unexpected_msg, Other}
+ receive_exec_result(Msgs)
end
end
after
@@ -526,20 +576,35 @@ do_del_files(Dir, Files) ->
end
end, Files).
-
openssh_sanity_check(Config) ->
ssh:start(),
- case ssh:connect("localhost", 22, [{password,""},
- {save_accepted_host, false},
- {silently_accept_hosts, true} ]) of
+ case ssh:connect("localhost", ?SSH_DEFAULT_PORT,
+ [{password,""},
+ {silently_accept_hosts, true},
+ {save_accepted_host, false},
+ {user_interaction, false}
+ ]) of
{ok, Pid} ->
ssh:close(Pid),
ssh:stop(),
- Config;
+ [{sanity_check_result, ok} | Config];
Err ->
Str = lists:append(io_lib:format("~p", [Err])),
+ ct:log("Error = ~p", [Err]),
+ ct:log(?SANITY_CHECK_NOTE),
ssh:stop(),
- {skip, Str}
+ [{sanity_check_result, Str} | Config]
+ end.
+
+verify_sanity_check(Config) ->
+ SanityCheckResult = proplists:get_value(sanity_check_result, Config, ok),
+ case SanityCheckResult of
+ ok ->
+ Config;
+ Err ->
+ ct:log("Error = ~p", [Err]),
+ ct:log(?SANITY_CHECK_NOTE),
+ {fail, passwordless_connection_failed}
end.
%%%--------------------------------------------------------------------
@@ -555,6 +620,7 @@ default_algorithms(sshd, Host, Port) ->
try run_fake_ssh(
ssh_trpt_test_lib:exec(
[{connect,Host,Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_interaction, false}]}]))
catch
_C:_E ->
@@ -587,14 +653,14 @@ default_algorithms(sshc, DaemonOptions)
{hostport,Srvr,{_Host,Port}} ->
spawn(fun()-> os:cmd(lists:concat(["ssh -o \"StrictHostKeyChecking no\" -p ",Port," localhost"])) end)
after ?TIMEOUT ->
- ct:fail("No server respons (timeout) 1")
+ ct:fail("No server response (timeout) 1")
end,
receive
{result,Srvr,L} ->
L
after ?TIMEOUT ->
- ct:fail("No server respons (timeout) 2")
+ ct:fail("No server response (timeout) 2")
end.
run_fake_ssh({ok,InitialState}) ->
@@ -900,7 +966,7 @@ create_random_dir(Config) ->
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
+ %% The likelihood of always generating an existing file name is low
create_random_dir(Config)
end.
@@ -1227,3 +1293,83 @@ file_base_name(system_src, 'ecdsa-sha2-n
file_base_name(system_src, Alg) -> file_base_name(system, Alg).
%%%----------------------------------------------------------------
+-define(SEARCH_FUN(EXP),
+ begin
+ fun(#{msg := {string, EXP},
+ level := debug}) ->
+ true;
+ (_) ->
+ false
+ end
+ end).
+-define(SEARCH_SUFFIX, " will use strict KEX ordering").
+
+kex_strict_negotiated(client, Events) ->
+ kex_strict_negotiated(?SEARCH_FUN("client" ++ ?SEARCH_SUFFIX), Events);
+kex_strict_negotiated(server, Events) ->
+ kex_strict_negotiated(?SEARCH_FUN("server" ++ ?SEARCH_SUFFIX), Events);
+kex_strict_negotiated(SearchFun, Events) when is_function(SearchFun) ->
+ %% FIXME use event_logged?
+ case lists:search(SearchFun, Events) of
+ {value, _} -> true;
+ _ -> false
+ end.
+
+event_logged(Role, Events, Reason) ->
+ SearchF =
+ fun(#{msg := {report, #{args := Args}}}) ->
+ AnyF = fun (E) when is_list(E) ->
+ case string:find(E, Reason) of
+ nomatch -> false;
+ _ -> true
+ end;
+ (_) ->
+ false
+ end,
+ lists:member(Role, Args) andalso
+ lists:any(AnyF, Args);
+ (_Event) ->
+ false
+ end,
+ case lists:search(SearchF, Events) of
+ {value, _} -> true;
+ _ -> false
+ end.
+
+get_log_level() ->
+ #{level := Level} = logger:get_primary_config(),
+ Level.
+
+set_log_level(Level) ->
+ ok = logger:set_primary_config(level, Level).
+
+add_log_handler() ->
+ logger:remove_handler(?MODULE),
+ TestRef = make_ref(),
+ ok = logger:add_handler(?MODULE, ?MODULE,
+ #{level => debug,
+ filter_default => log,
+ recipient => self(),
+ test_ref => TestRef}),
+ {ok, TestRef}.
+
+rm_log_handler() ->
+ ok = logger:remove_handler(?MODULE).
+
+get_log_events(TestRef) ->
+ {ok, get_log_events(TestRef, [])}.
+
+get_log_events(TestRef, Acc) ->
+ receive
+ {TestRef, Event} ->
+ get_log_events(TestRef, [Event | Acc])
+ after
+ 500 ->
+ Acc
+ end.
+
+%% logger callbacks
+log(LogEvent = #{level:=_Level,msg:=_Msg,meta:=_Meta},
+ #{test_ref := TestRef, recipient := Recipient}) ->
+ Recipient ! {TestRef, LogEvent},
+ ok.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_to_openssh_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_to_openssh_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_to_openssh_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2024. 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.
@@ -23,6 +23,7 @@
-include_lib("common_test/include/ct.hrl").
-include("ssh_test_lib.hrl").
+-include("ssh_transport.hrl").
-export([
suite/0,
@@ -38,7 +39,9 @@
-export([
erlang_server_openssh_client_renegotiate/1,
+ eserver_oclient_kex_strict/1,
erlang_shell_client_openssh_server/1,
+ eclient_oserver_kex_strict/1,
exec_direct_with_io_in_sshc/1,
exec_with_io_in_sshc/1,
tunnel_in_erlclient_erlserver/1,
@@ -50,7 +53,6 @@
]).
--define(SSH_DEFAULT_PORT, 22).
-define(REKEY_DATA_TMO, 65000).
%%--------------------------------------------------------------------
@@ -66,7 +68,7 @@ all() ->
{skip, "openSSH not installed on host"};
_ ->
[{group, erlang_client},
- {group, erlang_server}
+ {group, erlang_server}
]
end.
@@ -74,29 +76,31 @@ groups() ->
[{erlang_client, [], [tunnel_in_erlclient_erlserver,
tunnel_out_erlclient_erlserver,
{group, tunnel_distro_server},
- erlang_shell_client_openssh_server
+ erlang_shell_client_openssh_server,
+ eclient_oserver_kex_strict
]},
{tunnel_distro_server, [], [tunnel_in_erlclient_openssh_server,
tunnel_out_erlclient_openssh_server]},
{erlang_server, [], [{group, tunnel_distro_client},
erlang_server_openssh_client_renegotiate,
+ eserver_oclient_kex_strict,
exec_with_io_in_sshc,
exec_direct_with_io_in_sshc
- ]},
+ ]
+ },
{tunnel_distro_client, [], [tunnel_in_non_erlclient_erlserver,
tunnel_out_non_erlclient_erlserver]}
].
-init_per_suite(Config) ->
+init_per_suite(Config0) ->
?CHECK_CRYPTO(
- case gen_tcp:connect("localhost", 22, []) of
+ case gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, [{active, false}]) of
{error,econnrefused} ->
- {skip,"No openssh deamon (econnrefused)"};
- _ ->
+ {skip,"No openssh daemon (econnrefused)"};
+ {ok, Sock} ->
ssh_test_lib:openssh_sanity_check(
- [{ptty_supported, ssh_test_lib:ptty_supported()}
- | Config]
- )
+ [{ptty_supported, ssh_test_lib:ptty_supported()},
+ {kex_strict, check_kex_strict(Sock)}| Config0])
end
).
@@ -107,7 +111,7 @@ init_per_group(erlang_server, Config) ->
Config;
init_per_group(G, Config) when G==tunnel_distro_server ;
G==tunnel_distro_client ->
- case no_forwarding() of
+ case no_forwarding(Config) of
true ->
{skip, "port forwarding disabled in external ssh"};
false ->
@@ -127,12 +131,15 @@ end_per_group(_, 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])}
+ {unix,_} ->
+ ssh:start(),
+ ssh_test_lib:verify_sanity_check(Config);
+ Type ->
+ {skip, io_lib:format("Unsupported test on ~p",[Type])}
end;
init_per_testcase(_TestCase, Config) ->
ssh:start(),
- Config.
+ ssh_test_lib:verify_sanity_check(Config).
end_per_testcase(_TestCase, _Config) ->
ssh:stop(),
@@ -142,10 +149,32 @@ end_per_testcase(_TestCase, _Config) ->
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
erlang_shell_client_openssh_server(Config) when is_list(Config) ->
+ eclient_oserver_helper2(eclient_oserver_helper1(), Config).
+
+eclient_oserver_kex_strict(Config) when is_list(Config)->
+ case proplists:get_value(kex_strict, Config) of
+ true ->
+ {ok, TestRef} = ssh_test_lib:add_log_handler(),
+ Level = ssh_test_lib:get_log_level(),
+ ssh_test_lib:set_log_level(debug),
+ HelperParams = eclient_oserver_helper1(),
+ {ok, Events} = ssh_test_lib:get_log_events(TestRef),
+ true = ssh_test_lib:kex_strict_negotiated(client, Events),
+ ssh_test_lib:set_log_level(Level),
+ ssh_test_lib:rm_log_handler(),
+ eclient_oserver_helper2(HelperParams, Config);
+ _ ->
+ {skip, "KEX strict not support by local OpenSSH"}
+ end.
+
+eclient_oserver_helper1() ->
process_flag(trap_exit, true),
IO = ssh_test_lib:start_io_server(),
Prev = lists:usort(supervisor:which_children(sshc_sup)),
Shell = ssh_test_lib:start_shell(?SSH_DEFAULT_PORT, IO),
+ {Shell, Prev, IO}.
+
+eclient_oserver_helper2({Shell, Prev, IO}, Config) ->
IO ! {input, self(), "echo Hej\n"},
case proplists:get_value(ptty_supported, Config) of
true ->
@@ -166,7 +195,6 @@ erlang_shell_client_openssh_server(Confi
false
end)
end.
-
%%--------------------------------------------------------------------
%% Test that the server could redirect stdin and stdout from/to an
%% OpensSSH client when handling an exec request
@@ -176,11 +204,10 @@ exec_with_io_in_sshc(Config) when is_lis
{failfun, fun ssh_test_lib:failfun/2}]),
ct:sleep(500),
- PrivDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
+ _PrivDir = proplists:get_value(priv_dir, Config),
ExecStr = "\"io:read('% ').\"",
Cmd = "echo howdy. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- [" -o UserKnownHostsFile=", KnownHosts,
+ [" -o UserKnownHostsFile=", "/dev/null",
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
" -q"
@@ -210,10 +237,9 @@ exec_direct_with_io_in_sshc(Config) when
]),
ct:sleep(500),
- PrivDir = proplists:get_value(priv_dir, Config),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
+ _PrivDir = proplists:get_value(priv_dir, Config),
Cmd = "echo ciao. | " ++ ssh_test_lib:open_sshc_cmd(Host, Port,
- [" -o UserKnownHostsFile=", KnownHosts,
+ [" -o UserKnownHostsFile=", "/dev/null",
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
" -q"
@@ -233,6 +259,28 @@ exec_direct_with_io_in_sshc(Config) when
%%--------------------------------------------------------------------
%% Test that the Erlang/OTP server can renegotiate with openSSH
erlang_server_openssh_client_renegotiate(Config) ->
+ eserver_oclient_renegotiate_helper2(
+ eserver_oclient_renegotiate_helper1(Config)).
+
+eserver_oclient_kex_strict(Config) ->
+ case proplists:get_value(kex_strict, Config) of
+ true ->
+ {ok, TestRef} = ssh_test_lib:add_log_handler(),
+ Level = ssh_test_lib:get_log_level(),
+ ssh_test_lib:set_log_level(debug),
+
+ HelperParams = eserver_oclient_renegotiate_helper1(Config),
+ {ok, Events} = ssh_test_lib:get_log_events(TestRef),
+ ct:log("Events = ~n~p", [Events]),
+ true = ssh_test_lib:kex_strict_negotiated(server, Events),
+ ssh_test_lib:set_log_level(Level),
+ ssh_test_lib:rm_log_handler(),
+ eserver_oclient_renegotiate_helper2(HelperParams);
+ _ ->
+ {skip, "KEX strict not support by local OpenSSH"}
+ end.
+
+eserver_oclient_renegotiate_helper1(Config) ->
_PubKeyAlg = ssh_rsa,
SystemDir = proplists:get_value(data_dir, Config),
PrivDir = proplists:get_value(priv_dir, Config),
@@ -246,9 +294,8 @@ erlang_server_openssh_client_renegotiate
Data = lists:duplicate(trunc(1.1*RenegLimitK*1024), $a),
ok = file:write_file(DataFile, Data),
- KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
- [" -o UserKnownHostsFile=", KnownHosts,
+ [" -o UserKnownHostsFile=", "/dev/null",
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
" -q"
@@ -257,10 +304,12 @@ erlang_server_openssh_client_renegotiate
OpenSsh = ssh_test_lib:open_port({spawn, Cmd++" < "++DataFile}),
+ {Data, OpenSsh, Pid}.
- Expect = fun({data,R}) ->
+eserver_oclient_renegotiate_helper2({Data, OpenSsh, Pid}) ->
+ Expect = fun({data,R}) ->
try
- NonAlphaChars = [C || C<-lists:seq(1,255),
+ NonAlphaChars = [C || C<-lists:seq(1,255),
not lists:member(C,lists:seq($a,$z)),
not lists:member(C,lists:seq($A,$Z))
],
@@ -278,21 +327,20 @@ erlang_server_openssh_client_renegotiate
(_) ->
false
end,
-
- try
- ssh_test_lib:rcv_expected(Expect, OpenSsh, ?TIMEOUT)
+ 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)
+ _ ->
+ %% Unfortunately we can't check that there has been a renegotiation, just trust OpenSSH.
+ ssh:stop_daemon(Pid)
catch
- throw:{skip,R} -> {skip,R}
+ throw:{skip,R} -> {skip,R}
end.
%%--------------------------------------------------------------------
tunnel_out_non_erlclient_erlserver(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
- PrivDir = proplists:get_value(priv_dir, Config),
+ _PrivDir = proplists:get_value(priv_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_out, true},
{system_dir, SystemDir},
@@ -302,9 +350,8 @@ tunnel_out_non_erlclient_erlserver(Confi
ListenHost = {127,0,0,1},
ListenPort = 2345,
- KnownHosts = filename:join(PrivDir, "known_hosts"),
Cmd = ssh_test_lib:open_sshc_cmd(Host, Port,
- [" -o UserKnownHostsFile=", KnownHosts,
+ [" -o UserKnownHostsFile=", "/dev/null",
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
" -q"
@@ -322,7 +369,7 @@ tunnel_out_non_erlclient_erlserver(Confi
%%--------------------------------------------------------------------
tunnel_in_non_erlclient_erlserver(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
- UserDir = proplists:get_value(priv_dir, Config),
+ _UserDir = proplists:get_value(priv_dir, Config),
{_Pid, Host, Port} = ssh_test_lib:daemon([{tcpip_tunnel_in, true},
{system_dir, SystemDir},
{failfun, fun ssh_test_lib:failfun/2}]),
@@ -331,10 +378,9 @@ tunnel_in_non_erlclient_erlserver(Config
ListenHost = {127,0,0,1},
ListenPort = 2345,
- KnownHosts = filename:join(UserDir, "known_hosts"),
Cmd =
ssh_test_lib:open_sshc_cmd(Host, Port,
- [" -o UserKnownHostsFile=", KnownHosts,
+ [" -o UserKnownHostsFile=", "/dev/null",
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
" -q"
@@ -370,8 +416,7 @@ tunnel_in_erlclient_erlserver(Config) ->
%%--------------------------------------------------------------------
tunnel_in_erlclient_openssh_server(_Config) ->
- C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ C = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ToSock, ToHost, ToPort} = tunneling_listner(),
ListenHost = {127,0,0,1},
@@ -401,8 +446,7 @@ tunnel_out_erlclient_erlserver(Config) -
%%--------------------------------------------------------------------
tunnel_out_erlclient_openssh_server(_Config) ->
- C = ssh_test_lib:connect(loopback, 22, [{silently_accept_hosts, true},
- {user_interaction, false}]),
+ C = ssh_test_lib:connect(?SSH_DEFAULT_PORT, []),
{ToSock, ToHost, ToPort} = tunneling_listner(),
ListenHost = {127,0,0,1},
@@ -535,9 +579,14 @@ extra_logout() ->
end.
%%%----------------------------------------------------------------
-no_forwarding() ->
+no_forwarding(Config) ->
%%% Check if the ssh of the OS has tunneling enabled
- Cmnd = "ssh -R 0:localhost:4567 localhost exit",
+ _UserDir = proplists:get_value(priv_dir, Config),
+ Cmnd = ["ssh "
+ " -o UserKnownHostsFile=", "/dev/null",
+ " -o CheckHostIP=no"
+ " -o StrictHostKeyChecking=no"
+ " -R 0:localhost:4567 localhost exit"],
FailRegExp =
"Port forwarding is disabled"
"|remote port forwarding failed"
@@ -571,3 +620,18 @@ no_forwarding() ->
"---- The function no_forwarding() returns ~p",
[Cmnd,TheText, FailRegExp, Result]),
Result.
+
+check_kex_strict(Sock) ->
+ %% Send some version, in order to receive KEXINIT from server
+ ok = gen_tcp:send(Sock, "SSH-2.0-OpenSSH_9.5\r\n"),
+ ct:sleep(100),
+ {ok, Packet} = gen_tcp:recv(Sock, 0),
+ case string:find(Packet, ?kex_strict_s) of
+ nomatch ->
+ ct:log("KEX strict NOT supported by local OpenSSH"),
+ false;
+ _ ->
+ ct:log("KEX strict supported by local OpenSSH"),
+ true
+ end.
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_trpt_test_lib.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_trpt_test_lib.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_trpt_test_lib.erl
@@ -1,19 +1,20 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
+%%
+%% Copyright Ericsson AB 2004-2025. 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%
%%
%%
@@ -23,7 +24,8 @@
-export([exec/1, exec/2,
instantiate/2,
format_msg/1,
- server_host_port/1
+ server_host_port/1,
+ return_value/1
]
).
@@ -47,7 +49,7 @@
prints = [],
return_value,
- %% Packet retrival and decryption
+ %% Packet retrieval and decryption
decrypted_data_buffer = <<>>,
encrypted_data_buffer = <<>>,
aead_data = <<>>,
@@ -73,7 +75,7 @@ exec(L, S) when is_list(L) -> lists:fold
exec(Op, S0=#s{}) ->
S1 = init_op_traces(Op, S0),
try seqnum_trace(
- op(Op, S1))
+ op(Op, S1), S1)
of
S = #s{} ->
case proplists:get_value(silent,S#s.opts) of
@@ -90,7 +92,8 @@ exec(Op, S0=#s{}) ->
report_trace(throw, Term, S1),
throw({Term,Op});
- error:Error ->
+ error:Error:St ->
+ ct:log("Stacktrace=~n~p", [St]),
report_trace(error, Error, S1),
error({Error,Op});
@@ -331,19 +334,38 @@ send(S0, ssh_msg_kexinit) ->
{Msg, _Bytes, _C0} = ssh_transport:key_exchange_init_msg(S0#s.ssh),
send(S0, Msg);
+send(S0, ssh_msg_ignore) ->
+ Msg = #ssh_msg_ignore{data = "unexpected_ignore_message"},
+ send(S0, Msg);
+
+send(S0, ssh_msg_debug) ->
+ Msg = #ssh_msg_debug{
+ always_display = true,
+ message = "some debug message",
+ language = "en"},
+ send(S0, Msg);
+
+send(S0, ssh_msg_unimplemented) ->
+ Msg = #ssh_msg_unimplemented{sequence = 123},
+ send(S0, Msg);
+
+send(S0, ssh_msg_unknown) ->
+ Msg = binary:encode_hex(<<"0000000C060900000000000000000000">>),
+ send(S0, Msg);
+
send(S0=#s{alg_neg={undefined,PeerMsg}}, Msg=#ssh_msg_kexinit{}) ->
S1 = opt(print_messages, S0,
fun(X) when X==true;X==detail -> {"Send~n~s~n",[format_msg(Msg)]} end),
S2 = case PeerMsg of
#ssh_msg_kexinit{} ->
- try ssh_transport:handle_kexinit_msg(PeerMsg, Msg, S1#s.ssh) of
+ try ssh_transport:handle_kexinit_msg(PeerMsg, Msg, S1#s.ssh, init) of
{ok,Cx} when ?role(S1) == server ->
S1#s{alg = Cx#ssh.algorithms};
{ok,_NextKexMsgBin,Cx} when ?role(S1) == client ->
S1#s{alg = Cx#ssh.algorithms}
catch
Class:Exc ->
- save_prints({"Algoritm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s~n",
+ save_prints({"Algorithm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s~n",
[?MODULE,?LINE,Class,format_msg(Exc),format_msg(PeerMsg),format_msg(Msg)]},
S1)
end;
@@ -358,11 +380,11 @@ send(S0=#s{alg_neg={undefined,PeerMsg}},
send(S0, ssh_msg_kexdh_init) when ?role(S0) == client ->
{OwnMsg, PeerMsg} = S0#s.alg_neg,
{ok, NextKexMsgBin, C} =
- try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh)
+ try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh, init)
catch
Class:Exc ->
- fail("Algoritm negotiation failed!",
- {"Algoritm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s",
+ fail("Algorithm negotiation failed!",
+ {"Algorithm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s",
[?MODULE,?LINE,Class,format_msg(Exc),format_msg(PeerMsg),format_msg(OwnMsg)]},
S0)
end,
@@ -374,6 +396,26 @@ send(S0, ssh_msg_kexdh_init) when ?role(
end),
send_bytes(NextKexMsgBin, S#s{ssh = C});
+send(S0, ssh_msg_kexdh_init_dup) when ?role(S0) == client ->
+ {OwnMsg, PeerMsg} = S0#s.alg_neg,
+ {ok, NextKexMsgBin, C} =
+ try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S0#s.ssh, init)
+ catch
+ Class:Exc ->
+ fail("Algorithm negotiation failed!",
+ {"Algorithm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s",
+ [?MODULE,?LINE,Class,format_msg(Exc),format_msg(PeerMsg),format_msg(OwnMsg)]},
+ S0)
+ end,
+ S = opt(print_messages, S0,
+ fun(X) when X==true;X==detail ->
+ #ssh{keyex_key = {{_Private, Public}, {_G, _P}}} = C,
+ Msg = #ssh_msg_kexdh_init{e = Public},
+ {"Send (reconstructed)~n~s~n",[format_msg(Msg)]}
+ end),
+ send_bytes(NextKexMsgBin, S#s{ssh = C}),
+ send_bytes(NextKexMsgBin, S#s{ssh = C});
+
send(S0, ssh_msg_kexdh_reply) ->
Bytes = proplists:get_value(ssh_msg_kexdh_reply, S0#s.reply),
S = opt(print_messages, S0,
@@ -429,7 +471,7 @@ recv(S0 = #s{}) ->
%% Must see hello before binary messages
try_find_crlf(<<>>, S1);
true ->
- %% Has seen hello, therefore no more crlf-messages are alowed.
+ %% Has seen hello, therefore no more crlf-messages are allowed.
S = receive_binary_msg(S1),
case PeerMsg = S#s.return_value of
#ssh_msg_kexinit{} ->
@@ -441,7 +483,7 @@ recv(S0 = #s{}) ->
fail("2 kexint received!!", S);
{OwnMsg, _} ->
- try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S#s.ssh) of
+ try ssh_transport:handle_kexinit_msg(PeerMsg, OwnMsg, S#s.ssh, init) of
{ok,C} when ?role(S) == server ->
S#s{alg_neg = {OwnMsg, PeerMsg},
alg = C#ssh.algorithms,
@@ -451,7 +493,7 @@ recv(S0 = #s{}) ->
alg = C#ssh.algorithms}
catch
Class:Exc ->
- save_prints({"Algoritm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s~n",
+ save_prints({"Algorithm negotiation failed at line ~p:~p~n~p:~s~nPeer: ~s~n Own: ~s~n",
[?MODULE,?LINE,Class,format_msg(Exc),format_msg(PeerMsg),format_msg(OwnMsg)]},
S#s{alg_neg = {OwnMsg, PeerMsg}})
end
@@ -523,7 +565,10 @@ receive_binary_msg(S0=#s{}) ->
S0#s.ssh)
of
{packet_decrypted, DecryptedBytes, EncryptedDataRest, Ssh1} ->
- S1 = S0#s{ssh = Ssh1#ssh{recv_sequence = ssh_transport:next_seqnum(Ssh1#ssh.recv_sequence)},
+ S1 = S0#s{ssh = Ssh1#ssh{recv_sequence =
+ ssh_transport:next_seqnum(undefined,
+ Ssh1#ssh.recv_sequence,
+ false)},
decrypted_data_buffer = <<>>,
undecrypted_packet_length = undefined,
aead_data = <<>>,
@@ -650,7 +695,7 @@ ok({error,E}) -> erlang:error(E).
%%%================================================================
%%%
-%%% Formating of records
+%%% Formatting of records
%%%
format_msg(M) -> format_msg(M, 0).
@@ -725,23 +770,23 @@ report_trace(Class, Term, S) ->
fun(true) -> {"~s ~p",[Class,Term]} end)
).
-seqnum_trace(S) ->
+seqnum_trace(S, S0) ->
opt(print_seqnums, S,
- fun(true) when S#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence,
- S#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
+ fun(true) when S0#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence,
+ S0#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
{"~p seq num: send ~p->~p, recv ~p->~p~n",
[?role(S),
- S#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence,
- S#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence
+ S0#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence,
+ S0#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence
]};
- (true) when S#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence ->
+ (true) when S0#s.ssh#ssh.send_sequence =/= S#s.ssh#ssh.send_sequence ->
{"~p seq num: send ~p->~p~n",
[?role(S),
- S#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence]};
- (true) when S#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
+ S0#s.ssh#ssh.send_sequence, S#s.ssh#ssh.send_sequence]};
+ (true) when S0#s.ssh#ssh.recv_sequence =/= S#s.ssh#ssh.recv_sequence ->
{"~p seq num: recv ~p->~p~n",
[?role(S),
- S#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence]}
+ S0#s.ssh#ssh.recv_sequence, S#s.ssh#ssh.recv_sequence]}
end).
print_traces(S) when S#s.prints == [] -> S;
@@ -770,3 +815,6 @@ opt(Flag, S, Fun) when is_function(Fun,1
save_prints({Fmt,Args}, S) ->
S#s{prints = [{Fmt,Args}|S#s.prints]}.
+
+return_value(#s{return_value = ReturnValue}) ->
+ ReturnValue.
Index: otp-OTP-23.3.4.19/lib/ssh/vsn.mk
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/vsn.mk
+++ otp-OTP-23.3.4.19/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.11.1.6
+SSH_VSN = 5.1.4.13
APP_VSN = "ssh-$(SSH_VSN)"
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_kexinit.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_kexinit.erl
@@ -0,0 +1,330 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2025. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssh connection, e.i. both the
+%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
+%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
+%% Details of the different protocols are
+%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
+%% ----------------------------------------------------------------------
+
+-module(ssh_fsm_kexinit).
+
+-include("ssh.hrl").
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
+-include("ssh_connect.hrl").
+
+-include("ssh_fsm.hrl").
+
+%%====================================================================
+%%% Exports
+%%====================================================================
+
+%%% Behaviour callbacks
+-export([callback_mode/0, handle_event/4, terminate/3,
+ format_status/2, code_change/4]).
+
+-behaviour(ssh_dbg).
+-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1,
+ ssh_dbg_on/1, ssh_dbg_off/1,
+ ssh_dbg_format/2]).
+
+%%====================================================================
+%% gen_statem callbacks
+%%====================================================================
+
+callback_mode() ->
+ [handle_event_function,
+ state_enter].
+
+%%--------------------------------------------------------------------
+
+
+handle_event(Type, Event = prepare_next_packet, StateName, D) ->
+ ssh_connection_handler:handle_event(Type, Event, StateName, D);
+handle_event(Type, Event = {send_disconnect, _, _, _, _}, StateName, D) ->
+ ssh_connection_handler:handle_event(Type, Event, StateName, D);
+
+%%% ######## {kexinit, client|server, init|renegotiate} ####
+handle_event(internal, {#ssh_msg_kexinit{}=Kex, Payload}, {kexinit,Role,ReNeg},
+ D = #data{key_exchange_init_msg = OwnKex}) ->
+ Ssh1 = ssh_transport:key_init(peer_role(Role), D#data.ssh_params, Payload),
+ Ssh = case ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1, ReNeg) of
+ {ok, NextKexMsg, Ssh2} when Role==client ->
+ ssh_connection_handler:send_bytes(NextKexMsg, D),
+ Ssh2;
+ {ok, Ssh2} when Role==server ->
+ Ssh2
+ end,
+ {next_state, {key_exchange,Role,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%% ######## {key_exchange, client|server, init|renegotiate} ####
+%%%---- diffie-hellman
+handle_event(internal, #ssh_msg_kexdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, KexdhReply, Ssh1} = ssh_transport:handle_kexdh_init(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(KexdhReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+
+handle_event(internal, #ssh_msg_kexdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kexdh_reply(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%%---- diffie-hellman group exchange
+handle_event(internal, #ssh_msg_kex_dh_gex_request{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ ssh_connection_handler: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(internal, #ssh_msg_kex_dh_gex_request_old{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, GexGroup, Ssh1} = ssh_transport:handle_kex_dh_gex_request(Msg, D#data.ssh_params),
+ ssh_connection_handler: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(internal, #ssh_msg_kex_dh_gex_group{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, KexGexInit, Ssh} = ssh_transport:handle_kex_dh_gex_group(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(KexGexInit, D),
+ {next_state, {key_exchange_dh_gex_reply,client,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%%---- elliptic curve diffie-hellman
+handle_event(internal, #ssh_msg_kex_ecdh_init{} = Msg, {key_exchange,server,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, KexEcdhReply, Ssh1} = ssh_transport:handle_kex_ecdh_init(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(KexEcdhReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+
+handle_event(internal, #ssh_msg_kex_ecdh_reply{} = Msg, {key_exchange,client,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_ecdh_reply(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+
+%%% ######## handle KEX strict
+handle_event(internal, _Event, {key_exchange,_Role,init},
+ #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
+ send_sequence = SendSeq,
+ recv_sequence = RecvSeq}}) ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation: send_sequence = ~p recv_sequence = ~p",
+ [SendSeq, RecvSeq]));
+
+%%% ######## {key_exchange_dh_gex_init, server, init|renegotiate} ####
+handle_event(internal, #ssh_msg_kex_dh_gex_init{} = Msg, {key_exchange_dh_gex_init,server,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, KexGexReply, Ssh1} = ssh_transport:handle_kex_dh_gex_init(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(KexGexReply, D),
+ {ok, NewKeys, Ssh2} = ssh_transport:new_keys_message(Ssh1),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh2),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,server,ReNeg}, D#data{ssh_params=Ssh}};
+%%% ######## handle KEX strict
+handle_event(internal, _Event, {key_exchange_dh_gex_init,_Role,init},
+ #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
+ send_sequence = SendSeq,
+ recv_sequence = RecvSeq}}) ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation: send_sequence = ~p recv_sequence = ~p",
+ [SendSeq, RecvSeq]));
+
+%%% ######## {key_exchange_dh_gex_reply, client, init|renegotiate} ####
+handle_event(internal, #ssh_msg_kex_dh_gex_reply{} = Msg, {key_exchange_dh_gex_reply,client,ReNeg}, D) ->
+ ok = check_kex_strict(Msg, D),
+ {ok, NewKeys, Ssh1} = ssh_transport:handle_kex_dh_gex_reply(Msg, D#data.ssh_params),
+ ssh_connection_handler:send_bytes(NewKeys, D),
+ {ok, ExtInfo, Ssh} = ssh_transport:ext_info_message(Ssh1),
+ ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {new_keys,client,ReNeg}, D#data{ssh_params=Ssh}};
+%%% ######## handle KEX strict
+handle_event(internal, _Event, {key_exchange_dh_gex_reply,_Role,init},
+ #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
+ send_sequence = SendSeq,
+ recv_sequence = RecvSeq}}) ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation: send_sequence = ~p recv_sequence = ~p",
+ [SendSeq, RecvSeq]));
+
+%%% ######## {new_keys, client|server} ####
+%% First key exchange round:
+handle_event(internal, #ssh_msg_newkeys{} = Msg, {new_keys,client,init}, D0) ->
+ {ok, Ssh1} = ssh_transport:handle_new_keys(Msg, D0#data.ssh_params),
+ %% {ok, ExtInfo, Ssh2} = ssh_transport:ext_info_message(Ssh1),
+ %% ssh_connection_handler:send_bytes(ExtInfo, D0),
+ {MsgReq, Ssh} = ssh_auth:service_request_msg(Ssh1),
+ D = ssh_connection_handler:send_msg(MsgReq, D0#data{ssh_params = Ssh}),
+ {next_state, {ext_info,client,init}, D};
+
+handle_event(internal, #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),
+ %% ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {ext_info,server,init}, D#data{ssh_params=Ssh}};
+
+%%% ######## handle KEX strict
+handle_event(internal, _Event, {new_keys,_Role,init},
+ #data{ssh_params = #ssh{algorithms = #alg{kex_strict_negotiated = true},
+ send_sequence = SendSeq,
+ recv_sequence = RecvSeq}}) ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation (send_sequence = ~p recv_sequence = ~p)",
+ [SendSeq, RecvSeq]));
+
+%% Subsequent key exchange rounds (renegotiation):
+handle_event(internal, #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),
+ %% ssh_connection_handler:send_bytes(ExtInfo, D),
+ {next_state, {ext_info,Role,renegotiate}, D#data{ssh_params=Ssh}};
+
+
+%%% ######## {ext_info, client|server, init|renegotiate} ####
+
+handle_event(internal, #ssh_msg_ext_info{}=Msg, {ext_info,Role,init}, D0) ->
+ D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
+ {next_state, {service_request,Role}, D, {change_callback_module,ssh_connection_handler}};
+
+handle_event(internal, #ssh_msg_ext_info{}=Msg, {ext_info,Role,renegotiate}, D0) ->
+ D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
+ {next_state, {connected,Role}, D, {change_callback_module,ssh_connection_handler}};
+
+handle_event(internal, #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, {change_callback_module,ssh_connection_handler}]};
+
+handle_event(internal, Msg, {ext_info,Role,renegotiate}, 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, {change_callback_module,ssh_connection_handler}]};
+
+%%% ######## UNHANDLED EVENT!
+handle_event(Type, Event, StateName, D) ->
+ ssh_connection_handler:handle_event(Type, Event, StateName, D).
+
+%%--------------------------------------------------------------------
+format_status(A, B) ->
+ ssh_connection_handler:format_status(A, B).
+
+%%--------------------------------------------------------------------
+terminate(Reason, StateName, D) ->
+ ssh_connection_handler:terminate(Reason, StateName, D).
+
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+%% "Invert" the Role
+peer_role(client) -> server;
+peer_role(server) -> client.
+
+check_kex_strict(Msg,
+ #data{ssh_params =
+ #ssh{algorithms =
+ #alg{
+ kex = Kex,
+ kex_strict_negotiated = KexStrictNegotiated},
+ send_sequence = SendSeq,
+ recv_sequence = RecvSeq}}) ->
+ case check_msg_group(Msg, get_alg_group(Kex), KexStrictNegotiated) of
+ ok ->
+ ok;
+ error ->
+ ?DISCONNECT(?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ io_lib:format("KEX strict violation: send_sequence = ~p recv_sequence = ~p",
+ [SendSeq, RecvSeq]))
+ end.
+
+get_alg_group(Kex) when Kex == 'diffie-hellman-group16-sha512';
+ Kex == 'diffie-hellman-group18-sha512';
+ Kex == 'diffie-hellman-group14-sha256';
+ Kex == 'diffie-hellman-group14-sha1';
+ Kex == 'diffie-hellman-group1-sha1' ->
+ dh_alg;
+get_alg_group(Kex) when Kex == 'diffie-hellman-group-exchange-sha256';
+ Kex == 'diffie-hellman-group-exchange-sha1' ->
+ dh_gex_alg;
+get_alg_group(Kex) when Kex == 'curve25519-sha256';
+ Kex == 'curve25519-sha256@libssh.org';
+ Kex == 'curve448-sha512';
+ Kex == 'ecdh-sha2-nistp521';
+ Kex == 'ecdh-sha2-nistp384';
+ Kex == 'ecdh-sha2-nistp256' ->
+ ecdh_alg.
+
+check_msg_group(_Msg, _AlgGroup, false) -> ok;
+check_msg_group(#ssh_msg_kexdh_init{}, dh_alg, true) -> ok;
+check_msg_group(#ssh_msg_kexdh_reply{}, dh_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_dh_gex_request_old{}, dh_gex_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_dh_gex_request{}, dh_gex_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_dh_gex_group{}, dh_gex_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_dh_gex_init{}, dh_gex_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_dh_gex_reply{}, dh_gex_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_ecdh_init{}, ecdh_alg, true) -> ok;
+check_msg_group(#ssh_msg_kex_ecdh_reply{}, ecdh_alg, true) -> ok;
+check_msg_group(_Msg, _AlgGroup, _) -> error.
+
+%%%################################################################
+%%%#
+%%%# Tracing
+%%%#
+
+ssh_dbg_trace_points() -> [connection_events].
+
+ssh_dbg_flags(connection_events) -> [c].
+
+ssh_dbg_on(connection_events) -> dbg:tp(?MODULE, handle_event, 4, x).
+
+ssh_dbg_off(connection_events) -> dbg:ctpg(?MODULE, handle_event, 4).
+
+ssh_dbg_format(connection_events, {call, {?MODULE,handle_event, [EventType, EventContent, State, _Data]}}) ->
+ ["Connection event\n",
+ io_lib:format("[~w] EventType: ~p~nEventContent: ~p~nState: ~p~n", [?MODULE, EventType, EventContent, State])
+ ];
+ssh_dbg_format(connection_events, {return_from, {?MODULE,handle_event,4}, Ret}) ->
+ ["Connection event result\n",
+ io_lib:format("[~w] ~p~n", [?MODULE, ssh_dbg:reduce_state(Ret, #data{})])
+ ].
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_userauth_client.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_userauth_client.erl
@@ -0,0 +1,182 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2025. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssh connection, e.i. both the
+%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
+%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
+%% Details of the different protocols are
+%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
+%% ----------------------------------------------------------------------
+
+-module(ssh_fsm_userauth_client).
+
+-include("ssh.hrl").
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
+-include("ssh_connect.hrl").
+
+-include("ssh_fsm.hrl").
+
+%%====================================================================
+%%% Exports
+%%====================================================================
+
+%%% Behaviour callbacks
+-export([callback_mode/0, handle_event/4, terminate/3,
+ format_status/2, code_change/4]).
+
+%%====================================================================
+%% gen_statem callbacks
+%%====================================================================
+
+callback_mode() ->
+ [handle_event_function,
+ state_enter].
+
+%%--------------------------------------------------------------------
+
+%%% ######## {userauth, client} ####
+
+%%---- #ssh_msg_ext_info could follow after the key exchange, both the initial and the re-negotiation
+handle_event(internal, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
+
+%%---- received userauth success from the server
+handle_event(internal, #ssh_msg_userauth_success{}, {userauth,client}, D0=#data{ssh_params = Ssh}) ->
+ ssh_auth:ssh_msg_userauth_result(success),
+ ssh_connection_handler:handshake(ssh_connected, D0),
+ D = D0#data{ssh_params=Ssh#ssh{authenticated = true}},
+ {next_state, {connected,client}, D, {change_callback_module,ssh_connection_handler}};
+
+
+%%---- userauth failure response to clientfrom the server
+handle_event(internal, #ssh_msg_userauth_failure{}, {userauth,client}=StateName,
+ #data{ssh_params = #ssh{userauth_methods = []}} = D0) ->
+ {Shutdown, D} =
+ ?send_disconnect(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
+ StateName, D0),
+ {stop, Shutdown, D};
+
+handle_event(internal, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client},
+ D0 = #data{ssh_params = Ssh0}) ->
+ %% The preferred authentication method failed, try next method
+ Ssh1 = case Ssh0#ssh.userauth_methods of
+ none ->
+ %% Server tells us which authentication methods that are allowed
+ Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")};
+ _ ->
+ %% We already know...
+ Ssh0
+ end,
+ case ssh_auth:userauth_request_msg(Ssh1) of
+ {send_disconnect, Code, Ssh} ->
+ {Shutdown, D} =
+ ?send_disconnect(Code,
+ io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
+ StateName, D0#data{ssh_params = Ssh}),
+ {stop, Shutdown, D};
+ {"keyboard-interactive", {Msg, Ssh}} ->
+ D = ssh_connection_handler:send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,client}, D};
+ {_Method, {Msg, Ssh}} ->
+ D = ssh_connection_handler:send_msg(Msg, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
+ end;
+
+%%---- banner to client
+handle_event(internal, #ssh_msg_userauth_banner{message = Msg}, {S,client}, D)
+ when S == userauth; S == userauth_keyboard_interactive;
+ S == userauth_keyboard_interactive_extra;
+ S == userauth_keyboard_interactive_info_response ->
+ case D#data.ssh_params#ssh.userauth_quiet_mode of
+ false -> io:format("~s", [Msg]);
+ true -> ok
+ end,
+ keep_state_and_data;
+
+
+%%% ######## {userauth_keyboard_interactive, client}
+
+handle_event(internal, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
+ #data{ssh_params = Ssh0} = D0) ->
+ case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_info_response,client}, D};
+ not_ok ->
+ {next_state, {userauth,client}, D0, [postpone]}
+ end;
+
+handle_event(internal, #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, [postpone]};
+
+handle_event(internal, #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(internal, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) ->
+ %% FIXME: need new state to receive this msg!
+ D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
+ {keep_state, D};
+
+handle_event(internal, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth,client}, D, [postpone]};
+
+handle_event(internal, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
+ {next_state, {userauth_keyboard_interactive,client}, D, [postpone]};
+
+
+%%% ######## UNHANDLED EVENT!
+handle_event(Type, Event, StateName, D) ->
+ ssh_connection_handler:handle_event(Type, Event, StateName, D).
+
+%%--------------------------------------------------------------------
+format_status(A, B) ->
+ ssh_connection_handler:format_status(A, B).
+
+%%--------------------------------------------------------------------
+terminate(Reason, StateName, D) ->
+ ssh_connection_handler:terminate(Reason, StateName, D).
+
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_userauth_server.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm_userauth_server.erl
@@ -0,0 +1,214 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2022. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles an ssh connection, e.i. both the
+%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
+%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
+%% Details of the different protocols are
+%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
+%% ----------------------------------------------------------------------
+
+-module(ssh_fsm_userauth_server).
+
+-include("ssh.hrl").
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
+-include("ssh_connect.hrl").
+
+-include("ssh_fsm.hrl").
+
+%%====================================================================
+%%% Exports
+%%====================================================================
+
+%%% Behaviour callbacks
+-export([callback_mode/0, handle_event/4, terminate/3,
+ format_status/2, code_change/4]).
+
+%%====================================================================
+%% gen_statem callbacks
+%%====================================================================
+
+callback_mode() ->
+ [handle_event_function,
+ state_enter].
+
+%%--------------------------------------------------------------------
+
+%%% ######## {userauth, server} ####
+%%---- userauth request to server
+handle_event(internal,
+ Msg = #ssh_msg_userauth_request{service = ServiceName,
+ method = Method},
+ StateName = {userauth,server},
+ D0 = #data{ssh_params=Ssh0}) ->
+
+ case {ServiceName, Ssh0#ssh.service, Method} of
+ {"ssh-connection", "ssh-connection", "none"} ->
+ %% Probably the very first userauth_request but we deny unauthorized login
+ %% However, we *may* accept unauthorized login if instructed so
+ case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
+ {not_authorized, _, {Reply,Ssh}} ->
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D};
+ {authorized, User, {Reply, Ssh1}} ->
+ D = connected_state(Reply, Ssh1, User, Method, D0),
+ {next_state, {connected,server}, D,
+ [set_max_initial_idle_timeout(D),
+ {change_callback_module,ssh_connection_handler}
+ ]
+ }
+
+ end;
+
+ {"ssh-connection", "ssh-connection", Method} ->
+ %% Userauth request with a method like "password" or so
+ case lists:member(Method, Ssh0#ssh.userauth_methods) of
+ true ->
+ %% Yepp! we support this method
+ case ssh_auth:handle_userauth_request(Msg, Ssh0#ssh.session_id, Ssh0) of
+ {authorized, User, {Reply, Ssh1}} ->
+ D = connected_state(Reply, Ssh1, User, Method, D0),
+ {next_state, {connected,server}, D,
+ [set_max_initial_idle_timeout(D),
+ {change_callback_module,ssh_connection_handler}
+ ]};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} when Method == "keyboard-interactive" ->
+ retry_fun(User, Reason, D0),
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive,server}, D};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, D0),
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {keep_state, D}
+ end;
+ false ->
+ %% No we do not support this method (=/= none)
+ %% At least one non-erlang client does like this. Retry as the next event
+ {keep_state_and_data,
+ [{next_event, internal, Msg#ssh_msg_userauth_request{method="none"}}]
+ }
+ end;
+
+ %% {"ssh-connection", Expected, Method} when Expected =/= ServiceName -> Do what?
+ %% {ServiceName, Expected, Method} when Expected =/= ServiceName -> Do what?
+
+ {ServiceName, _, _} when ServiceName =/= "ssh-connection" ->
+ {Shutdown, D} =
+ ?send_disconnect(?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ io_lib:format("Unknown service: ~p",[ServiceName]),
+ StateName, D0),
+ {stop, Shutdown, D}
+ end;
+
+handle_event(internal, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D0) ->
+ case ssh_auth:handle_userauth_info_response(Msg, D0#data.ssh_params) of
+ {authorized, User, {Reply, Ssh1}} ->
+ D = connected_state(Reply, Ssh1, User, "keyboard-interactive", D0),
+ {next_state, {connected,server}, D,
+ [set_max_initial_idle_timeout(D),
+ {change_callback_module,ssh_connection_handler}
+ ]};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, D0),
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth,server}, D};
+
+ {authorized_but_one_more, _User, {Reply, Ssh}} ->
+ D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
+ {next_state, {userauth_keyboard_interactive_extra,server}, D}
+ end;
+
+handle_event(internal, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive_extra, server}, D0) ->
+ {authorized, User, {Reply, Ssh1}} =
+ ssh_auth:handle_userauth_info_response({extra,Msg}, D0#data.ssh_params),
+ D = connected_state(Reply, Ssh1, User, "keyboard-interactive", D0),
+ {next_state, {connected,server}, D,
+ [set_max_initial_idle_timeout(D),
+ {change_callback_module,ssh_connection_handler}
+ ]
+ };
+
+
+%%% ######## UNHANDLED EVENT!
+handle_event(Type, Event, StateName, D) ->
+ ssh_connection_handler:handle_event(Type, Event, StateName, D).
+
+%%--------------------------------------------------------------------
+format_status(A, B) ->
+ ssh_connection_handler:format_status(A, B).
+
+%%--------------------------------------------------------------------
+terminate(Reason, StateName, D) ->
+ ssh_connection_handler:terminate(Reason, StateName, D).
+
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+
+connected_state(Reply, Ssh1, User, Method, D0) ->
+ D1 = #data{ssh_params=Ssh} =
+ ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh1}),
+ ssh_connection_handler:handshake(ssh_connected, D1),
+ connected_fun(User, Method, D1),
+ D1#data{auth_user=User,
+ %% Note: authenticated=true MUST NOT be sent
+ %% before send_msg!
+ ssh_params = Ssh#ssh{authenticated = true}}.
+
+
+set_max_initial_idle_timeout(#data{ssh_params = #ssh{opts=Opts}}) ->
+ {{timeout,max_initial_idle_time}, ?GET_OPT(max_initial_idle_time,Opts), none}.
+
+connected_fun(User, Method, #data{ssh_params = #ssh{peer = {_,Peer}}} = D) ->
+ ?CALL_FUN(connectfun,D)(User, Peer, Method).
+
+
+retry_fun(_, undefined, _) ->
+ ok;
+retry_fun(User, Reason, #data{ssh_params = #ssh{opts = Opts,
+ peer = {_,Peer}
+ }}) ->
+ {Tag,Info} =
+ case Reason of
+ {error, Error} ->
+ {failfun, Error};
+ _ ->
+ {infofun, Reason}
+ end,
+ Fun = ?GET_OPT(Tag, Opts),
+ try erlang:fun_info(Fun, arity)
+ of
+ {arity, 2} -> %% Backwards compatible
+ catch Fun(User, Info);
+ {arity, 3} ->
+ catch Fun(User, Peer, Info);
+ _ ->
+ ok
+ catch
+ _:_ ->
+ ok
+ end.
+
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_lib.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_lib.erl
@@ -0,0 +1,101 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2025. 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%
+%%
+
+%%
+%% Common library routines in the SSH application
+%%
+
+-module(ssh_lib).
+
+-export([
+ format_address_port/2, format_address_port/1,
+ format_address/1,
+ format_time_ms/1,
+ comp/2,
+ trim_reason/1,
+ max_log_len/1
+ ]).
+
+-include("ssh.hrl").
+
+%%%----------------------------------------------------------------
+format_address_port({IP,Port}) when is_integer(Port) ->
+ format_address_port(IP, Port);
+format_address_port(X) ->
+ io_lib:format("~p", [X]).
+
+%%%----------------------------------------------------------------
+format_address_port(Address, Port) ->
+ try lists:concat([format_address(Address), ":", Port])
+ catch
+ _:_ -> io_lib:format("~p:~p",[Address,Port])
+ end.
+
+%%%----------------------------------------------------------------
+format_address(#address{address=A, port=P}) ->
+ format_address_port(A,P);
+format_address(A) ->
+ try inet:ntoa(A)
+ catch
+ _:_ when is_list(A) -> A;
+ _:_ -> io_lib:format('~p',[A])
+ end.
+
+%%%----------------------------------------------------------------
+format_time_ms(T) when is_integer(T) ->
+ if
+ T < 60000 -> io_lib:format("~.3f sec", [T/1000]);
+ true -> io_lib:format("~p min ~s", [T div 60000, format_time_ms(T rem 60000)])
+ end.
+
+
+%%%----------------------------------------------------------------
+
+comp(X1, X2) ->
+ comp(X1, X2, true).
+
+%%% yes - very far from best implementation
+comp(<<B1,R1/binary>>, <<B2,R2/binary>>, Truth) ->
+ comp(R1, R2, Truth and (B1 == B2));
+comp(<<_,R1/binary>>, <<>>, Truth) ->
+ comp(R1, <<>>, Truth and false);
+comp(<<>>, <<>>, Truth) ->
+ Truth;
+
+comp([H1|T1], [H2|T2], Truth) ->
+ comp(T1, T2, Truth and (H1 == H2));
+comp([_|T1], [], Truth) ->
+ comp(T1, [], Truth and false);
+comp([], [], Truth) ->
+ Truth;
+
+comp(_, _, _) ->
+ false.
+%% We don't want to process badmatch details, potentially containing
+%% malicious data of unknown size
+trim_reason({badmatch, V}) when is_binary(V) ->
+ badmatch;
+trim_reason(E) ->
+ E.
+
+max_log_len(#ssh{opts = Opts}) ->
+ ?GET_OPT(max_log_item_len, Opts);
+max_log_len(Opts) when is_map(Opts) ->
+ ?GET_OPT(max_log_item_len, Opts).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm.hrl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_fsm.hrl
@@ -0,0 +1,57 @@
+%%====================================================================
+%% Types
+%%====================================================================
+-type connection_ref() :: ssh:connection_ref().
+-type channel_id() :: ssh:channel_id().
+
+%%====================================================================
+%% Internal process state
+%%====================================================================
+-record(data, {
+ starter :: pid()
+ | undefined,
+ auth_user :: string()
+ | undefined,
+ connection_state :: #connection{}
+ | undefined,
+ latest_channel_id = 0 :: non_neg_integer()
+ | undefined,
+ transport_protocol :: atom()
+ | undefined, % ex: tcp
+ transport_cb :: atom()
+ | undefined, % ex: gen_tcp
+ transport_close_tag :: atom()
+ | undefined, % ex: tcp_closed
+ ssh_params :: #ssh{}
+ | undefined,
+ socket :: gen_tcp:socket()
+ | undefined,
+ decrypted_data_buffer = <<>> :: binary()
+ | undefined,
+ encrypted_data_buffer = <<>> :: binary()
+ | undefined,
+ aead_data = <<>> :: binary()
+ | undefined,
+ undecrypted_packet_length :: undefined | non_neg_integer(),
+ key_exchange_init_msg :: #ssh_msg_kexinit{}
+ | undefined,
+ last_size_rekey = 0 :: non_neg_integer(),
+ event_queue = [] :: list(),
+ inet_initial_buffer_size :: pos_integer()
+ | undefined
+ }).
+
+
+%%====================================================================
+%% Macros
+%%====================================================================
+-define(send_disconnect(Code, DetailedText, StateName, State),
+ ssh_connection_handler:send_disconnect(Code, DetailedText, ?MODULE, ?LINE, StateName, State)).
+
+-define(send_disconnect(Code, Reason, DetailedText, StateName, State),
+ ssh_connection_handler:send_disconnect(Code, Reason, DetailedText, ?MODULE, ?LINE, StateName, State)).
+
+
+-define(CALL_FUN(Key,D), catch (?GET_OPT(Key, (D#data.ssh_params)#ssh.opts)) ).
+
+-define(role(StateName), element(2,StateName)).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_acceptor_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_acceptor_sup.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_acceptor_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2021. 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,64 +29,43 @@
-include("ssh.hrl").
--export([start_link/4, start_child/5, stop_child/4]).
+-export([start_link/3,
+ restart_child/2
+ ]).
%% Supervisor callback
-export([init/1]).
--define(DEFAULT_TIMEOUT, 50000).
-
%%%=========================================================================
%%% API
%%%=========================================================================
-start_link(Address, Port, Profile, Options) ->
- supervisor:start_link(?MODULE, [Address, Port, Profile, Options]).
-
-start_child(AccSup, Address, Port, Profile, Options) ->
- Spec = child_spec(Address, Port, Profile, Options),
- case supervisor:start_child(AccSup, Spec) of
- {error, already_present} ->
- %% 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
+start_link(SystemSup, Address, Options) ->
+ case supervisor:start_link(?MODULE, [SystemSup, Address, Options]) of
+ {error, {shutdown, {failed_to_start_child, _, Error}}} ->
+ {error,Error};
+ Other ->
+ Other
end.
-stop_child(AccSup, Address, Port, Profile) ->
- Name = id(Address, Port, Profile),
- case supervisor:terminate_child(AccSup, Name) of
- ok ->
- supervisor:delete_child(AccSup, Name);
- Error ->
- Error
- end.
+restart_child(AccSup, Address) ->
+ supervisor:restart_child(AccSup, {?MODULE,Address}).
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([Address, Port, Profile, Options]) ->
- %% Initial start of ssh_acceptor_sup for this port or new start after
- %% ssh:stop_daemon
+init([SystemSup, Address, Options]) ->
+ %% Initial start of ssh_acceptor_sup for this port
SupFlags = #{strategy => one_for_one,
intensity => 10,
period => 3600
},
- ChildSpecs = [child_spec(Address, Port, Profile, Options)],
+ ChildSpecs = [#{id => {?MODULE,Address},
+ start => {ssh_acceptor, start_link, [SystemSup, Address, Options]},
+ restart => transient % because a crashed listener could be replaced by a new one
+ }
+ ],
{ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
%%% Internal functions
%%%=========================================================================
-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 % because a crashed listener could be replaced by a new one
- }.
-
-id(Address, Port, Profile) ->
- {ssh_acceptor_sup, Address, Port, Profile}.
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_auth.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_auth.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_auth.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2022. 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.
@@ -59,7 +59,7 @@
-record(ssh_msg_userauth_passwd_changereq,
{
prompt, %% string
- languge %% string
+ language %% string
}).
-record(ssh_msg_userauth_pk_ok,
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_controller.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_controller.erl
+++ /dev/null
@@ -1,125 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2020. 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%
-%%
-%%
-%%----------------------------------------------------------------------
-%% Purpose: Handles an ssh connection, e.i. both the
-%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
-%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
-%% Details of the different protocols are
-%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
-%% ----------------------------------------------------------------------
-
--module(ssh_controller).
-
--behaviour(gen_server).
-
-
-%%====================================================================
-%%% Exports
-%%====================================================================
-
-%%% API
--export([start_system_subsystem/7,
- stop_system/2
- ]).
-
-%%% Start and stop
--export([start_link/2
- ]).
-
--export([init/1,
- handle_call/3,
- handle_cast/2
- ]).
-
-%%====================================================================
-%% Start / stop
-%%====================================================================
-
-start_link(Role, RegName) ->
- gen_server:start_link({local,RegName}, ?MODULE, [Role], []).
-
-%%====================================================================
-%% Internal application API
-%%====================================================================
-
--define(TIMEOUT, 30000).
-
-start_system_subsystem(Controller, Sup, Host, Port, Profile, Options, ChildSpec) ->
- gen_server:call(Controller, {start_system_subsystem, Sup, Host, Port, Profile, Options, ChildSpec}, ?TIMEOUT).
-
-stop_system(Controller, SysSup) ->
- gen_server:call(Controller, {stop_system,SysSup}, ?TIMEOUT).
-
-%%====================================================================
-%% Internal process state
-%%====================================================================
--record(data, {
- role
- }).
-
-%%====================================================================
-%% Intitialisation
-%%====================================================================
-init([Role]=_Args) ->
- {ok, #data{role=Role}}.
-
-%%====================================================================
-%% gen_server callbacks
-%%====================================================================
-handle_call({start_system_subsystem, Sup, Address, Port, Profile, Options, ChildSpec}, _From, D) ->
- try
- {ok,SystemSup0} =
- case ssh_system_sup:system_supervisor(Address, Port, Profile) of
- undefined ->
- supervisor:start_child(Sup, ChildSpec);
- Pid ->
- {ok,Pid}
- end,
- {SystemSup0, ssh_system_sup:start_subsystem(SystemSup0, D#data.role, Address, Port, Profile, Options)}
- of
- {SystemSup, {ok,SubSysSup}} ->
- {reply, {ok,{SystemSup,SubSysSup}}, D}
- catch
- C:E:S ->
- {reply, {error,{failed,C,E,S}}, D}
- end;
-
-
-handle_call({stop_system,SysSup}, _From, D) ->
- try
- case supervisor:which_children(SysSup) of
- [] ->
- ssh_system_sup:stop_system(D#data.role, SysSup);
- _X ->
- ok
- end
- catch
- _:_ ->
- %% Already stopped (?)
- skip
- end,
- {reply, ok, D}.
-
-
-
-
-handle_cast(_Request, D) ->
- {noreply, D}.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_subsystem_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_subsystem_sup.erl
+++ /dev/null
@@ -1,121 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2018. 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%
-%%
-%%
-%%----------------------------------------------------------------------
-%% Purpose: The ssh subsystem supervisor
-%%----------------------------------------------------------------------
-
--module(ssh_subsystem_sup).
-
--behaviour(supervisor).
-
--include("ssh.hrl").
-
--export([start_link/5,
- connection_supervisor/1,
- channel_supervisor/1,
- tcpip_fwd_supervisor/1,
- start_channel/8
- ]).
-
-%% Supervisor callback
--export([init/1]).
-
-%%%=========================================================================
-%%% API
-%%%=========================================================================
-start_link(Role, Address, Port, Profile, Options) ->
- supervisor:start_link(?MODULE, [Role, Address, Port, Profile, Options]).
-
-connection_supervisor(SupPid) ->
- Children = supervisor:which_children(SupPid),
- ssh_connection_sup(Children).
-
-channel_supervisor(SupPid) when is_pid(SupPid) ->
- Children = supervisor:which_children(SupPid),
- ssh_channel_sup(Children).
-
-tcpip_fwd_supervisor(SupPid) when is_pid(SupPid) ->
- Children = supervisor:which_children(SupPid),
- tcpip_fwd_sup(Children).
-
-start_channel(Role, SupPid, ConnRef, Callback, Id, Args, Exec, Opts) ->
- ChannelSup = channel_supervisor(SupPid),
- ssh_channel_sup:start_child(Role, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts).
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
-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(Role, Address, Port, Profile, Options) ->
- [ssh_channel_child_spec(Role, Address, Port, Profile, Options),
- ssh_connection_child_spec(Role, Address, Port, Profile, Options),
- ssh_tcpip_forward_acceptor_child_spec()].
-
-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,
- type => supervisor
- }.
-
-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,
- type => supervisor
- }.
-
-ssh_tcpip_forward_acceptor_child_spec() ->
- #{id => make_ref(),
- start => {ssh_tcpip_forward_acceptor_sup, start_link, []},
- restart => temporary,
- type => supervisor
- }.
-
-
-id(Role, Sup, Address, Port) ->
- {Role, Sup, Address, Port}.
-
-ssh_connection_sup([{_, Child, _, [ssh_connection_sup]} | _]) ->
- Child;
-ssh_connection_sup([_ | Rest]) ->
- ssh_connection_sup(Rest).
-
-ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) ->
- Child;
-ssh_channel_sup([_ | Rest]) ->
- ssh_channel_sup(Rest).
-
-tcpip_fwd_sup([{_, Child, _, [ssh_tcpip_forward_acceptor_sup]} | _]) ->
- Child;
-tcpip_fwd_sup([_ | Rest]) ->
- tcpip_fwd_sup(Rest).
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_sup.erl
+++ /dev/null
@@ -1,118 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2018. 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%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: The top supervisor for the ssh application.
-%%----------------------------------------------------------------------
--module(ssh_sup).
-
--behaviour(supervisor).
-
--export([init/1]).
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
-init(_) ->
- add_logger_filter(),
- SupFlags = #{strategy => one_for_one,
- intensity => 10,
- period => 3600
- },
- ChildSpecs = [#{id => sshd_sup,
- start => {sshd_sup, start_link, []},
- type => supervisor
- },
- #{id => sshc_sup,
- start => {sshc_sup, start_link, []},
- type => supervisor
- }
- ],
- {ok, {SupFlags,ChildSpecs}}.
-
-
-%%%================================================================
-add_logger_filter() ->
- DefAct = application:get_env(ssh, default_filter, rm),
- DefF = start_link,
- ModulesActions =
- lists:map(fun(M) when is_atom(M) ->
- {M,{DefF,DefAct}};
-
- ({M,Act}) when is_atom(M),
- (Act == rm orelse
- Act == filter) ->
- {M,{DefF,Act}};
-
- ({M,F}) when is_atom(M), is_atom(F) ->
- {M,{F,DefAct}};
-
- ({M,F,Act}) when is_atom(M), is_atom(F),
- (Act == rm orelse
- Act == filter) ->
- {M,{F,Act}}
- end, application:get_env(ssh, filter_modules, [])),
- logger:add_primary_filter(ssh_filter, {fun ssh_filter/2, ModulesActions}).
-
-
-ssh_filter(Ev = #{msg := {report, R=#{report := Rep}}},
- ModulesActions = [_|_]) when is_list(Rep) ->
- %% io:format("Ev = ~p~n", [Ev]),
- try
- Ev#{msg := {report, R#{report := remove_sensitive(Rep, ModulesActions)}}}
- catch
- throw:{ssh_filter_return,Ret} ->
- %% io:format("ssh_filter returns ~p~n", [Ret]),
- Ret;
- _C:_E ->
- %% io:format("*** ~p ~p~n", [_C,_E]),
- stop
- end;
-ssh_filter(OtherEv, _) ->
- %% io:format("OtherEv = ~p~n", [OtherEv]),
- OtherEv.
-
-
-remove_sensitive(L, ModActs) when is_list(L) -> rs(L, ModActs);
-remove_sensitive(_, _) -> throw({ssh_filter_return,ignore}).
-
-
-rs([{K,V0}|T], ModActs) when is_list(V0) ->
- case proplists:get_value(mfargs, V0) of
- {M,F,A} ->
- MFA1 = filter(proplists:get_value(M,ModActs), {M,F,A}),
- V = lists:keyreplace(mfargs, 1, V0, {mfargs,MFA1}),
- [{K,V} | T];
- _ ->
- [{K,V0} | rs(T,ModActs)]
- end;
-
-rs([H|T], ModActs) ->
- [H | rs(T,ModActs)];
-
-rs(Other, _) ->
- Other.
-
-
-filter({F,Act}, {M,F,A}) -> {M, F, ssh_options:no_sensitive(Act,A)};
-filter(stop, _) -> throw({ssh_filter_return,stop});
-filter(_, _) -> throw({ssh_filter_return,ignore}).
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/sshc_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/sshc_sup.erl
+++ /dev/null
@@ -1,109 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2018. 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%
-%%
-
-%%
-%%----------------------------------------------------------------------
-%% Purpose: The ssh client subsystem supervisor
-%%----------------------------------------------------------------------
-
--module(sshc_sup).
-
--behaviour(supervisor).
-
--export([start_link/0,
- start_child/4,
- start_system_subsystem/4,
- stop_child/1,
- stop_system/1
- ]).
-
-%% Supervisor callback
--export([init/1]).
-
--include("ssh.hrl").
-
--define(SSHC_SUP, ?MODULE).
-
-%%%=========================================================================
-%%% API
-%%%=========================================================================
-start_link() ->
- supervisor:start_link({local,?MODULE}, ?MODULE, []).
-
-start_child(Address, Port, Profile, Options) ->
- case ssh_system_sup:system_supervisor(Address, Port, Profile) of
- undefined ->
- %% Here we a new connction on a new Host/Port/Profile
- Spec = child_spec(Address, Port, Profile, Options),
- supervisor:start_child(?MODULE, Spec);
- Pid ->
- {ok,Pid}
- end.
-
-
-start_system_subsystem(Host, Port, Profile, Options0) ->
- Options = ?DELETE_OPT(password, Options0),
- ssh_controller:start_system_subsystem(client_controller, ?MODULE, Host, Port, Profile, Options,
- child_spec(Host, Port, Profile, Options)
- ).
-
-stop_child(ChildId) when is_tuple(ChildId) ->
- supervisor:terminate_child(?SSHC_SUP, ChildId);
-stop_child(ChildPid) when is_pid(ChildPid)->
- stop_child(system_name(ChildPid)).
-
-stop_system(SysSup) ->
- ssh_controller:stop_system(client_controller, SysSup).
-
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
-init(_) ->
- SupFlags = #{strategy => one_for_one,
- intensity => 0,
- period => 3600
- },
- ChildSpecs = [#{id => client_controller,
- start => {ssh_controller, start_link, [client, client_controller]},
- restart => permanent,
- type => worker
- }],
- {ok, {SupFlags,ChildSpecs}}.
-
-%%%=========================================================================
-%%% Internal functions
-%%%=========================================================================
-child_spec(Address, Port, Profile, Options) ->
- #{id => id(Address, Port, Profile),
- start => {ssh_system_sup, start_link, [client, Address, Port, Profile, Options]},
- restart => temporary,
- type => supervisor
- }.
-
-id(Address, Port, Profile) ->
- {client, ssh_system_sup, Address, Port, Profile}.
-
-system_name(SysSup) ->
- case lists:keyfind(SysSup, 2, supervisor:which_children(?SSHC_SUP)) of
- {Name, SysSup, _, _} -> Name;
- false -> undefind
- end.
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/sshd_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/sshd_sup.erl
+++ /dev/null
@@ -1,105 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2018. 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%
-%%
-%%
-%%----------------------------------------------------------------------
-%% Purpose: The top supervisor for ssh servers hangs under
-%% ssh_sup.
-%%----------------------------------------------------------------------
-
--module(sshd_sup).
-
--behaviour(supervisor).
-
--include("ssh.hrl").
-
--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() ->
- %% 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(Address, Port, Profile, Options) ->
- case ssh_system_sup:system_supervisor(Address, Port, Profile) of
- undefined ->
- %% 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, Address, Port, Profile, Options),
- {ok,Pid}
- end.
-
-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) ->
- Id = id(Address, Port, Profile),
- stop_child(Id).
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
-init(_) ->
- SupFlags = #{strategy => one_for_one,
- intensity => 10,
- period => 3600
- },
- ChildSpecs = [
- ],
- {ok, {SupFlags,ChildSpecs}}.
-
-%%%=========================================================================
-%%% Internal functions
-%%%=========================================================================
-child_spec(Address, Port, Profile, Options) ->
- #{id => id(Address, Port, Profile),
- start => {ssh_system_sup, start_link, [server, Address, Port, Profile, Options]},
- restart => temporary,
- type => supervisor
- }.
-
-id(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.
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_agent.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_agent.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_agent.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2023. 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.
@@ -173,7 +173,7 @@ send(Request, Opts) ->
%% Message packing
pack(Data) ->
- <<(size(Data)):32/unsigned-big-integer, Data/binary>>.
+ <<(byte_size(Data)):32/unsigned-big-integer, Data/binary>>.
%% SSH Agent message encoding
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_auth.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_auth.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_auth.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2022. 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.
@@ -149,7 +149,7 @@ get_public_key(SigAlg, #ssh{opts = Opts}
try
%% Check the key - the KeyCb may be a buggy plugin
true = ssh_transport:valid_key_sha_alg(private, PrivKey, KeyAlg),
- Key = ssh_transport:extract_public_key(PrivKey),
+ Key = ssh_file:extract_public_key(PrivKey),
ssh_message:ssh2_pubkey_encode(Key)
of
PubKeyBlob -> {ok, {PrivKey, PubKeyBlob}}
@@ -172,26 +172,28 @@ publickey_msg([SigAlg, #ssh{user = User,
SigAlgStr = atom_to_list(SigAlg),
SigData = build_sig_data(SessionId, User, Service, PubKeyBlob, SigAlgStr),
- Sig = case Key of
- {ssh2_pubkey, PubKeyBlob} ->
- ssh_transport:call_KeyCb(sign, [PubKeyBlob, SigData], Opts);
-
- {PrivKey, PubKeyBlob} ->
- Hash = ssh_transport:sha(SigAlg),
- ssh_transport:sign(SigData, Hash, PrivKey)
- end,
-
- SigBlob = list_to_binary([?string(SigAlgStr),
- ?binary(Sig)]),
-
- {#ssh_msg_userauth_request{user = User,
- service = Service,
- method = "publickey",
- data = [?TRUE,
- ?string(SigAlgStr),
- ?binary(PubKeyBlob),
- ?binary(SigBlob)]},
- Ssh};
+ SigRes = case Key of
+ {ssh2_pubkey, PubKeyBlob} ->
+ {ok, ssh_transport:call_KeyCb(sign, [PubKeyBlob, SigData], Opts)};
+ {PrivKey, PubKeyBlob} ->
+ ssh_transport:sign(SigData, SigAlg, PrivKey, Ssh)
+ end,
+ case SigRes of
+ {ok,Sig} ->
+ SigBlob = list_to_binary([?string(SigAlgStr),
+ ?binary(Sig)]),
+
+ {#ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "publickey",
+ data = [?TRUE,
+ ?string(SigAlgStr),
+ ?binary(PubKeyBlob),
+ ?binary(SigBlob)]},
+ Ssh};
+ {error,_} ->
+ {not_ok, Ssh}
+ end;
_ ->
{not_ok, Ssh}
@@ -270,11 +272,21 @@ handle_userauth_request(#ssh_msg_useraut
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
method = "none"}, _,
- #ssh{userauth_supported_methods = Methods} = Ssh) ->
- {not_authorized, {User, undefined},
- {#ssh_msg_userauth_failure{authentications = Methods,
- partial_success = false}, Ssh}
- };
+ #ssh{userauth_supported_methods = Methods,
+ opts = Opts} = Ssh) ->
+ case ?GET_OPT(no_auth_needed, Opts) of
+ false ->
+ %% The normal case
+ {not_authorized, {User, undefined},
+ {#ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh}
+ };
+ true ->
+ %% RFC 4252 5.2
+ {authorized, User,
+ {#ssh_msg_userauth_success{}, Ssh}
+ }
+ end;
handle_userauth_request(#ssh_msg_userauth_request{user = User,
service = "ssh-connection",
@@ -502,7 +514,7 @@ check_password(User, Password, #ssh{opts
undefined ->
Static = get_password_option(Opts, User),
- {crypto:equal_const_time(Password,Static), Ssh};
+ {ssh_lib:comp(Password,Static), Ssh};
Checker when is_function(Checker,2) ->
{Checker(User, Password), Ssh};
@@ -545,13 +557,16 @@ pre_verify_sig(User, KeyBlob, #ssh{opts
verify_sig(SessionId, User, Service, AlgBin, KeyBlob, SigWLen, #ssh{opts=Opts} = Ssh) ->
try
Alg = binary_to_list(AlgBin),
+ true = lists:member(list_to_existing_atom(Alg),
+ proplists:get_value(public_key,
+ ?GET_OPT(preferred_algorithms,Opts))),
Key = ssh_message:ssh2_pubkey_decode(KeyBlob), % or exception
true = ssh_transport:call_KeyCb(is_auth_key, [Key, User], Opts),
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, Ssh)
+ ssh_transport:verify(PlainText, list_to_existing_atom(Alg), Sig, Key, Ssh)
catch
_:_ ->
false
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_bits.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_bits.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_bits.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. 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.
@@ -40,20 +40,20 @@ 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 >>;
+ <<(byte_size(V)+2):32/unsigned-big-integer, 0,B1,V/binary >>;
_ ->
- <<(size(V)+1):32/unsigned-big-integer, B1,V/binary >>
+ <<(byte_size(V)+1):32/unsigned-big-integer, B1,V/binary >>
end;
mpint(N) when N<0 ->
- Sxn = 8*size(binary:encode_unsigned(-N)),
+ Sxn = bit_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 >>;
+ <<(byte_size(V)+1):32/unsigned-big-integer, B1,V/binary >>;
_ ->
- <<(size(V)+2):32/unsigned-big-integer, 255,B1,V/binary >>
+ <<(byte_size(V)+2):32/unsigned-big-integer, 255,B1,V/binary >>
end.
%%%----------------------------------------------------------------
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_channel_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_channel_sup.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_channel_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2021. 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.
@@ -40,35 +40,17 @@ start_link(Args) ->
start_child(client, ChannelSup, ConnRef, Callback, Id, Args, Exec, _Opts) when is_pid(ConnRef) ->
- start_the_child(ssh_client_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
+ start_the_channel(ssh_client_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
+
start_child(server, ChannelSup, ConnRef, Callback, Id, Args, Exec, Opts) when is_pid(ConnRef) ->
case max_num_channels_not_exceeded(ChannelSup, Opts) of
true ->
- start_the_child(ssh_server_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
+ start_the_channel(ssh_server_channel, ChannelSup, ConnRef, Callback, Id, Args, Exec);
false ->
{error, max_num_channels_exceeded}
end.
-start_the_child(ChanMod, ChannelSup, ConnRef, Callback, Id, Args, Exec) ->
- ChildSpec =
- #{id => make_ref(),
- start => {ChanMod, start_link, [ConnRef, Id, Callback, Args, Exec]},
- restart => temporary,
- type => worker,
- modules => [ChanMod]
- },
- case supervisor:start_child(ChannelSup, ChildSpec) of
- {ok, Pid} ->
- {ok, Pid};
- {ok, Pid, _Info} ->
- {ok,Pid};
- {error, {Error,_Info}} ->
- {error, Error};
- {error, Error} ->
- {error, Error}
- end.
-
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
@@ -88,3 +70,20 @@ max_num_channels_not_exceeded(ChannelSup
supervisor:which_children(ChannelSup)]),
%% Note that NumChannels is BEFORE starting a new one
NumChannels < MaxNumChannels.
+
+
+start_the_channel(ChanMod, ChannelSup, ConnRef, Callback, Id, Args, Exec) ->
+ ChildSpec =
+ #{id => make_ref(),
+ start => {ChanMod, start_link, [ConnRef, Id, Callback, Args, Exec]},
+ restart => temporary,
+ type => worker,
+ modules => [ChanMod]
+ },
+ case supervisor:start_child(ChannelSup, ChildSpec) of
+ {ok, Pid} -> {ok, Pid};
+ {ok, Pid, _Info} -> {ok, Pid};
+ {error, {Error,_Info}} -> {error, Error};
+ {error, Error} -> {error, Error}
+ end.
+
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_cli.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_cli.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_cli.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. 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.
@@ -79,7 +79,7 @@ handle_ssh_msg({ssh_cm, _ConnectionHandl
#state{group = Group} = State0) ->
{Enc, State} = guess_encoding(Data, State0),
List = unicode:characters_to_list(Data, Enc),
- to_group(List, Group),
+ to_group(List, Group, get_dumb(State#state.pty)),
{ok, State};
handle_ssh_msg({ssh_cm, ConnectionHandler,
@@ -281,6 +281,10 @@ handle_msg({Group, get_unicode_state}, S
Group ! {self(), get_unicode_state, false},
{ok, State};
+handle_msg({Group, get_terminal_state}, State) ->
+ Group ! {self(), get_terminal_state, true},
+ {ok, State};
+
handle_msg({Group, tty_geometry}, #state{group = Group,
pty = Pty
} = State) ->
@@ -389,21 +393,28 @@ out_enc(#state{encoding = PeerEnc,
%%--------------------------------------------------------------------
-to_group([], _Group) ->
+to_group([], _Group, _Dumb) ->
ok;
-to_group([$\^C | Tail], Group) ->
+to_group([$\^C | Tail], Group, Dumb) ->
exit(Group, interrupt),
- to_group(Tail, Group);
-to_group(Data, Group) ->
+ to_group(Tail, Group, Dumb);
+to_group(Data, Group, Dumb) ->
Func = fun(C) -> C /= $\^C end,
Tail = case lists:splitwith(Func, Data) of
{[], Right} ->
Right;
{Left, Right} ->
- Group ! {self(), {data, Left}},
+ %% Filter out escape sequences, only support Ctrl sequences
+ Left1 = if Dumb -> replace_escapes(Left); true -> Left end,
+ Group ! {self(), {data, Left1}},
Right
end,
- to_group(Tail, Group).
+ to_group(Tail, Group, Dumb).
+replace_escapes(Data) ->
+ lists:flatten([ if C =:= 27 ->
+ [$^,C+64];
+ true -> C
+ end || C <- Data]).
%%--------------------------------------------------------------------
%%% io_request, handle io requests from the user process,
@@ -420,14 +431,44 @@ io_request({put_chars, Cs}, Buf, Tty, _G
put_chars(bin_to_list(Cs), Buf, Tty);
io_request({put_chars, unicode, Cs}, Buf, Tty, _Group) ->
put_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
+io_request({put_expand_no_trim, unicode, Expand}, Buf, Tty, _Group) ->
+ insert_chars(unicode:characters_to_list(Expand, unicode), Buf, Tty);
io_request({insert_chars, Cs}, Buf, Tty, _Group) ->
insert_chars(bin_to_list(Cs), Buf, Tty);
io_request({insert_chars, unicode, Cs}, Buf, Tty, _Group) ->
insert_chars(unicode:characters_to_list(Cs,unicode), Buf, Tty);
io_request({move_rel, N}, Buf, Tty, _Group) ->
move_rel(N, Buf, Tty);
+io_request({move_line, N}, Buf, Tty, _Group) ->
+ move_line(N, Buf, Tty);
+io_request({move_combo, L, V, R}, Buf, Tty, _Group) ->
+ {ML, Buf1} = move_rel(L, Buf, Tty),
+ {MV, Buf2} = move_line(V, Buf1, Tty),
+ {MR, Buf3} = move_rel(R, Buf2, Tty),
+ {[ML,MV,MR], Buf3};
+io_request(new_prompt, _Buf, _Tty, _Group) ->
+ {[], {[], {[],[]}, [], 0 }};
+io_request(delete_line, {_, {_, _}, _, Col}, Tty, _Group) ->
+ MoveToBeg = move_cursor(Col, 0, Tty),
+ {[MoveToBeg, "\e[J"],
+ {[],{[],[]},[],0}};
+io_request({redraw_prompt, Pbs, Pbs2, {LB, {Bef, Aft}, LA}}, Buf, Tty, _Group) ->
+ {ClearLine, Cleared} = io_request(delete_line, Buf, Tty, _Group),
+ CL = lists:reverse(Bef,Aft),
+ Text = Pbs ++ lists:flatten(lists:join("\n"++Pbs2, lists:reverse(LB)++[CL|LA])),
+ Moves = if LA /= [] ->
+ [Last|_] = lists:reverse(LA),
+ {move_combo, -length(Last), -length(LA), length(Bef)};
+ true ->
+ {move_rel, -length(Aft)}
+ end,
+ {T, InsertedText} = io_request({insert_chars, unicode:characters_to_binary(Text)}, Cleared, Tty, _Group),
+ {M, Moved} = io_request(Moves, InsertedText, Tty, _Group),
+ {[ClearLine, T, M], Moved};
io_request({delete_chars,N}, Buf, Tty, _Group) ->
delete_chars(N, Buf, Tty);
+io_request(clear, Buf, _Tty, _Group) ->
+ {"\e[H\e[2J", Buf};
io_request(beep, Buf, _Tty, _Group) ->
{[7], Buf};
@@ -441,13 +482,12 @@ io_request({requests,Rs}, Buf, Tty, Grou
io_request(tty_geometry, Buf, Tty, Group) ->
io_requests([{move_rel, 0}, {put_chars, unicode, [10]}],
Buf, Tty, [], Group);
- %{[], Buf};
%% New in 18
io_request({put_chars_sync, Class, Cs, Reply}, Buf, Tty, Group) ->
%% We handle these asynchronous for now, if we need output guarantees
%% we have to handle these synchronously
- Group ! {reply, Reply},
+ Group ! {reply, Reply, ok},
io_request({put_chars, Class, Cs}, Buf, Tty, Group);
io_request(_R, Buf, _Tty, _Group) ->
@@ -478,57 +518,67 @@ get_tty_command(left, N, _TerminalType)
-define(TABWIDTH, 8).
%% convert input characters to buffer and to writeout
-%% Note that the buf is reversed but the buftail is not
+%% Note that Bef is reversed but Aft is not
%% (this is handy; the head is always next to the cursor)
-conv_buf([], AccBuf, AccBufTail, AccWrite, Col, _Tty) ->
- {AccBuf, AccBufTail, lists:reverse(AccWrite), Col};
-conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
- conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0, Tty);
-conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col, Tty) ->
- conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0, Tty);
-conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite0, _Col, Tty) ->
+conv_buf([], {LB, {Bef, Aft}, LA, Col}, AccWrite, _Tty) ->
+ {{LB, {Bef, Aft}, LA, Col}, lists:reverse(AccWrite)};
+conv_buf([13, 10 | Rest], {LB, {Bef, Aft}, LA, Col}, AccWrite, Tty = #ssh_pty{width = W}) ->
+ conv_buf(Rest, {[lists:reverse(Bef)|LB], {[], tl2(Aft)}, LA, Col+(W-(Col rem W))}, [10, 13 | AccWrite], Tty);
+conv_buf([13 | Rest], {LB, {Bef, Aft}, LA, Col}, AccWrite, Tty = #ssh_pty{width = W}) ->
+ conv_buf(Rest, {[lists:reverse(Bef)|LB], {[], tl1(Aft)}, LA, Col+(W-(Col rem W))}, [13 | AccWrite], Tty);
+conv_buf([10 | Rest],{LB, {Bef, Aft}, LA, Col}, AccWrite0, Tty = #ssh_pty{width = W}) ->
AccWrite =
case pty_opt(onlcr,Tty) of
0 -> [10 | AccWrite0];
1 -> [10,13 | AccWrite0];
undefined -> [10 | AccWrite0]
end,
- conv_buf(Rest, [], tl1(AccBufTail), AccWrite, 0, Tty);
-conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col, Tty) ->
- conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1, Tty).
-
-
-%%% put characters at current position (possibly overwriting
-%%% characters after current position in buffer)
-put_chars(Chars, {Buf, BufTail, Col}, Tty) ->
- {NewBuf, NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, BufTail, [], Col, Tty),
- {WriteBuf, {NewBuf, NewBufTail, NewCol}}.
+ conv_buf(Rest, {[lists:reverse(Bef)|LB], {[], tl1(Aft)}, LA, Col+(W - (Col rem W))}, AccWrite, Tty);
+conv_buf([C | Rest], {LB, {Bef, Aft}, LA, Col}, AccWrite, Tty) ->
+ conv_buf(Rest, {LB, {[C|Bef], tl1(Aft)}, LA, Col+1}, [C | AccWrite], Tty).
+
+%%% put characters before the prompt
+put_chars(Chars, Buf, Tty) ->
+ Dumb = get_dumb(Tty),
+ case Buf of
+ {[],{[],[]},[],_} -> {_, WriteBuf} = conv_buf(Chars, Buf, [], Tty),
+ {WriteBuf, Buf};
+ _ when Dumb =:= false ->
+ {Delete, DeletedState} = io_request(delete_line, Buf, Tty, []),
+ {_, PutBuffer} = conv_buf(Chars, DeletedState, [], Tty),
+ {Redraw, _} = io_request(redraw_prompt_pre_deleted, Buf, Tty, []),
+ {[Delete, PutBuffer, Redraw], Buf};
+ _ ->
+ %% When we have a dumb terminal, we get messages via put_chars requests
+ %% so state should be empty {[],{[],[]},[],_},
+ %% but if we end up here its not, so keep the state
+ {_, WriteBuf} = conv_buf(Chars, Buf, [], Tty),
+ {WriteBuf, Buf}
+ end.
%%% insert character at current position
-insert_chars([], {Buf, BufTail, Col}, _Tty) ->
- {[], {Buf, BufTail, Col}};
-insert_chars(Chars, {Buf, BufTail, Col}, Tty) ->
- {NewBuf, _NewBufTail, WriteBuf, NewCol} =
- conv_buf(Chars, Buf, [], [], Col, Tty),
- M = move_cursor(special_at_width(NewCol+length(BufTail), Tty), NewCol, Tty),
- {[WriteBuf, BufTail | M], {NewBuf, BufTail, NewCol}}.
+insert_chars([], Buf, _Tty) ->
+ {[], Buf};
+insert_chars(Chars, {_LB,{_Bef, Aft},LA, _Col}=Buf, Tty) ->
+ {{NewLB, {NewBef, _NewAft}, _NewLA, NewCol}, WriteBuf} = conv_buf(Chars, Buf, [], Tty),
+ M = move_cursor(special_at_width(NewCol+length(Aft), Tty), NewCol, Tty),
+ {[WriteBuf, Aft | M], {NewLB,{NewBef, Aft},LA, NewCol}}.
%%% delete characters at current position, (backwards if negative argument)
-delete_chars(0, {Buf, BufTail, Col}, _Tty) ->
- {[], {Buf, BufTail, Col}};
-delete_chars(N, {Buf, BufTail, Col}, Tty) when N > 0 ->
- NewBufTail = nthtail(N, BufTail),
- M = move_cursor(Col + length(NewBufTail) + N, Col, Tty),
- {[NewBufTail, lists:duplicate(N, $ ) | M],
- {Buf, NewBufTail, Col}};
-delete_chars(N, {Buf, BufTail, Col}, Tty) -> % N < 0
- NewBuf = nthtail(-N, Buf),
+delete_chars(0, {LB,{Bef, Aft},LA, Col}, _Tty) ->
+ {[], {LB,{Bef, Aft},LA, Col}};
+delete_chars(N, {LB,{Bef, Aft},LA, Col}, Tty) when N > 0 ->
+ NewAft = nthtail(N, Aft),
+ M = move_cursor(Col + length(NewAft) + N, Col, Tty),
+ {[NewAft, lists:duplicate(N, $ ) | M],
+ {LB,{Bef, NewAft},LA, Col}};
+delete_chars(N, {LB,{Bef, Aft},LA, Col}, Tty) -> % N < 0
+ NewBef = nthtail(-N, Bef),
NewCol = case Col + N of V when V >= 0 -> V; _ -> 0 end,
M1 = move_cursor(Col, NewCol, Tty),
- M2 = move_cursor(special_at_width(NewCol+length(BufTail)-N, Tty), NewCol, Tty),
- {[M1, BufTail, lists:duplicate(-N, $ ) | M2],
- {NewBuf, BufTail, NewCol}}.
+ M2 = move_cursor(special_at_width(NewCol+length(Aft)-N, Tty), NewCol, Tty),
+ {[M1, Aft, lists:duplicate(-N, $ ) | M2],
+ {LB,{NewBef, Aft},LA, NewCol}}.
%%% Window change, redraw the current line (and clear out after it
%%% if current window is wider than previous)
@@ -536,52 +586,74 @@ window_change(Tty, OldTty, Buf)
when OldTty#ssh_pty.width == Tty#ssh_pty.width ->
%% No line width change
{[], Buf};
-window_change(Tty, OldTty, {Buf, BufTail, Col}) ->
+window_change(Tty, OldTty, {LB, {Bef, Aft}, LA, Col}) ->
case OldTty#ssh_pty.width - Tty#ssh_pty.width of
0 ->
%% No line width change
- {[], {Buf,BufTail,Col}};
+ {[], {LB, {Bef, Aft}, LA, Col}};
DeltaW0 when DeltaW0 < 0,
- BufTail == [] ->
+ Aft == [] ->
% Line width is decreased, cursor is at end of input
- {[], {Buf,BufTail,Col}};
+ {[], {LB, {Bef, Aft}, LA, Col}};
DeltaW0 when DeltaW0 < 0,
- BufTail =/= [] ->
+ Aft =/= [] ->
% Line width is decreased, cursor is not at end of input
- {[], {Buf,BufTail,Col}};
+ {[], {LB, {Bef, Aft}, LA, Col}};
DeltaW0 when DeltaW0 > 0 ->
% Line width is increased
- {[], {Buf,BufTail,Col}}
+ {[], {LB, {Bef, Aft}, LA, Col}}
end.
%% move around in buffer, respecting pad characters
-step_over(0, Buf, [?PAD | BufTail], Col) ->
- {[?PAD | Buf], BufTail, Col+1};
-step_over(0, Buf, BufTail, Col) ->
- {Buf, BufTail, Col};
-step_over(N, [C | Buf], BufTail, Col) when N < 0 ->
+step_over(0, {LB, {Bef, [?PAD |Aft]}, LA, Col}) ->
+ {LB, {[?PAD | Bef], Aft}, LA, Col+1};
+step_over(0, {LB, {Bef, Aft}, LA, Col}) ->
+ {LB, {Bef, Aft}, LA, Col};
+step_over(N, {LB, {[C | Bef], Aft}, LA, Col}) when N < 0 ->
N1 = ifelse(C == ?PAD, N, N+1),
- step_over(N1, Buf, [C | BufTail], Col-1);
-step_over(N, Buf, [C | BufTail], Col) when N > 0 ->
+ step_over(N1, {LB, {Bef, [C | Aft]}, LA, Col-1});
+step_over(N, {LB, {Bef, [C | Aft]}, LA, Col}) when N > 0 ->
N1 = ifelse(C == ?PAD, N, N-1),
- step_over(N1, [C | Buf], BufTail, Col+1).
+ step_over(N1, {LB, {[C | Bef], Aft}, LA, Col+1}).
%%% an empty line buffer
-empty_buf() -> {[], [], 0}.
+empty_buf() -> {[], {[], []}, [], 0}.
%%% col and row from position with given width
col(N, W) -> N rem W.
row(N, W) -> N div W.
%%% move relative N characters
-move_rel(N, {Buf, BufTail, Col}, Tty) ->
- {NewBuf, NewBufTail, NewCol} = step_over(N, Buf, BufTail, Col),
+move_rel(N, {_LB, {_Bef, _Aft}, _LA, Col}=Buf, Tty) ->
+ {NewLB, {NewBef, NewAft}, NewLA, NewCol} = step_over(N, Buf),
M = move_cursor(Col, NewCol, Tty),
- {M, {NewBuf, NewBufTail, NewCol}}.
+ {M, {NewLB, {NewBef, NewAft}, NewLA, NewCol}}.
+move_line(V, {_LB, {_Bef, _Aft}, _LA, Col}, Tty = #ssh_pty{width=W})
+ when V < 0, length(_LB) >= -V ->
+ {LinesJumped, [B|NewLB]} = lists:split(-V -1, _LB),
+ CL = lists:reverse(_Bef,_Aft),
+ NewLA = lists:reverse([CL|LinesJumped], _LA),
+ {NewBB, NewAft} = lists:split(min(length(_Bef),length(B)), B),
+ NewBef = lists:reverse(NewBB),
+ NewCol = Col - length(_Bef) - lists:sum([((length(L)-1) div W)*W + W || L <- [B|LinesJumped]]) + length(NewBB),
+ M = move_cursor(Col, NewCol, Tty),
+ {M, {NewLB, {NewBef, NewAft}, NewLA, NewCol}};
+move_line(V, {_LB, {_Bef, _Aft}, _LA, Col}, Tty = #ssh_pty{width=W})
+ when V > 0, length(_LA) >= V ->
+ {LinesJumped, [A|NewLA]} = lists:split(V -1, _LA),
+ CL = lists:reverse(_Bef,_Aft),
+ NewLB = lists:reverse([CL|LinesJumped],_LB),
+ {NewBB, NewAft} = lists:split(min(length(_Bef),length(A)), A),
+ NewBef = lists:reverse(NewBB),
+ NewCol = Col - length(_Bef) + lists:sum([((length(L)-1) div W)*W + W || L <- [CL|LinesJumped]]) + length(NewBB),
+ M = move_cursor(Col, NewCol, Tty),
+ {M, {NewLB, {NewBef, NewAft}, NewLA, NewCol}};
+move_line(_, Buf, _) ->
+ {"", Buf}.
%%% give move command for tty
move_cursor(A, A, _Tty) ->
[];
@@ -666,7 +738,9 @@ start_shell(ConnectionHandler, State) ->
{_,_,_} = Shell ->
Shell
end,
- State#state{group = group:start(self(), ShellSpawner, [{echo, get_echo(State#state.pty)}]),
+ State#state{group = group:start(self(), ShellSpawner,
+ [{dumb, get_dumb(State#state.pty)},{expand_below, false},
+ {echo, get_echo(State#state.pty)}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -687,7 +761,8 @@ start_exec_shell(ConnectionHandler, Cmd,
{M,F,A} ->
{M, F, A++[Cmd]}
end,
- State#state{group = group:start(self(), ExecShellSpawner, [{echo,false}]),
+ State#state{group = group:start(self(), ExecShellSpawner, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}.
%%--------------------------------------------------------------------
@@ -771,7 +846,8 @@ exec_in_self_group(ConnectionHandler, Ch
end
end)
end,
- {ok, State#state{group = group:start(self(), Exec, [{echo,false}]),
+ {ok, State#state{group = group:start(self(), Exec, [{expand_below, false},
+ {echo,false}]),
buf = empty_buf()}}.
@@ -780,6 +856,13 @@ t2str(T) -> try io_lib:format("~s",[T])
end.
%%--------------------------------------------------------------------
+get_dumb(Tty) ->
+ try
+ Tty#ssh_pty.term =:= "dumb"
+ catch
+ _:_ -> false
+ end.
+
% Pty can be undefined if the client never sets any pty options before
% starting the shell.
get_echo(Tty) ->
@@ -923,4 +1006,3 @@ fmt_kv1({K,h,V}) -> io_lib:format("~n~p:
type(0) -> "0 (normal data)";
type(1) -> "1 (extended data, i.e. errors)";
type(T) -> T.
-
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_client_channel.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_client_channel.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_client_channel.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -70,7 +70,8 @@
-export([cache_create/0, cache_lookup/2, cache_update/2,
cache_delete/1, cache_delete/2, cache_foldl/3,
cache_info/2, cache_find/2,
- get_print_info/1]).
+ get_print_info/1, get_print_info/2
+ ]).
-behaviour(ssh_dbg).
-export([ssh_dbg_trace_points/0, ssh_dbg_flags/1, ssh_dbg_on/1, ssh_dbg_off/1, ssh_dbg_format/2]).
@@ -212,6 +213,9 @@ handle_call(get_print_info, _From, State
},
{reply, Reply, State};
+handle_call({get_print_info,channel_cb}, _From, State) ->
+ {reply, State#state.channel_cb, State};
+
handle_call(Request, From, #state{channel_cb = Module,
channel_state = ChannelState} = State) ->
try Module:handle_call(Request, From, ChannelState) of
@@ -257,10 +261,9 @@ handle_info({ssh_cm, ConnectionManager,
(catch ssh_connection:close(ConnectionManager, ChannelId)),
{stop, normal, State#state{close_sent = true}};
-handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager,
- channel_cb = Module,
- channel_state = ChannelState0} = State) ->
- case Module:handle_ssh_msg(Msg, ChannelState0) of
+handle_info({ssh_cm, _, _} = Msg, #state{channel_cb = Module,
+ channel_state = ChannelState0} = State) ->
+ try Module:handle_ssh_msg(Msg, ChannelState0) of
{ok, ChannelState} ->
adjust_window(Msg),
{noreply, State#state{channel_state = ChannelState}};
@@ -268,34 +271,39 @@ handle_info({ssh_cm, _, _} = Msg, #state
adjust_window(Msg),
{noreply, State#state{channel_state = ChannelState}, Timeout};
{stop, ChannelId, ChannelState} ->
- catch ssh_connection:close(ConnectionManager, ChannelId),
- {stop, normal, State#state{close_sent = true,
- channel_state = ChannelState}}
+ do_the_close(Msg, ChannelId, State#state{channel_state = ChannelState})
+ catch
+ error:_ ->
+ do_the_close(Msg, State#state.channel_id, State)
end;
-handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module,
+handle_info(Msg, #state{channel_cb = Module,
channel_state = ChannelState0} = State) ->
- case Module:handle_msg(Msg, ChannelState0) of
+ try Module:handle_msg(Msg, ChannelState0)
+ of
{ok, ChannelState} ->
{noreply, State#state{channel_state = ChannelState}};
{ok, ChannelState, Timeout} ->
{noreply, State#state{channel_state = ChannelState}, Timeout};
- {stop, Reason, ChannelState} when is_atom(Reason)->
- {stop, Reason, State#state{close_sent = true,
- channel_state = ChannelState}};
{stop, ChannelId, ChannelState} ->
- Reason =
- case Msg of
- {'EXIT', _Pid, shutdown} ->
- shutdown;
- _ ->
- normal
- end,
- (catch ssh_connection:close(ConnectionManager, ChannelId)),
- {stop, Reason, State#state{close_sent = true,
- channel_state = ChannelState}}
+ do_the_close(Msg, ChannelId, State#state{channel_state = ChannelState})
+ catch
+ error:function_clause when tuple_size(Msg) == 3,
+ element(1,Msg) == 'EXIT' ->
+ do_the_close(Msg, State#state.channel_id, State)
end.
+
+
+do_the_close(Msg, ChannelId, State = #state{close_sent = false,
+ cm = ConnectionManager}) ->
+ catch ssh_connection:close(ConnectionManager, ChannelId),
+ do_the_close(Msg, ChannelId, State#state{close_sent=true});
+do_the_close({'EXIT', _Pid, shutdown=Reason}, _, State) -> {stop, Reason, State};
+do_the_close({'EXIT', _Pid, {shutdown,_}=Reason}, _, State) -> {stop, Reason, State};
+do_the_close(_Msg, _, State) -> {stop, normal, State}.
+
+
%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
@@ -361,6 +369,9 @@ cache_find(ChannelPid, Cache) ->
get_print_info(Pid) ->
call(Pid, get_print_info, 1000).
+get_print_info(Pid, Arg) ->
+ call(Pid, {get_print_info,Arg}, 1000).
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -379,7 +390,7 @@ handle_cb_result({stop, Reason, ChannelS
adjust_window({ssh_cm, ConnectionManager,
{data, ChannelId, _, Data}}) ->
- ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data));
+ ssh_connection:adjust_window(ConnectionManager, ChannelId, byte_size(Data));
adjust_window(_) ->
ok.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_client_key_api.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_client_key_api.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_client_key_api.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2021. 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.
@@ -82,7 +82,7 @@
%%% in the argument Host with the port Port.
%%%
%%% Due to compatibility reasons, the OTP/SSH application first
-%%% trys add_host_key/4 and then the old add_host_key/3
+%%% tries add_host_key/4 and then the old add_host_key/3
-callback add_host_key(Host :: inet:ip_address() | inet:hostname()
| [inet:ip_address() | inet:hostname()],
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_daemon_channel.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_daemon_channel.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_daemon_channel.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2022. 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,7 +25,7 @@
-module(ssh_daemon_channel).
-%% API to server side channel that can be pluged into the erlang ssh daemeon
+%% API to server side channel that can be plugged into the erlang ssh daemeon
-callback init(Args :: term()) ->
{ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} |
{stop, Reason :: term()} | ignore.
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_dbg.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_dbg.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2023. 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.
@@ -113,7 +113,7 @@ start(IoFmtFun) when is_function(IoFmtFu
stop() ->
try
- dbg:stop_clear(),
+ dbg:stop(),
gen_server:stop(?SERVER)
catch
_:_ -> ok
@@ -156,26 +156,26 @@ go_on() ->
on(IsOn).
%%%----------------------------------------------------------------
-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(B) when is_binary(B), byte_size(B)>256 -> {'*** SHRUNK BIN',
+ byte_size(B),
+ element(1,split_binary(B,64)),
+ '...',
+ element(2,split_binary(B,byte_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.
%%%----------------------------------------------------------------
-%% Replace any occurence of {Name,...}, with "#Name{}"
+%% Replace any occurrence of {Name,...}, with "#Name{}"
reduce_state(T, RecordExample) ->
Name = element(1, RecordExample),
- Arity = size(RecordExample),
+ Arity = tuple_size(RecordExample),
reduce_state(T, Name, Arity).
-%% Replace any occurence of {Name,...}, with "#Name{}"
+%% Replace any occurrence of {Name,...}, with "#Name{}"
reduce_state(T, Name, Arity) when element(1,T) == Name,
- size(T) == Arity ->
+ tuple_size(T) == Arity ->
lists:concat(['#',Name,'{}']);
reduce_state(L, Name, Arity) when is_list(L) ->
[reduce_state(E,Name,Arity) || E <- L];
@@ -353,7 +353,7 @@ trace_pid(T) when element(1,T)==trace
%% Pick last element, the Time Stamp, and format it
trace_ts(T) when element(1,T)==trace_ts ->
- ts( element(size(T), T) ).
+ ts( element(tuple_size(T), T) ).
%% Make a tuple of all elements but the 1st, 2nd and last
trace_info(T) ->
@@ -400,7 +400,7 @@ try_all_types_in_all_modules(TypesOn, Ar
catch
_:_ ->
%% and finally, signal for special formatting
- %% if noone else formats it
+ %% if no one else formats it
Acc
end
end
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_file.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_file.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_file.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2023. 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.
@@ -30,7 +30,8 @@
-include("ssh.hrl").
%% experimental:
--export([decode_ssh_file/4]).
+-export([decode_ssh_file/4
+ ]).
%%%--------------------- server exports ---------------------------
-behaviour(ssh_server_key_api).
@@ -44,10 +45,16 @@
-export_type([pubkey_passphrase_client_options/0]).
-type pubkey_passphrase_client_options() :: {dsa_pass_phrase, string()}
| {rsa_pass_phrase, string()}
-%% Not yet implemented: | {ed25519_pass_phrase, string()}
-%% Not yet implemented: | {ed448_pass_phrase, string()}
+ %% Not yet implemented: | {ed25519_pass_phrase, string()}
+ %% Not yet implemented: | {ed448_pass_phrase, string()}
| {ecdsa_pass_phrase, string()} .
+%%%--------------------- utility exports ---------------------------
+-export([decode/2, encode/2]).
+-export([extract_public_key/1]).
+
+-define(ENCODED_LINE_LENGTH, 68).
+
%%%--------------------- common exports ---------------------------
-export_type([user_dir_common_option/0,
user_dir_fun_common_option/0
@@ -59,6 +66,10 @@
-type optimize_key_lookup() :: {optimize, time|space} .
+-type key() :: public_key:public_key() | public_key:private_key() .
+-type experimental_openssh_key_v1() :: [{key(), openssh_key_v1_attributes()}].
+-type openssh_key_v1_attributes() :: [{atom(),term()}].
+
%%%================================================================
%%%
%%% API
@@ -87,7 +98,7 @@ is_auth_key(Key0, User, Opts) ->
Key = encode_key(Key0),
lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys"), Opts)
orelse
- lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys2"), Opts).
+ lookup_auth_keys(KeyType, Key, filename:join(Dir,"authorized_keys2"), Opts).
%%%---------------- CLIENT API ------------------------------------
-spec user_key(Algorithm, Options) -> Result when
@@ -146,6 +157,267 @@ add_host_key(Hosts0, Port, Key, Opts) ->
{error,{add_host_key,Error}}
end.
+%%%---------------- UTILITY API -----------------------------------
+%%% In public key before OTP-24.0 as ssh_decode/2 and ssh_encode/2
+
+-spec decode(SshBin, Type) -> Decoded | {error,term()}
+ when SshBin :: binary(),
+ Type :: ssh2_pubkey
+ | public_key
+ | openssh_key
+ | rfc4716_key
+ | openssh_key_v1 % Experimental
+ | known_hosts
+ | auth_keys,
+ Decoded :: Decoded_ssh2_pubkey
+ | Decoded_public
+ | Decoded_openssh
+ | Decoded_rfc4716
+ | Decoded_openssh_key_v1
+ | Decoded_known_hosts
+ | Decoded_auth_keys,
+
+ Decoded_ssh2_pubkey :: public_key:public_key(),
+ Decoded_public :: Decoded_rfc4716
+ | Decoded_openssh_key_v1
+ | Decoded_openssh,
+ Decoded_openssh :: [{public_key:public_key(), [{comment,string()}]}],
+ Decoded_rfc4716 :: [{key(), [{headers,Attrs}]}],
+ Decoded_openssh_key_v1 :: experimental_openssh_key_v1(),
+ Decoded_known_hosts :: [{public_key:public_key(), [{comment,string()}
+ | {hostnames,[string()]}]}],
+ Decoded_auth_keys :: [{public_key:public_key(), [{comment,string()}
+ | {options,[string()]}]}],
+ Attrs :: {Key::string(), Value::string()} .
+
+decode(KeyBin, ssh2_pubkey) when is_binary(KeyBin) ->
+ ssh_message:ssh2_pubkey_decode(KeyBin);
+
+decode(KeyBin, public_key) when is_binary(KeyBin) ->
+ Type = case KeyBin of
+ <<"-----BEGIN OPENSSH",_/binary>> -> openssh_key_v1;
+ <<"----",_/binary>> -> rfc4716_key;
+ _ -> openssh_key
+ end,
+ decode(KeyBin, Type);
+
+decode(KeyBin, Type) when is_binary(KeyBin) andalso
+ (Type==rfc4716_key orelse
+ Type==openssh_key_v1 % Experimental
+ ) ->
+ %% Ex: <<"---- BEGIN SSH2 PUBLIC KEY ----\n....">> (rfc4716_key)
+ %% <<"-----BEGIN OPENSSH PRIVATE KEY-----\n....">> (openssh_key_v1)
+ case decode_ssh_file(public, any, KeyBin, ignore) of
+ {ok,Keys} ->
+ [{Key,
+ if
+ Attrs =/= [] ->
+ [{headers, [{binary_to_list(K),binary_to_list(V)} || {K,V} <- Attrs]}];
+ Attrs == [] ->
+ []
+ end
+ }
+ || {Key,Attrs} <- Keys];
+
+ {error,Error} ->
+ {error,Error}
+ end;
+
+decode(KeyBin0, openssh_key) when is_binary(KeyBin0) ->
+ %% Ex: <<"ssh-rsa AAAAB12....3BC someone@example.com">>
+ try
+ [begin
+ [_,K|Rest] = binary:split(Line, <<" ">>, [global,trim_all]),
+ Key = ssh_message:ssh2_pubkey_decode(base64:decode(K)),
+ case Rest of
+ [Comment] -> {Key, [{comment,binary_to_list(Comment)}]};
+ [] -> {Key,[]}
+ end
+ end || Line <- split_in_nonempty_lines(KeyBin0)
+ ]
+ catch
+ _:_ -> {error, key_decode_failed}
+ end;
+
+decode(Bin, known_hosts) when is_binary(Bin) ->
+ [begin
+ Attrs =
+ [
+ {comment, binary_to_list(erlang:iolist_to_binary(lists:join(" ", Comment)))}
+ || Comment =/= []
+ ] ++
+ [
+ {hostnames,
+ [binary_to_list(HP)
+ || HP <- binary:split(HostPort,<<",">>,[global,trim_all])
+ ]}
+ ],
+ {ssh_message:ssh2_pubkey_decode(base64:decode(KeyBin)),
+ Attrs
+ }
+ end
+ || L <- split_in_nonempty_lines(Bin),
+ [HostPort,_KeyType,KeyBin|Comment] <- [binary:split(L,<<" ">>,[global,trim_all])]
+ ];
+
+decode(Bin, auth_keys) when is_binary(Bin) ->
+ [begin
+ Attrs =
+ [
+ {comment, binary_to_list(erlang:iolist_to_binary(lists:join(" ", Comment)))}
+ || Comment =/= []
+ ] ++
+ [
+ {options, lists:map(fun erlang:binary_to_list/1, Options)}
+ || Options =/= []
+ ],
+ {ssh_message:ssh2_pubkey_decode(base64:decode(KeyBin)),
+ Attrs
+ }
+ end
+ || L <- split_in_nonempty_lines(Bin),
+ [Options,_KeyType,KeyBin|Comment] <-
+ case binary:match(L, [<<"ssh-rsa">>,
+ <<"rsa-sha2-">>,
+ <<"ssh-dss">>,
+ <<"ecdsa-sha2-nistp">>,
+ <<"ssh-ed">>
+ ]) of
+ nomatch ->
+ [];
+ {0, Len} when is_integer(Len) ->
+ [ [[] | binary:split(L,<<" ">>,[global,trim_all])] ];
+ {Pos,Len} when is_integer(Pos), is_integer(Len) ->
+ [ [binary:split(binary:part(L,0,Pos-1), <<",">>,[global,trim_all]) |
+ binary:split(binary:part(L,Pos,byte_size(L)-Pos), <<" ">>, [global,trim_all])]
+ ]
+ end
+ ];
+
+decode(_KeyBin, _Type) ->
+ error(badarg).
+
+%%%----------------------------------------------------------------
+-spec encode(InData, Type) -> binary() | {error,term()}
+ when Type :: ssh2_pubkey
+ | openssh_key
+ | rfc4716_key
+ | openssh_key_v1 % Experimental
+ | known_hosts
+ | auth_keys,
+ InData :: InData_ssh2_pubkey
+ | InData_openssh
+ | InData_rfc4716
+ | InData_openssh_key_v1
+ | InData_known_hosts
+ | InData_auth_keys,
+
+ InData_ssh2_pubkey :: public_key:public_key(),
+ InData_openssh :: [{public_key:public_key(), [{comment,string()}]}],
+ InData_rfc4716 :: [{key(), [{headers,Attrs}]}],
+ InData_openssh_key_v1 :: experimental_openssh_key_v1(),
+ InData_known_hosts :: [{public_key:public_key(), [{comment,string()}
+ | {hostnames,[string()]}]}],
+ InData_auth_keys :: [{public_key:public_key(), [{comment,string()}
+ | {options,[string()]}]}],
+ Attrs :: {Key::string(), Value::string()} .
+
+encode(Key, ssh2_pubkey) ->
+ ssh_message:ssh2_pubkey_encode(Key);
+
+encode(KeyAttrs, Type) when Type==rfc4716_key ;
+ Type==openssh_key_v1 % Experimental
+ ->
+ {Begin, End, F} =
+ case Type of
+ rfc4716_key ->
+ {"---- BEGIN SSH2 PUBLIC KEY ----\n",
+ "---- END SSH2 PUBLIC KEY ----\n",
+ fun ssh_message:ssh2_pubkey_encode/1};
+ openssh_key_v1 ->
+ {"-----BEGIN OPENSSH PRIVATE KEY-----\n",
+ "-----END OPENSSH PRIVATE KEY-----\n",
+ fun openssh_key_v1_encode/1}
+ end,
+ iolist_to_binary(
+ [
+ [Begin,
+ [rfc4716_encode_header(H) || H <- proplists:get_value(headers, Attrs, [])],
+ split_long_lines( base64:encode( F(Key) ) ),
+ "\n",
+ End
+ ] ||
+ {Key,Attrs} <- KeyAttrs
+ ]
+ );
+
+encode(KeyAttrs, Type) when Type == known_hosts;
+ Type == auth_keys ;
+ Type == openssh_key ->
+ FirstArgTag =
+ case Type of
+ known_hosts -> hostnames;
+ auth_keys -> options;
+ openssh_key -> '*no tag*'
+ end,
+ iolist_to_binary(
+ [
+ begin
+ <<?DEC_BIN(KeyType,__0),_/binary>> = Enc = ssh_message:ssh2_pubkey_encode(Key),
+ [case lists:join(",", proplists:get_value(FirstArgTag, Attributes, [])) of
+ [] -> "";
+ C -> [C," "]
+ end,
+ KeyType, " ",
+ base64:encode(Enc), " ",
+ case proplists:get_value(comment, Attributes, []) of
+ [] -> "";
+ C -> C
+ end,
+ "\n"
+ ]
+ end
+ || {Key,Attributes} <- KeyAttrs
+ ]
+ );
+
+encode(_KeyBin, _Type) ->
+ error(badarg).
+
+%%%----------------------------------------------------------------
+
+-spec extract_public_key(PrivKey) -> PubKey
+ when PrivKey :: public_key:private_key(),
+ PubKey :: public_key:public_key().
+
+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}) ->
+ {Y, #'Dss-Parms'{p=P, q=Q, g=G}};
+extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Pub0, privateKey = Priv}) when
+ OID == ?'id-Ed25519' orelse
+ OID == ?'id-Ed448' ->
+ case {pubkey_cert_records:namedCurves(OID), Pub0} of
+ {Alg, asn1_NOVALUE} ->
+ %% If we're missing the public key, we can create it with
+ %% the private key.
+ {Pub, Priv} = crypto:generate_key(eddsa, Alg, Priv),
+ {#'ECPoint'{point=Pub}, {namedCurve,OID}};
+ {_Alg, Pub} ->
+ {#'ECPoint'{point=Pub}, {namedCurve,OID}}
+ end;
+extract_public_key(#'ECPrivateKey'{parameters = {namedCurve,OID},
+ publicKey = Q}) when is_tuple(OID) ->
+ {#'ECPoint'{point=Q}, {namedCurve,OID}};
+extract_public_key(#{engine:=_, key_id:=_, algorithm:=Alg} = M) ->
+ case {Alg, crypto:privkey_to_pubkey(Alg, M)} of
+ {rsa, [E,N]} ->
+ #'RSAPublicKey'{modulus = N, publicExponent = E};
+ {dss, [P,Q,G,Y]} ->
+ {Y, #'Dss-Parms'{p=P, q=Q, g=G}}
+ end.
+
%%%================================================================
%%%
%%% Local functions
@@ -158,7 +430,7 @@ lookup_auth_keys(KeyType, Key, File, Opt
time ->
case file:read_file(File) of
{ok,Bin} ->
- Lines = binary:split(Bin, <<"\n">>, [global,trim_all]),
+ Lines = split_in_lines(Bin),
find_key(KeyType, Key, Lines);
_ ->
false
@@ -174,7 +446,7 @@ lookup_auth_keys(KeyType, Key, File, Opt
file:close(Fd),
Result;
{error,_Error} ->
- false
+ false
end;
Other ->
{error,{is_auth_key,{opt,Other}}}
@@ -215,7 +487,7 @@ normalize_hosts_list(Hosts, Port) when i
Hs = case Port of
22 -> H1s;
_ -> [lists:concat(["[",Hx,"]:",Port]) || Hx <- H1s]
- end,
+ end,
lists:foldl(
fun(Hy, Acc2) ->
case lists:member(Hy, Acc2) of
@@ -270,7 +542,7 @@ read_test_loop(Fd, Test) ->
%% Rare... For example NFS errors
{error,Error};
Line0 ->
- case binary:split(Line0, <<"\n">>, [global,trim_all]) of % remove trailing \n
+ case split_in_lines(Line0) of % remove trailing EOL
[Line] ->
case Test(Line) of
false ->
@@ -282,7 +554,7 @@ read_test_loop(Fd, Test) ->
read_test_loop(Fd, Test)
end
end.
-
+
%%%--------------------------------
lookup_host_keys(Hosts, KeyType, Key, File, Opts) ->
@@ -290,7 +562,7 @@ lookup_host_keys(Hosts, KeyType, Key, Fi
time ->
case file:read_file(File) of
{ok,Bin} ->
- Lines = binary:split(Bin, <<"\n">>, [global,trim_all]),
+ Lines = split_in_lines(Bin),
case find_host_key(Hosts, KeyType, Key, Lines) of
{true,RestLines} ->
case revoked_key(Hosts, KeyType, Key, RestLines) of
@@ -364,7 +636,7 @@ find_host_key(_, _, _, []) ->
revoked_key(Hosts, KeyType, EncKey, [<<"@revoked ",RestLine/binary>> | Lines]) ->
case binary:split(RestLine, <<" ">>, [global,trim_all]) of
[Patterns, KeyType, EncKey|_Comment] ->
- %% Very likeley to be a revoked key,
+ %% Very likely to be a revoked key,
%% but does any of the hosts match the pattern?
case host_match(Hosts, Patterns) of
true ->
@@ -411,7 +683,7 @@ line_match(_, _, _, _) ->
host_match(Hosts, Patterns) ->
PatternList = binary:split(Patterns, <<",">>, [global]),
host_matchL(Hosts, PatternList).
-
+
host_matchL([H|Hosts], Patterns) ->
case one_host_match(H, Patterns) of
true ->
@@ -458,18 +730,45 @@ pos_match(H, P) ->
{[Hh], [Ph,<<"*">>]} ->
%% host [host]:*
- Sz = size(Hh),
+ Sz = byte_size(Hh),
Ph == <<"[", Hh:Sz/binary, "]">>;
{[Hh], [Ph,<<"22">>]} ->
%% host [host]:22
- Sz = size(Hh),
+ Sz = byte_size(Hh),
Ph == <<"[", Hh:Sz/binary, "]">>;
_ ->
false
end.
+%%%---------------- UTILITY ---------------------------------------
+rfc4716_encode_header({Tag, Value}) ->
+ TagLen = length(Tag),
+ ValueLen = length(Value),
+ case TagLen + 1 + ValueLen of
+ N when N > ?ENCODED_LINE_LENGTH ->
+ NumOfChars = ?ENCODED_LINE_LENGTH - (TagLen + 1),
+ {First, Rest} = lists:split(NumOfChars, Value),
+ [Tag,": " , First, [$\\], "\n", rfc4716_encode_value(Rest) , "\n"];
+ _ ->
+ [Tag, ": ", Value, "\n"]
+ end.
+
+rfc4716_encode_value(Value) ->
+ case length(Value) of
+ N when N > ?ENCODED_LINE_LENGTH ->
+ {First, Rest} = lists:split(?ENCODED_LINE_LENGTH, Value),
+ [First, [$\\], "\n", rfc4716_encode_value(Rest)];
+ _ ->
+ Value
+ end.
+
+split_long_lines(<<Text:?ENCODED_LINE_LENGTH/binary, Rest/binary>>) when Rest =/= <<"">> ->
+ [Text, $\n | split_long_lines(Rest)];
+split_long_lines(Bin) ->
+ [Bin].
+
%%%---------------- COMMON FUNCTIONS ------------------------------
assure_file_mode(File, user_write) -> assure_file_mode(File, 8#200);
@@ -515,7 +814,7 @@ read_ssh_key_file(Role, PrivPub, Algorit
try
decode_ssh_file(PrivPub, Algorithm, Pem, Password)
of
- {ok, [Key|_Keys]} ->
+ {ok, [{Key,_Attrs}|_Keys]} ->
{ok,Key};
{error, Reason} ->
{error, Reason}
@@ -533,19 +832,23 @@ read_ssh_key_file(Role, PrivPub, Algorit
-spec decode_ssh_file(PrivPub, Algorithm, Pem, Password) -> Result when
PrivPub :: private | public,
- Algorithm :: ssh:pubkey_alg(),
+ Algorithm :: ssh:pubkey_alg() | any,
Pem :: binary(),
- Password :: string(),
+ Password :: string() | ignore,
Result :: {ok, Keys} | {error, any()},
- Keys :: [Key],
+ Keys :: [{Key,Attrs}],
+ Attrs :: [{any(),any()}],
Key :: public_key:private_key() | public_key:public_key() .
-
+
decode_ssh_file(PrivPub, Algorithm, Pem, Password) ->
try decode_pem_keys(Pem, Password)
of
+ {ok, Keys} when Algorithm == any ->
+ {ok, Keys};
+
{ok, Keys0} ->
- case [Key || Key <- Keys0,
- ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm)] of
+ case [{Key,Attrs} || {Key,Attrs} <- Keys0,
+ ssh_transport:valid_key_sha_alg(PrivPub, Key, Algorithm)] of
[] ->
{error,no_key_found};
Keys ->
@@ -560,26 +863,40 @@ decode_ssh_file(PrivPub, Algorithm, Pem,
{error, key_decode_failed}
end.
-decode_pem_keys(Pem, Password) ->
+
+decode_pem_keys(RawBin, Password) ->
+ PemLines = split_in_lines(
+ binary:replace(RawBin, [<<"\\\n">>,<<"\\\r\\\n">>], <<"">>, [global])
+ ),
+ decode_pem_keys(PemLines, Password, []).
+decode_pem_keys([], _, Acc) ->
+ {ok,lists:reverse(Acc)};
+
+
+decode_pem_keys(PemLines, Password, Acc) ->
%% Private Key
- try get_key_part(Pem) of
- {'openssh-key-v1', Bin, _KeyValues} ->
+ try get_key_part(PemLines) of
+ {'openssh-key-v1', Bin, Attrs, RestLines} ->
+ %% -----BEGIN OPENSSH PRIVATE KEY-----
%% Holds both public and private keys
- KeyPairs = new_openssh_decode(Bin, Password),
- Keys = [Key || {Pub,Priv} <- KeyPairs,
- Key <- [Pub,Priv]],
- {ok,Keys};
+ KeyPairs = openssh_key_v1_decode(Bin, Password),
+ Keys = [{Key,Attrs} || {Pub,Priv} <- KeyPairs,
+ Key <- [Pub,Priv]],
+ decode_pem_keys(RestLines, Password, Keys ++ Acc);
- {rfc4716, Bin, _KeyValues} ->
+ {rfc4716, Bin, Attrs, RestLines} ->
+ %% ---- BEGIN SSH2 PUBLIC KEY ----
%% rfc4716 only defines public keys
Key = ssh_message:ssh2_pubkey_decode(Bin),
- {ok,[Key]};
+ decode_pem_keys(RestLines, Password, [{Key,Attrs}|Acc]);
- {Type, Bin, KeyValues} ->
- case get_encrypt_hdrs(KeyValues) of
+ {Type, Bin, Attrs, RestLines} ->
+ %% -----BEGIN (RSA|DSA|EC) PRIVATE KEY-----
+ %% and possibly others
+ case get_encrypt_hdrs(Attrs) of
not_encrypted ->
Key = public_key:pem_entry_decode({Type,Bin,not_encrypted}),
- {ok, [Key]};
+ decode_pem_keys(RestLines, Password, [{Key,Attrs}|Acc]);
[Cipher,Salt] when is_binary(Cipher),
is_binary(Salt),
@@ -587,7 +904,7 @@ decode_pem_keys(Pem, Password) ->
CryptInfo =
{binary_to_list(Cipher), unhex(binary_to_list(Salt))},
Key = public_key:pem_entry_decode({Type,Bin,CryptInfo}, Password),
- {ok, [Key]};
+ decode_pem_keys(RestLines, Password, [{Key,Attrs}|Acc]);
_X ->
{error, no_pass_phrase}
@@ -696,25 +1013,21 @@ default_user_dir(Home) when is_list(Home
UserDir.
%%%################################################################
-get_key_part(RawBin) when is_binary(RawBin) ->
- case binary:split(
- binary:replace(RawBin, <<"\\\n">>, <<"">>, [global]),
- <<"\n">>, [global,trim_all])
- of
- [<<"---- BEGIN SSH2 PUBLIC KEY ----">> | Lines0] ->
- %% RFC 4716 format
- {KeyValues,Lines} = get_hdr_lines(Lines0, []),
- ExpectedEndLine = <<"---- END SSH2 PUBLIC KEY ----">>,
- {rfc4716, get_body(Lines,ExpectedEndLine), KeyValues};
-
- [<<"-----BEGIN ", Rest/binary>> | Lines0] ->
- %% PEM format
- ExpectedEndLine = <<"-----END ",Rest/binary>>,
- [MiddlePart, <<>>] = binary:split(Rest, <<" KEY-----">>),
- {KeyValues,Lines} = get_hdr_lines(Lines0, []),
- {asn1_type(MiddlePart), get_body(Lines,ExpectedEndLine), KeyValues}
- end.
-
+get_key_part([<<"---- BEGIN SSH2 PUBLIC KEY ----">> | Lines0]) ->
+ %% RFC 4716 format
+ {KeyValues,Lines} = get_hdr_lines(Lines0, []),
+ ExpectedEndLine = <<"---- END SSH2 PUBLIC KEY ----">>,
+ {Key,RestLines} = get_body(Lines,ExpectedEndLine),
+ {rfc4716, Key, KeyValues, RestLines};
+
+get_key_part([<<"-----BEGIN ", Rest/binary>> | Lines0]) ->
+ %% PEM format
+ ExpectedEndLine = <<"-----END ",Rest/binary>>,
+ [MiddlePart, <<>>] = binary:split(Rest, <<" KEY-----">>),
+ {KeyValues,Lines} = get_hdr_lines(Lines0, []),
+ {Key,RestLines} = get_body(Lines,ExpectedEndLine),
+ {asn1_type(MiddlePart), Key, KeyValues, RestLines}.
+
get_hdr_lines(Lines, Acc) ->
Line1 = hd(Lines),
@@ -727,8 +1040,9 @@ get_hdr_lines(Lines, Acc) ->
get_body(Lines, ExpectedEndLine) ->
- {KeyPart, [ExpectedEndLine]} = lists:split(length(Lines)-1, Lines),
- base64:mime_decode(iolist_to_binary(KeyPart)).
+ {KeyPart, [ExpectedEndLine|RestLines]} =
+ lists:splitwith(fun(L) -> L=/=ExpectedEndLine end, Lines),
+ {base64:mime_decode(iolist_to_binary(KeyPart)), RestLines}.
trim(<<" ",B/binary>>) -> trim(B);
trim(B) -> B.
@@ -738,6 +1052,7 @@ asn1_type(<<"RSA PUBLIC">>) -> 'RSAPubli
asn1_type(<<"DSA PRIVATE">>) -> 'DSAPrivateKey';
asn1_type(<<"EC PRIVATE">>) -> 'ECPrivateKey';
asn1_type(<<"OPENSSH PRIVATE">>) -> 'openssh-key-v1';
+asn1_type(<<"PRIVATE">>) -> 'PrivateKeyInfo';
asn1_type(_) -> undefined.
%%%================================================================
@@ -746,55 +1061,52 @@ asn1_type(_) -> undefined.
-define(NON_CRYPT_BLOCKSIZE, 8).
-new_openssh_decode(<<"openssh-key-v1",0,
- ?DEC_BIN(CipherName, _L1),
- ?DEC_BIN(KdfName, _L2),
- ?DEC_BIN(KdfOptions, _L3),
- ?UINT32(N), % number of keys
- Rest/binary
- >>, Pwd) ->
- new_openssh_decode(Rest, N, Pwd, CipherName, KdfName, KdfOptions, N, []).
+openssh_key_v1_decode(<<"openssh-key-v1",0,
+ ?DEC_BIN(CipherName, _L1),
+ ?DEC_BIN(KdfName, _L2),
+ ?DEC_BIN(KdfOptions, _L3),
+ ?UINT32(N), % number of keys
+ Rest/binary
+ >>, Pwd) ->
+ openssh_key_v1_decode(Rest, N, Pwd, CipherName, KdfName, KdfOptions, N, []).
-new_openssh_decode(<<?DEC_BIN(BinKey,_L1), Rest/binary>>, I, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAcc) when I>0 ->
+openssh_key_v1_decode(<<?DEC_BIN(BinKey,_L1), Rest/binary>>, I,
+ Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAcc) when I>0 ->
PublicKey = ssh_message:ssh2_pubkey_decode(BinKey),
- new_openssh_decode(Rest, I-1, Pwd, CipherName, KdfName, KdfOptions, N, [PublicKey|PubKeyAcc]);
+ openssh_key_v1_decode(Rest, I-1, Pwd, CipherName, KdfName, KdfOptions, N, [PublicKey|PubKeyAcc]);
-new_openssh_decode(<<?DEC_BIN(Encrypted,_L)>>,
- 0, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAccRev) ->
+openssh_key_v1_decode(<<?DEC_BIN(Encrypted,_L)>>,
+ 0, Pwd, CipherName, KdfName, KdfOptions, N, PubKeyAccRev) ->
PubKeys = lists:reverse(PubKeyAccRev),
try
- Plain = decrypt_new_openssh(Encrypted, KdfName, KdfOptions, CipherName, Pwd),
- new_openssh_decode_priv_keys(Plain, N, N, [], [])
+ Plain = decrypt_openssh_key_v1(Encrypted, KdfName, KdfOptions, CipherName, Pwd),
+ openssh_key_v1_decode_priv_keys(Plain, N, N, [], [])
of
{PrivKeys, _Comments} ->
- lists:map(fun({ {ed_pub,A,Pub}, {ed_pri,A,Pub,Pri0} }) ->
- Pri = binary:part(Pri0, {0,size(Pri0)-size(Pub)}),
- {{ed_pub,A,Pub}, {ed_pri,A,Pub,Pri}};
- (Pair) ->
- Pair
- end, lists:zip(PubKeys, PrivKeys))
+ lists:zip(PubKeys, PrivKeys)
+ %% lists:zip3(PubKeys, PrivKeys,_ Comments))
catch
error:{decryption, DecryptError} ->
error({decryption, DecryptError})
end.
-new_openssh_decode_priv_keys(Bin, I, N, KeyAcc, CmntAcc) when I>0 ->
+openssh_key_v1_decode_priv_keys(Bin, I, N, KeyAcc, CmntAcc) when I>0 ->
{PrivKey, <<?DEC_BIN(Comment,_Lc),Rest/binary>>} = ssh_message:ssh2_privkey_decode2(Bin),
- new_openssh_decode_priv_keys(Rest, I-1, N, [PrivKey|KeyAcc], [Comment|CmntAcc]);
-new_openssh_decode_priv_keys(_Padding, 0, _N, PrivKeyAccRev, CommentAccRev) ->
+ openssh_key_v1_decode_priv_keys(Rest, I-1, N, [PrivKey|KeyAcc], [Comment|CmntAcc]);
+openssh_key_v1_decode_priv_keys(_Padding, 0, _N, PrivKeyAccRev, CommentAccRev) ->
{lists:reverse(PrivKeyAccRev),
lists:reverse(CommentAccRev)}.
-decrypt_new_openssh(Encrypted, <<"none">>, <<>>, _CipherName, _Pwd) ->
+decrypt_openssh_key_v1(Encrypted, <<"none">>, <<>>, _CipherName, _Pwd) ->
check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE);
-decrypt_new_openssh(Encrypted, <<>>, <<>>, _CipherName, _Pwd) ->
+decrypt_openssh_key_v1(Encrypted, <<>>, <<>>, _CipherName, _Pwd) ->
check_valid_decryption(Encrypted, ?NON_CRYPT_BLOCKSIZE);
-decrypt_new_openssh(_Encrypted, <<"bcrypt">>, <<?DEC_BIN(_Salt,_L),?UINT32(_Rounds)>>, _CipherName, _Pwd) ->
+decrypt_openssh_key_v1(_Encrypted, <<"bcrypt">>, <<?DEC_BIN(_Salt,_L),?UINT32(_Rounds)>>, _CipherName, _Pwd) ->
error({decryption, {not_supported,bcrypt}});
-decrypt_new_openssh(_Encrypted, KdfName, _KdfOpts, _CipherName, _Pwd) ->
+decrypt_openssh_key_v1(_Encrypted, KdfName, _KdfOpts, _CipherName, _Pwd) ->
error({decryption, {not_supported,KdfName}}).
@@ -821,5 +1133,78 @@ check_padding(Bin, BlockSize) ->
true
end.
+%%%----------------------------------------------------------------
+%% KeyPairs :: [ {Pub,Priv,Comment} ]
+openssh_key_v1_encode(KeyPairs) ->
+ CipherName = <<"none">>,
+ BlockSize = ?NON_CRYPT_BLOCKSIZE, % Cipher dependent
+ KdfName = <<"none">>,
+ KdfOptions = <<>>,
+ NumKeys = length(KeyPairs),
+ CheckInt = crypto:strong_rand_bytes(4),
+ UnEncrypted0 = <<CheckInt/binary,
+ CheckInt/binary,
+ (openssh_key_v1_encode_priv_keys_cmnts(KeyPairs))/binary>>,
+ UnEncrypted = <<UnEncrypted0/binary,
+ (pad(byte_size(UnEncrypted0), BlockSize))/binary>>,
+ Encrypted = encrypt_openssh_key_v1(UnEncrypted, KdfName, KdfOptions, CipherName, ignore),
+ <<"openssh-key-v1",0,
+ ?STRING(CipherName),
+ ?STRING(KdfName),
+ ?STRING(KdfOptions),
+ ?UINT32(NumKeys),
+ (openssh_key_v1_encode_pub_keys(KeyPairs))/binary,
+ ?STRING(Encrypted)>>.
+
+%%%----
+openssh_key_v1_encode_pub_keys(KeyPairs) ->
+ openssh_key_v1_encode_pub_keys(KeyPairs, []).
+
+openssh_key_v1_encode_pub_keys([{Priv = #'ECPrivateKey'{}, _Cmnt} | Ks], Acc) ->
+ Pub = extract_public_key(Priv),
+ Bk = ssh_message:ssh2_pubkey_encode(Pub),
+ openssh_key_v1_encode_pub_keys(Ks, [<<?STRING(Bk)>>|Acc]);
+openssh_key_v1_encode_pub_keys([{K,_,_C}|Ks], Acc) ->
+ Bk = ssh_message:ssh2_pubkey_encode(K),
+ openssh_key_v1_encode_pub_keys(Ks, [<<?STRING(Bk)>>|Acc]);
+openssh_key_v1_encode_pub_keys([], Acc) ->
+ list_to_binary(lists:reverse(Acc)).
+
+
+%%%----
+openssh_key_v1_encode_priv_keys_cmnts(KeyPairs) ->
+ openssh_key_v1_encode_priv_keys_cmnts(KeyPairs, []).
+
+openssh_key_v1_encode_priv_keys_cmnts([{K = #'ECPrivateKey'{}, C} | Ks], Acc) ->
+ Bk = ssh_message:ssh2_privkey_encode(K),
+ openssh_key_v1_encode_priv_keys_cmnts(Ks, [<<Bk/binary,?STRING(C)>>|Acc]);
+openssh_key_v1_encode_priv_keys_cmnts([{_,K,C}|Ks], Acc) ->
+ Bk = ssh_message:ssh2_privkey_encode(K),
+ openssh_key_v1_encode_priv_keys_cmnts(Ks, [<<Bk/binary,?STRING(C)>>|Acc]);
+openssh_key_v1_encode_priv_keys_cmnts([], Acc) ->
+ list_to_binary(lists:reverse(Acc)).
+
+encrypt_openssh_key_v1(UnEncrypted, <<"none">>, <<>>, _CipherName, _Pwd) ->
+ UnEncrypted;
+encrypt_openssh_key_v1(_UnEncrypted, KdfName, _KdfOptions, _CipherName, _Pwd) ->
+ error({decryption, {not_supported,KdfName}}).
+
+pad(N, BlockSize) when N>BlockSize -> pad(N rem BlockSize, BlockSize);
+pad(N, BlockSize) -> list_to_binary(lists:seq(1,BlockSize-N)).
+
%%%================================================================
%%%
+split_in_nonempty_lines(Bin) ->
+ skip_blank_lines_and_comments( split_in_lines(Bin) ).
+
+split_in_lines(Bin) ->
+ binary:split(Bin, [<<"\n">>,<<"\r\n">>], [global,trim_all]).
+
+skip_blank_lines_and_comments(Lines) ->
+ lists:filter(fun(<<"#",_/binary>>) ->
+ %% skip comments
+ false;
+ (L) ->
+ %% skip blank lines
+ re:run(L, "^(\t|\s)+$") == nomatch
+ end, Lines).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_io.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_io.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2021. 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,49 +27,31 @@
-export([yes_no/2, read_password/2, read_line/2, format/2]).
-include("ssh.hrl").
-read_line(Prompt, Opts) ->
+read_line(Prompt, _Opts) ->
format("~s", [listify(Prompt)]),
- ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question},
- receive
- Answer when is_list(Answer) or is_binary(Answer) ->
- unicode:characters_to_list(Answer)
- end.
+ unicode:characters_to_list(io:get_line("")).
yes_no(Prompt, Opts) ->
format("~s [y/n]?", [Prompt]),
- ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), question},
- receive
- %% 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) or is_binary(Answer) ->
- case trim(Answer) of
- "y" -> yes;
- "n" -> no;
- "Y" -> yes;
- "N" -> no;
- _ ->
- format("please answer y or n\n",[]),
- yes_no(Prompt, Opts)
- end
+ case trim(io:get_line("")) of
+ "y" -> yes;
+ "n" -> no;
+ "Y" -> yes;
+ "N" -> no;
+ _ ->
+ format("please answer y or n\n",[]),
+ yes_no(Prompt, Opts)
end.
read_password(Prompt, Opts) ->
format("~s", [listify(Prompt)]),
- ?GET_INTERNAL_OPT(user_pid, Opts) ! {self(), user_password},
- receive
- Answer when is_list(Answer) or is_binary(Answer) ->
- case trim(Answer) of
- "" ->
- read_password(Prompt, Opts);
- Pwd ->
- Pwd
- end
+ case trim(io:get_password()) of
+ "" ->
+ read_password(Prompt, Opts);
+ Pwd ->
+ Pwd
end.
-
format(Fmt, Args) ->
io:format(Fmt, Args).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_server_channel.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_server_channel.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_server_channel.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2021. 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,7 +25,7 @@
-module(ssh_server_channel).
-%% API to server side channel that can be pluged into the erlang ssh daemeon
+%% API to server side channel that can be plugged into the erlang ssh daemeon
-callback init(Args :: term()) ->
{ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} |
{stop, Reason :: term()} | ignore.
@@ -44,7 +44,7 @@
%%% Internal API
-export([start_link/5,
- get_print_info/1
+ get_print_info/1, get_print_info/2
]).
start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) ->
@@ -53,3 +53,6 @@ start_link(ConnectionManager, ChannelId,
get_print_info(Pid) ->
ssh_client_channel:get_print_info(Pid).
+
+get_print_info(Pid, Arg) ->
+ ssh_client_channel:get_print_info(Pid, Arg).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_server_key_api.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_server_key_api.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_server_key_api.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2021. 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 @@
%%% The option key_cb_private is to pass options needed by other
%%% callback modules than the default ssh_file.erl
%%%
-%%% If ssh:deamon(n, [ {key_cb_private, {hi,{there}}} ]
+%%% If ssh:daemon(n, [ {key_cb_private, {hi,{there}}} ]
%%% is called, the term() will be {hi,{there}}
-type daemon_key_cb_options(T) :: [{key_cb_private,[T]} | ssh:daemon_option()].
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_shell.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_shell.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_shell.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2009-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2009-2022. 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,7 @@
-include("ssh_connect.hrl").
%%% As this is an user interactive client it behaves like a daemon
-%%% channel inspite of it being a client.
+%%% channel in spite of it being a client.
-behaviour(ssh_server_channel).
%% ssh_server_channel callbacks
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_tcpip_forward_acceptor_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2021. 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,20 +41,21 @@ start_link() ->
start_child(Sup, LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid) ->
Args = [LSock, ListenAddr, ConnectToAddr, ChanType, ChanCB, ConnPid],
- supervisor:start_child(Sup, Args).
+ supervisor:start_child(Sup,
+ #{id => {ListenAddr,ConnectToAddr},
+ start => {ssh_tcpip_forward_acceptor, start_link, Args}
+ }).
+
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
init([]) ->
- SupFlags = #{strategy => simple_one_for_one,
+ SupFlags = #{strategy => one_for_one,
intensity => 10,
period => 3600
},
- ChildSpecs = [#{id => undefined, % As simple_one_for_one is used.
- start => {ssh_tcpip_forward_acceptor, start_link, []}
- }
- ],
+ ChildSpecs = [],
{ok, {SupFlags,ChildSpecs}}.
%%%=========================================================================
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_transport.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_transport.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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,7 +20,7 @@
%%
%%----------------------------------------------------------------------
-%% Purpose: Record and constant defenitions for the SSH-tansport layer
+%% Purpose: Record and constant definitions for the SSH-tansport layer
%% protocol see RFC 4253
%%----------------------------------------------------------------------
@@ -266,5 +266,7 @@
-define(dh_group18,
{2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF}).
-
+%%% OpenSSH KEX strict
+-define(kex_strict_c, "kex-strict-c-v00@openssh.com").
+-define(kex_strict_s, "kex-strict-s-v00@openssh.com").
-endif. % -ifdef(ssh_transport).
Index: otp-OTP-23.3.4.19/lib/ssh/src/ssh_xfer.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/src/ssh_xfer.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/src/ssh_xfer.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2022. 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.
@@ -226,7 +226,7 @@
-record(ssh_xfer_attr,
{
- type, %% regular, dirctory, symlink, ...
+ type, %% regular, directory, symlink, ...
size,
owner,
group,
Index: otp-OTP-23.3.4.19/lib/ssh/test/property_test/ssh_eqc_client_server.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/property_test/ssh_eqc_client_server.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/property_test/ssh_eqc_client_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2022. 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.
@@ -299,7 +299,7 @@ do(Pid, Fun, Timeout) when is_function(F
%%%----------------
%%% Start a new connection
-%%% Precondition: deamon exists
+%%% Precondition: daemon exists
ssh_open_connection_pre(S) -> S#state.servers /= [].
Index: otp-OTP-23.3.4.19/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/property_test/ssh_eqc_encode_decode.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2004-2022. 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.
@@ -250,7 +250,7 @@ msg_code(Num) -> Name
-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. :(
+%%% Encoding and decoding is asymmetric 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}
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed25519_key
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed25519_key
@@ -0,0 +1,4 @@
+-----BEGIN EC PRIVATE KEY-----
+MFECAQEEIFZTDpCko9CxtWW5UKzRqvMXPTJZfIdcA5u/IOV3EmU4oAUGAytlcKEj
+AyEAIAJ1D3OUNmUJRCDQ8uPkVCeTV6oNOtpubn2I7x2VxM0=
+-----END EC PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed25519_key.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICACdQ9zlDZlCUQg0PLj5FQnk1eqDTrabm59iO8dlcTN uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed448_key
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed448_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGDAgEBBDlf6cdZBOKnN7msVY8oloq9Se2ee5RBXGf3Ofs96djk8uqEAyQdWUe+
+fWMNDPVnyViy7qceZkECkf6gBQYDK2VxoTwDOgCLHixRiwKzXM1/OOTdsC1L06OS
+3BntXrBWpqKU2Xhr1acxidyCB/0hhtf89NtZVm0zkXBf/zL6jQA=
+-----END EC PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed448_key.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADmLHixRiwKzXM1/OOTdsC1L06OS3BntXrBWpqKU2Xhr1acxidyCB/0hhtf89NtZVm0zkXBf/zL6jQA= uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed25519_key
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed25519_key
@@ -0,0 +1,4 @@
+-----BEGIN EC PRIVATE KEY-----
+MFECAQEEIFZTDpCko9CxtWW5UKzRqvMXPTJZfIdcA5u/IOV3EmU4oAUGAytlcKEj
+AyEAIAJ1D3OUNmUJRCDQ8uPkVCeTV6oNOtpubn2I7x2VxM0=
+-----END EC PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed25519_key.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICACdQ9zlDZlCUQg0PLj5FQnk1eqDTrabm59iO8dlcTN uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed448_key
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed448_key
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGDAgEBBDlf6cdZBOKnN7msVY8oloq9Se2ee5RBXGf3Ofs96djk8uqEAyQdWUe+
+fWMNDPVnyViy7qceZkECkf6gBQYDK2VxoTwDOgCLHixRiwKzXM1/OOTdsC1L06OS
+3BntXrBWpqKU2Xhr1acxidyCB/0hhtf89NtZVm0zkXBf/zL6jQA=
+-----END EC PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed448_key.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/old_format_passphrase/ssh_host_ed448_key.pub
@@ -0,0 +1 @@
+ssh-ed448 AAAACXNzaC1lZDQ0OAAAADmLHixRiwKzXM1/OOTdsC1L06OS3BntXrBWpqKU2Xhr1acxidyCB/0hhtf89NtZVm0zkXBf/zL6jQA= uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/id_rsa
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/id_rsa
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2d3XMIA8GTEQc
+UFCOm31M5jt6lGjN61ZYGnXBVBjEcyJynB7Y3C437cDpjmvbSWF1oSVVDTwMERwn
+XzixLG//7w8K7i6aJLKpHKtS91qnrQidmrUWDnQ4kx8AZxaN46nhSsf+cZ0nKp03
+ZjjR5WxeDimiDLsSUbdDmFE6ZsL2+k5OStvcqu/skUVfPe+FGTGJgIw3DyErxM7J
+72jUkLJXMiZkYbB1QD05k3g2LOiPqJ73QoJVGgj7YagTSA3Lgy3s/6U7IMHMV4ls
+aXShv1Lk/eCfIJVSaVXQRjV9KKM3wgg6PmWqwGkAO36w3eJiW1kmYKfnAM/+I5Gf
+o/TiNZTXAgMBAAECggEAeWdwfDmUZZdW9hPGFayFKSZCyuN1/BSqZYJteQ2QUR1d
+/S29JIMTSWkqovt87fGcI9ztbvKYUlsMBXJI0TGE75/KvXYOkcb7DKQjpdcofUoW
+4m4uMJe7Ym0ZAnaUviGNRXYxLS3A529mHZcpFRb2DHqV3tljmuO98P6mhRocfKCc
+3p/T4+LIGlRlov5lOP/oKkeILF1m04J/SjptTtNo81xne9/dCGTiOTXjS8QMslCc
+8Xyy0Go9Zr0d+YzsI2NYF2aFBce0fDwK0Xpptr0FEL8UxjHjeK0T2GSDqncmtKoA
+3+BnpEJcuiDqZBi20lX7LygtNe9uVPZjdz1iOeKAaQKBgQDZjj6yOwbDeXkedD0I
+25RC8lmCWhV381PDz9RdeRXVC50jq3OYwmdcDEIK23YNWU8GoUnvi7B1aljSfAUm
+yUSnixXpU+/ZOkGYA48MHpC1DxJeEVZDu+MFWHmTCXctQNUj40gAKhozJm5Lo33s
+Wnhr+Yq2CP65w6R+vXn4gXsv9QKBgQDWtd0BMEVCUug+6/dWCVTUsuBouz/erOgE
+f0PPA8/IQV1ZhBQK4wewv14R8Nkywb8Z5lsVyH8JHRHZC35mVzFxGJyGAzDfJ3Mg
+GoK8t7jjiUHPF0tYWpLmAKdKHmJqB7ZBGzT7pAP07XockRHoeYHBHoO3Ck/c3h0f
+EtclGMOuGwKBgQCXf8z9RMmS+lZz9LJEJtT6QdY/RghJPbOJWoMijJ29fJbzLgQT
+zt03ZnnfIbD13sl/bnYUUIyTV3l/KkpUFjivC9Y4Y/FUrpLbDy9gWzCeRV6fDyep
+h3+yS0huMltBsjI7CZ0sMCWKlSqdlb6tBttxJZeI6H6qUimM8NmtSk3EuQKBgAw+
+OIjt0LU0dwvHdsYQKCcswAEY1E6FO4GuJBa01+9KUuFc16u7QGACuYF6Y1gylgwL
+B5yZXy0M3EytDBsX07joN1yo5+uBm130RQovy7olxHvjjydNmtzEosVmMCRtpiXW
+QFItCxC3TeQ9HXFNJGn3rHkOfHlSrQRtlZkG7XmLAoGACVKIJSoI2Kvd913TATb4
+whzNqbdiyFCadLf9cST7sGZ+ZvtHF3CT3iU/9JpBhndu4IqgFPPk694GXhoR2LK8
+SoGR4mhRvPAUgvKjRE2dypQtytgA8gm1soofSjSdoJlSBQkaxfT8N0mwQTbsoyWN
+zUDUWl3epDaqpUsV8NOuy4E=
+-----END PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/id_rsa.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2d3XMIA8GTEQcUFCOm31M5jt6lGjN61ZYGnXBVBjEcyJynB7Y3C437cDpjmvbSWF1oSVVDTwMERwnXzixLG//7w8K7i6aJLKpHKtS91qnrQidmrUWDnQ4kx8AZxaN46nhSsf+cZ0nKp03ZjjR5WxeDimiDLsSUbdDmFE6ZsL2+k5OStvcqu/skUVfPe+FGTGJgIw3DyErxM7J72jUkLJXMiZkYbB1QD05k3g2LOiPqJ73QoJVGgj7YagTSA3Lgy3s/6U7IMHMV4lsaXShv1Lk/eCfIJVSaVXQRjV9KKM3wgg6PmWqwGkAO36w3eJiW1kmYKfnAM/+I5Gfo/TiNZTX uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/ssh_host_rsa_key
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/ssh_host_rsa_key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuvOQFQAxi8Cyj
+iFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp
+1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRx
+rUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3
+M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0R
+P4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6l
+ftX8O3sNAgMBAAECggEADXIlja3fBiH7HV5ZB78BGPNzdcETCaF0knTv4c8Uj7O2
+f2Uw5YQNaIzifC57bCo33srdDUJB5+6Ma/MwMLfYgOcQihkAZPiNj4k+dBOB7GLJ
+XgPc973N+NujeyvbEpzomNbqOb5Z24iETGcL/KX5BdvvJya1p/Du/UJq0LRGD1tS
+0TycwcphU9rOffTCUd0+XpPJO0RehkIoyDY5PGu9rzTHkluhSotldfjVWpWaqqhq
+QQ7c04aWGsjMg4HzrqnLx35/rCKU3+tRwZ4wnAHxpOtg/EuQJiX08Z4wMwsBG+GM
+ybnd/pRAUOkvatjRemdqqtmpL0qtsmhNANaUyPc6IQKBgQDgUvd9trgVgbN/tP+b
+bFFILmqumvSA2fWZknmhRYaIcHCMAcLsRVZqlyOyRpztavOcgEmJXajPPFA6AjKa
+5g70tf3kbpveeuRaFVepSLIKSl0xIT9hV8CIxzdnRA3P4j9xsQ9Qpnuiwo9mbgFG
+lQ28nCPhW+3mNfBNmU+ZWak0dQKBgQDHaXGroc74attrQDODvChuedny5lm91n5C
+nGAaEfVHH3zrYoz65VisnvERSU1Nh8G12moldCcaWnOMY97OJmMnG/sCBZskDzRp
+e1Mf+gT0TQoyYZHMTZtA1HyRRkdTlLZ7S77HUNTK8qrIpJEHLFSnzCPlBkY84fgw
++8IdVkX5OQKBgCDnapARFi1paffoh7m3iLCqxlE4P3cLAYB2QMsMFLC8tXWD6KCZ
+hxR5eO30d55HmtYw5xh0GYfUU/w+SEf6SOVSMJyqMMjQg+BG0yXsmNjzkXncY5yW
+r5IgjpriG5iLmjzF+PYehXIZUcl3h05gHLS2vniW8G1dKhNn0oou4aflAoGAFTLh
+caR+8yuw7cLidxOunKf5gnf4fFTsETq8gKj+ETSIvCE66YUuGxO+ft7zB9XxwtpY
+RGkHqyaIeBk522J7UfIIiht8daXkJX6FxLV4h1wVRGvY6wYpBghQwcTd2kXJ7GuN
++XRfWr/XZgMQo9mTmk76VeOH3fsLvnFVHndIcwkCgYBLntA0osVpZm6egw26+80C
+PtnSrUmsW4sTB+eQbbyDn6i/fAgGKc+2WuvcdorqyfLSEcs+hE/59roFVFpCEPN5
+4oO7o+o0SQ2ehxY+Lv2XF+TnfUQlAc6BCBfK3tG6rROUFiznAaua1hcsoAa9x/LH
+0SgWzYqbWI0qq7pv91tBdw==
+-----END PRIVATE KEY-----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/ssh_host_rsa_key.pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/pkcs8/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuvOQFQAxi8CyjiFHv2+Y1vE1sti3tChy6RuIJY6rb0dC+ptLOP85ITVCDoL9pNuCG92m/FtoW3dqp1augXQ+FVWgSwljvpsjtGyhsh73Mj9nd2RBGZbcjKMe+lUHDLzh8pnTkDwF3XSRxrUZ+tBeLsuQVnkVGPFqsXMyiMZV6xZxktqBOE8bRACNLDawb6iqXhigh5qBJ1/e3M44X7Ti9ymCoJG6CyxzDXuUjVHCrScze2FQGBJYNFuPOaRaj2Llalz1kpLXPgA0RP4jaEZbs8QDpcBckoLqchZ1UiJ7QY3TYL+aLjk3JM+fnYj9MkoUC0ihnx9uscR6lftX8O3sN uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/auth_keys
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/auth_keys
@@ -0,0 +1,7 @@
+command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmtisaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRuLDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368+dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNWjeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4fuKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV225JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRMIB+X+OTUUI8= dhopson@VMUbuntu-DSH
+
+ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH
+
+command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmtisaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRuLDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368+dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNWjeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4fuKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV225JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRMIB+X+OTUUI8= dhopson@VMUbuntu-DSH comment with whitespaces
+
+ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH comment with whitespaces
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/known_hosts
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/known_hosts
@@ -0,0 +1,8 @@
+hostname.domain.com,192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM=
+
+|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM= foo@bar.com
+
+hostname.domain.com,192.168.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM= Comment with whitespaces
+
+|1|BWO5qDxk/cFH0wa05JLdHn+j6xQ=|rXQvIxh5cDD3C43k5DPDamawVNA= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA1XY18+zA8VNK2YkzygOkMqUxHSTfxT1Xxx8CgDZgcQH8HUhPssW5ttvG8nKetlPQZAVk1C4WkWS1y5b3ekBhZTIxocp9Joc6V1+f2EOfO2mSLRwB16RGrdw6q7msrBXTC/dl+hF45kMMzVNzqxnSMVOa0sEPK2zK6Sg3Vi9fCSM= foo@bar.com Comment with whitespaces
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_dsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_dsa_pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_dsa_with_comment_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_dsa_with_comment_pub
@@ -0,0 +1,3 @@
+#This should be ignored!!
+
+ssh-dss AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbETW6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdHYI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5cvwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGfJ0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAAvioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACBAN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HSn24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV dhopson@VMUbuntu-DSH
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_ecdsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_ecdsa_pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIJrVlKYIT+MlxxRx5BFXisHHkcGMAAKv2dguUeOsutsYyzs9JAczvl6c+Sypra5+qOi2LHPXw6GGluuXcOssOM= uabhnil@elxadlj3q32
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_rsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/openssh_rsa_pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmtisaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRuLDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368+dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNWjeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4fuKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV225JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRMIB+X+OTUUI8= dhopson@VMUbuntu-DSH
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh1_auth_keys
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh1_auth_keys
@@ -0,0 +1,9 @@
+1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663
+
+1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH
+
+command="dump /home",no-pty,no-port-forwarding 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH
+
+1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH comment with whitespaces
+
+command="dump /home",no-pty,no-port-forwarding 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH comment with whitespaces
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh1_known_hosts
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh1_known_hosts
@@ -0,0 +1,3 @@
+hostname.domain.com,192.168.0.1 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH
+hostname2.domain.com,192.168.0.2 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663
+hostname3.domain.com,192.168.0.3 1024 35 794430685278501116412873221867658581245241426828503388129294124540165981586596106773643485704743298698207838825035605868404742682423919455523383721081589378970796492944950066480951790660582889972423189943567111507801410254720228911513553205592856585541922662924268445466959576882300405064708497308004255650466014242855505233634626075778108365396568863197935915425650388910408127232583533503834009244199384570662092164277923946411149853110048365318587554141774139652307149492021035538341281427025252592933784473453522113124752189378715431529801894015739903371171585194505182320772654217490509848165365152457990491089951560694728469571221819385402117009544812199223715540348068497710535492913376699508575875577554607325905000745578091554027803374110357015655416894607641289462159580964951182385869168785183135763253784745647466464331174922663455073627501620274348748413309761116542324505123795743603781806636788810617169341018091186028310551725315297135354426735951943325476221811539822892501042385411792050504283745898099390893596941969752683246939665141002098430129617772928840718016009187577151479855846883928332010147501182201528575840364152774917950524127063432334646746291719251739989499132767590205934821590545762802261107691663 dhopson@VMUbuntu-DSH comment with whitespaces
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_dsa_comment_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_dsa_comment_pub
@@ -0,0 +1,13 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: This is my public key for use on \
+servers which I don't like.
+AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
+W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
+YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
+vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
+J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
+vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
+AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
+n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
+sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_dsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_dsa_pub
@@ -0,0 +1,12 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: DSA Public Key for use with MyIsp
+AAAAB3NzaC1kc3MAAACBAPY8ZOHY2yFSJA6XYC9HRwNHxaehvx5wOJ0rzZdzoSOXxbET
+W6ToHv8D1UJ/z+zHo9Fiko5XybZnDIaBDHtblQ+Yp7StxyltHnXF1YLfKD1G4T6JYrdH
+YI14Om1eg9e4NnCRleaqoZPF3UGfZia6bXrGTQf3gJq2e7Yisk/gF+1VAAAAFQDb8D5c
+vwHWTZDPfX0D2s9Rd7NBvQAAAIEAlN92+Bb7D4KLYk3IwRbXblwXdkPggA4pfdtW9vGf
+J0/RHd+NjB4eo1D+0dix6tXwYGN7PKS5R/FXPNwxHPapcj9uL1Jn2AWQ2dsknf+i/FAA
+vioUPkmdMc0zuWoSOEsSNhVDtX3WdvVcGcBq9cetzrtOKWOocJmJ80qadxTRHtUAAACB
+AN7CY+KKv1gHpRzFwdQm7HK9bb1LAo2KwaoXnadFgeptNBQeSXG1vO+JsvphVMBJc9HS
+n24VYtYtsMu74qXviYjziVucWKjjKEb11juqnF0GDlB3VVmxHLmxnAz643WK42Z7dLM5
+sY29ouezv4Xz2PuMch5VGPP+CDqzCM4loWgV
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_ecdsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_ecdsa_pub
@@ -0,0 +1,6 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "256-bit ECDSA, converted by uabhnil@elxadlj3q32 from OpenSSH"
+AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIJrVlKYIT+MlxxRx5
+BFXisHHkcGMAAKv2dguUeOsutsYyzs9JAczvl6c+Sypra5+qOi2LHPXw6GGluuXcOssOM=
+
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_rsa_comment_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_rsa_comment_pub
@@ -0,0 +1,7 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: "1024-bit RSA, converted from OpenSSH by me@example.com"
+x-command: /home/me/bin/lock-in-guest.sh
+AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb
+YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ
+5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_rsa_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_rsa_pub
@@ -0,0 +1,13 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1yc2EAAAABIwAAAgEAwrr66r8n6B8Y0zMF3dOpXEapIQD9DiYQ6D6/zwor9o
+39jSkHNiMMER/GETBbzP83LOcekm02aRjo55ArO7gPPVvCXbrirJu9pkm4AC4BBre5xSLS
+7soyzwbigFruM8G63jSXqpHqJ/ooi168sKMC2b0Ncsi+JlTfNYlDXJVLKEeZgZOInQyMmt
+isaDTUQWTIv1snAizf4iIYENuAkGYGNCL77u5Y5VOu5eQipvFajTnps9QvUx/zdSFYn9e2
+sulWM3Bxc/S4IJ67JWHVRpfJxGi3hinRBH8WQdXuUwdJJTiJHKPyYrrM7Q6Xq4TOMFtcRu
+LDC6u3BXM1L0gBvHPNOnD5l2Lp5EjUkQ9CBf2j4A4gfH+iWQZyk08esAG/iwArAVxkl368
++dkbMWOXL8BN4x5zYgdzoeypQZZ2RKH780MCTSo4WQ19DP8pw+9q3bSFC9H3xYAxrKAJNW
+jeTUJOTrTe+mWXXU770gYyQTxa2ycnYrlZucn1S3vsvn6eq7NZZ8NRbyv1n15Ocg+nHK4f
+uKOrwPhU3NbKQwtjb0Wsxx1gAmQqIOLTpAdsrAauPxC7TPYA5qQVCphvimKuhQM/1gMV22
+5JrnjspVlthCzuFYUjXOKC3wxz6FFEtwnXu3uC5bVVkmkNadJmD21gD23yk4BraGXVYpRM
+IB+X+OTUUI8=
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_subject_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh2_subject_pub
@@ -0,0 +1,8 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Subject: me
+Comment: 1024-bit rsa, created by me@example.com Mon Jan 15 \
+08:31:24 2001
+AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4
+596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4
+soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh_rsa_long_comment_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh_rsa_long_comment_pub
@@ -0,0 +1,9 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: This is an example of a very very very very looooooooooooo\
+ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\
+comment
+x-command: /home/me/bin/lock-in-guest.sh
+AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb
+YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ
+5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh_rsa_long_header_pub
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE_data/public_key/ssh_rsa_long_header_pub
@@ -0,0 +1,9 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+Comment: This is an example of a very very very very looooooooooooo\
+ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong\
+comment
+x-command: /home/me/bin/lock-in-guest.sh
+AAAAB3NzaC1yc2EAAAABIwAAAIEA1on8gxCGJJWSRT4uOrR13mUaUk0hRf4RzxSZ1zRb
+YYFw8pfGesIFoEuVth4HKyF8k1y4mRUnYHP1XNMNMJl1JcEArC2asV8sHf6zSPVffozZ
+5TT4SfsUu/iKy9lUcCfXzwre4WWZSXXcPff+EHtWshahu3WzBdnGxm5Xoi89zcE=
+---- END SSH2 PUBLIC KEY ----
Index: otp-OTP-23.3.4.19/lib/ssh/test/Makefile
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/Makefile
+++ otp-OTP-23.3.4.19/lib/ssh/test/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2004-2018. All Rights Reserved.
+# Copyright Ericsson AB 2004-2023. 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,11 +26,12 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
MODULES= \
+ ssh_cth \
ssh_algorithms_SUITE \
- ssh_options_SUITE \
ssh_basic_SUITE \
ssh_bench_SUITE \
ssh_chan_behaviours_SUITE \
+ ssh_collect_labmachine_info_SUITE \
ssh_compat_SUITE \
ssh_connection_SUITE \
ssh_agent_mock_server \
@@ -97,6 +98,7 @@ RELSYSDIR = $(RELEASE_PATH)/ssh_test
INCLUDES = -I$(ERL_TOP)/lib/ssh/src
ERL_COMPILE_FLAGS += $(INCLUDES) -pa ../ebin
+ERL_COMPILE_FLAGS := $(filter-out +deterministic,$(ERL_COMPILE_FLAGS))
EBIN = .
@@ -104,7 +106,7 @@ EBIN = .
# Targets
# ----------------------------------------------------
-tests debug opt: emakebuild $(TARGET_FILES)
+tests $(TYPES): emakebuild $(TARGET_FILES)
.PHONY: emakebuild
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_agent_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_agent_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_agent_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2019. All Rights Reserved.
+%% Copyright Ericsson AB 2019-2021. 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.
@@ -173,6 +173,7 @@ chk_unix_domain_socket(Config0) ->
file:delete(SocketPath),
{skip, "Unix Domain Sockets are not supported"};
{ok, Socket} ->
+ ct:log("Socket = ~p", [Socket]),
gen_tcp:close(Socket),
file:delete(SocketPath),
Config
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_agent_mock_server.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_agent_mock_server.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_agent_mock_server.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2022. 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.
@@ -149,7 +149,7 @@ check_mktemp(Config) ->
end.
extract_pubkey(PrivKey) ->
- PubKey = ssh_transport:extract_public_key(PrivKey),
+ PubKey = ssh_file:extract_public_key(PrivKey),
ssh_message:ssh2_pubkey_encode(PubKey).
sig_format('ssh-rsa') -> <<"ssh-rsa">>;
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_algorithms_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_algorithms_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_algorithms_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2021. 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.
@@ -86,6 +86,24 @@ groups() ->
|| {Tag,Algs} <- ErlAlgos ++ DoubleAlgos,
Alg <- Algs],
+ ct:log(
+ "ErlAlgos = ~p~n"
+ "SshcAlgos = ~p~n"
+ "SshdAlgos = ~p~n"
+ "DoubleAlgos = ~p~n"
+ "TypeSSH = ~p~n"
+ "TagGroupSet = ~p~n"
+ "AlgoTcSet = ~p~n"
+ ,[
+ ErlAlgos,
+ SshcAlgos,
+ SshdAlgos,
+ DoubleAlgos,
+ TypeSSH,
+ TagGroupSet,
+ AlgoTcSet
+ ]),
+
TagGroupSet ++ AlgoTcSet.
tags() -> [kex,cipher,mac,compression,public_key].
@@ -146,50 +164,76 @@ init_per_group(Group, Config) ->
Tag = proplists:get_value(name,
hd(proplists:get_value(tc_group_path, Config))),
Alg = Group,
- init_per_group(Tag, Alg, Config)
+ Algs = split(Tag, Alg),
+ SupportedAlgs = proplists:get_value(Tag, ssh_transport:supported_algorithms()),
+ PA =
+ case Algs of
+ [_] ->
+ [Alg];
+ [A1,A2] when Tag == public_key ->
+ [A1,A2];
+ [A1,A2] ->
+ [{client2server,[A1]},
+ {server2client,[A2]}]
+ end,
+ case lists:foldl(fun({K,As}, Acc) ->
+ ct:log("~p:~p K=~p, As=~p, SupportedAlgs=~p", [?MODULE,?LINE,K,As,SupportedAlgs]),
+ SAs = proplists:get_value(K,SupportedAlgs),
+ lists:foldl(fun(A1, Acc1) ->
+ case lists:member(A1, SAs) of
+ true -> Acc1;
+ false -> [A1|Acc1]
+ end
+ end, Acc, As);
+ (A, Acc) when is_atom(hd(SupportedAlgs)) ->
+ ct:log("~p:~p A=~p, SupportedAlgs=~p", [?MODULE,?LINE,A,SupportedAlgs]),
+ case lists:member(A, SupportedAlgs) of
+ true -> Acc;
+ false -> [A|Acc]
+ end;
+ (A, Acc) when is_tuple(hd(SupportedAlgs)) ->
+ ct:log("~p:~p A=~p, SupportedAlgs=~p", [?MODULE,?LINE,A,SupportedAlgs]),
+ [{_,S1},{_,S2}] = SupportedAlgs,
+
+ case lists:member(A, S1) andalso
+ lists:member(A, S2) of
+ true -> Acc;
+ false -> [A|Acc]
+ end
+ end, [], PA) of
+ [] ->
+ init_per_group(Tag, Algs, Alg, PA, Config);
+ L ->
+ ct:log("~p:~p Tag ~p, Alg ~p, Algs ~p, PA ~p,~nSupportedAlgs ~p", [?MODULE,?LINE, Tag, Alg, Algs, PA, SupportedAlgs]),
+ {skip,io_lib:format("Unsupported ~p: ~p", [Tag,L])}
+ end
end.
-init_per_group(public_key=Tag, Alg, Config) ->
- PA =
- case split(Tag, Alg) of
- [_] ->
- [Alg];
- [A1,A2] ->
- [A1,A2]
- end,
- OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag],
- ct:log("Init tests for public_key ~p~nOtherAlgs=~p",[PA,OtherAlgs]),
- PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
- %% Daemon started later in init_per_testcase
- try
- setup_pubkey(PA,
- [{pref_algs,PrefAlgs},
- {tag_alg,{Tag,PA}}
- | Config])
- catch
- _C:_E:_S ->
- ct:log("Exception ~p:~p~n~p",[_C,_E,_S]),
- {skip, io_lib:format("Unsupported: ~p",[Alg])}
- end;
-
-init_per_group(Tag, Alg, Config) ->
- PA =
- case split(Tag, Alg) of
- [_] ->
- [Alg];
- [A1,A2] ->
- [{client2server,[A1]},
- {server2client,[A2]}]
- end,
+init_per_group(Tag, Algs, Alg, PA, Config) ->
OtherAlgs = [{T,L} || {T,L} <- ssh_transport:supported_algorithms(), T=/=Tag],
- ct:log("Init tests for tag=~p alg=~p~nOtherAlgs=~p",[Tag,PA,OtherAlgs]),
+ ct:log("init_per_group Tag ~p, Alg ~p, Algs ~p ,PA ~p,~nOtherAlgs ~p", [Tag, Alg, Algs, PA, OtherAlgs]),
PrefAlgs = {preferred_algorithms,[{Tag,PA}|OtherAlgs]},
- start_std_daemon([PrefAlgs],
- [{pref_algs,PrefAlgs},
- {tag_alg,{Tag,[Alg]}}
- | Config]).
+ case Tag of
+ public_key ->
+ %% Daemon started later in init_per_testcase
+ try
+ setup_pubkey(PA,
+ [{pref_algs,PrefAlgs},
+ {tag_alg,{Tag,PA}}
+ | Config])
+ catch
+ _C:_E:_S ->
+ ct:log("Exception ~p:~p~n~p",[_C,_E,_S]),
+ {skip, io_lib:format("Unsupported: ~p",[Alg])}
+ end;
+ _ ->
+ start_std_daemon([PrefAlgs],
+ [{pref_algs,PrefAlgs},
+ {tag_alg,{Tag,[Alg]}}
+ | Config])
+ end.
end_per_group(_Alg, Config) ->
case proplists:get_value(srvr_pid,Config) of
@@ -364,6 +408,7 @@ sshc_simple_exec_os_cmd(Config) ->
" -o UserKnownHostsFile=",KnownHosts,
" -o CheckHostIP=no"
" -o StrictHostKeyChecking=no"
+ " -o UpdateHostKeys=no"
" -q"
" -x"
],
@@ -393,10 +438,9 @@ sshd_simple_exec(Config) ->
{public_key,Alg} -> [{pref_public_key_algs,Alg}];
_ -> []
end,
- ConnectionRef = ssh_test_lib:connect(22, [{silently_accept_hosts, true},
- proplists:get_value(pref_algs,Config),
- {user_interaction, false}
- | ClientPubKeyOpts]),
+ ConnectionRef = ssh_test_lib:connect(?SSH_DEFAULT_PORT,
+ [proplists:get_value(pref_algs,Config)
+ | ClientPubKeyOpts]),
{ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity),
success = ssh_connection:exec(ConnectionRef, ChannelId0,
"echo testing", infinity),
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_basic_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_basic_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_basic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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,6 +22,7 @@
-module(ssh_basic_SUITE).
+-include_lib("public_key/include/public_key.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/file.hrl").
@@ -73,6 +74,7 @@
login_bad_pwd_no_retry3/1,
login_bad_pwd_no_retry4/1,
login_bad_pwd_no_retry5/1,
+ max_initial_idle_time/1,
misc_ssh_options/1,
multi_daemon_opt_fd/1,
openssh_zlib_basic_test/1,
@@ -154,7 +156,9 @@ groups() ->
exec, exec_compressed,
exec_with_io_out, exec_with_io_in,
cli, cli_exit_normal, cli_exit_status,
- idle_time_client, idle_time_server, openssh_zlib_basic_test,
+ idle_time_client, idle_time_server,
+ max_initial_idle_time,
+ openssh_zlib_basic_test,
misc_ssh_options, inet_option, inet6_option,
shell, shell_socket, shell_ssh_conn, shell_no_unicode, shell_unicode_string,
close
@@ -242,16 +246,16 @@ end_per_testcase(_Config) ->
%%--------------------------------------------------------------------
%%% Application consistency test.
app_test(Config) when is_list(Config) ->
- ?t:app_test(ssh),
+ test_server:app_test(ssh),
ok.
%%--------------------------------------------------------------------
%%% Appup file consistency test.
appup_test(Config) when is_list(Config) ->
- ok = ?t:appup_test(ssh).
+ ok = test_server:appup_test(ssh).
%%--------------------------------------------------------------------
%%% Test that we can set some misc options not tested elsewhere
%%% some options not yet present are not decided if we should support or
-%%% if they need thier own test case.
+%%% if they need their own test case.
misc_ssh_options(Config) when is_list(Config) ->
SystemDir = filename:join(proplists:get_value(priv_dir, Config), system),
UserDir = proplists:get_value(priv_dir, Config),
@@ -471,6 +475,25 @@ idle_time_common(DaemonExtraOpts, Client
ssh:stop_daemon(Pid).
%%--------------------------------------------------------------------
+max_initial_idle_time(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},
+ {failfun, fun ssh_test_lib:failfun/2},
+ {max_initial_idle_time, 2000}
+ ]),
+ ConnectionRef =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false}
+ ]),
+ timer:sleep(8000),
+ {error, closed} = ssh_connection:session_channel(ConnectionRef, 1000),
+ ssh:stop_daemon(Pid).
+
+%%--------------------------------------------------------------------
%%% Test that ssh:shell/2 works
shell(Config) when is_list(Config) ->
process_flag(trap_exit, true),
@@ -520,7 +543,7 @@ shell_socket(Config) when is_list(Config
ct:log("~p:~p udp socket failed ok", [?MODULE,?LINE]),
gen_udp:close(BadSock),
- %% And finaly test with passive mode (which should work):
+ %% And finally test with passive mode (which should work):
IO = ssh_test_lib:start_io_server(),
{ok,Sock} = gen_tcp:connect(Host, Port, [{active,false}]),
Shell = ssh_test_lib:start_shell(Sock, IO, [{user_dir,UserDir}]),
@@ -550,7 +573,7 @@ shell_ssh_conn(Config) when is_list(Conf
ct:sleep(500),
IO = ssh_test_lib:start_io_server(),
- {ok,C} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ C = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
Shell = ssh_test_lib:start_shell(C, IO, undefined),
@@ -659,7 +682,7 @@ cli_exit_status(Config) when is_list(Con
%%--------------------------------------------------------------------
%%% Test that get correct error message if you try to start a daemon
-%%% on an adress that already runs a daemon see also seq10667
+%%% on an address that already runs a daemon see also seq10667
daemon_already_started(Config) when is_list(Config) ->
SystemDir = proplists:get_value(data_dir, Config),
UserDir = proplists:get_value(priv_dir, Config),
@@ -716,7 +739,9 @@ known_hosts(Config) when is_list(Config)
ConnectionRef =
ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
{user_interaction, false},
- silently_accept_hosts]),
+ {silently_accept_hosts, true},
+ {save_accepted_host, true}
+ ]),
{ok, _Channel} = ssh_connection:session_channel(ConnectionRef, infinity),
ok = ssh:close(ConnectionRef),
{ok, Binary} = file:read_file(KnownHosts),
@@ -745,7 +770,9 @@ known_hosts(Config) when is_list(Config)
_ConnectionRef2 =
ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
{user_interaction, false},
- silently_accept_hosts]),
+ {silently_accept_hosts, true},
+ {save_accepted_host, true}
+ ]),
{ok, Binary2} = file:read_file(KnownHosts),
case Binary of
Binary2 -> ok;
@@ -758,7 +785,9 @@ known_hosts(Config) when is_list(Config)
_ConnectionRef3 =
ssh_test_lib:connect(Host, Port, [{user_dir, PrivDir},
{user_interaction, false},
- silently_accept_hosts]),
+ {silently_accept_hosts, true},
+ {save_accepted_host, true}
+ ]),
ct:log("New known_hosts:~n~p",[Binary3]),
{ok, Binary4} = file:read_file(KnownHosts),
case Binary3 of
@@ -777,12 +806,14 @@ ssh_file_is_host_key(Config) ->
ct:log("Dir = ~p", [Dir]),
KnownHosts = filename:join(Dir, "known_hosts"),
- Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
- 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
- Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
- 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
- 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
- 190,175,232,37,97,128>>},
+ Key1 = {#'ECPoint'{point = <<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ {namedCurve,?'id-Ed25519'}},
+ Key2 = {#'ECPoint'{point = <<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+ {namedCurve,?'id-Ed448'}},
Key3 = {'RSAPublicKey',26565213557098441060571713941539431805641814292761836797158846333985276408616038302348064841541244792430014595960643885863857366044141899534486816837416587694213836843799730043696945690516841209754307951050689906601353687467659852190777927968674989320642319504162787468947018505175948989102544757855693228490011564030927714896252701919941617689227585365348356580525802093985552564228730275431222515673065363441446158870936027338182083252824862151536327733046243804704721201548991176621134884093279416695997338124856506800535228380202243308550318880784741179703553922258881924287662178348044420509921666661119986374777,
65537},
@@ -825,13 +856,14 @@ ssh_file_is_host_key_misc(Config) ->
ct:log("Dir = ~p", [Dir]),
KnownHosts = filename:join(Dir, "known_hosts"),
- Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
- 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
- Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
- 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
- 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
- 190,175,232,37,97,128>>},
-
+ Key1 = {#'ECPoint'{point = <<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ {namedCurve,?'id-Ed25519'}},
+ Key2 = {#'ECPoint'{point = <<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+ {namedCurve,?'id-Ed448'}},
FileContents = <<"h11,h12,!h12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9\n",
%% Key revoked later in file:
"h22 ssh-ed448 AAAACXNzaC1lZDQ0OAAAADlf10SbWbRh/Sznh+xhatRqHaE0JIWnDh"
@@ -865,13 +897,14 @@ ssh_file_is_auth_key(Config) ->
ct:log("Dir = ~p", [Dir]),
AuthKeys = filename:join(Dir, "authorized_keys"),
- Key1 = {ed_pub,ed25519,<<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
- 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
- Key2 = {ed_pub,ed448,<<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
- 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
- 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
- 190,175,232,37,97,128>>},
-
+ Key1 = {#'ECPoint'{point = <<73,72,235,162,96,101,154,59,217,114,123,192,96,105,250,29,
+ 214,76,60,63,167,21,221,118,246,168,152,2,7,172,137,125>>},
+ {namedCurve,?'id-Ed25519'}},
+ Key2 = {#'ECPoint'{point = <<95,215,68,155,89,180,97,253,44,231,135,236,97,106,212,106,29,
+ 161,52,36,133,167,14,31,138,14,167,93,128,233,103,120,237,241,
+ 36,118,155,70,199,6,27,214,120,61,241,229,15,108,209,250,26,
+ 190,175,232,37,97,128>>},
+ {namedCurve,?'id-Ed448'}},
FileContents = <<" \n",
"# A test file\n",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIElI66JgZZo72XJ7wGBp+h3WTDw/pxXddvaomAIHrIl9 foo@example.com\n",
@@ -979,6 +1012,7 @@ internal_error(Config) when is_list(Conf
{error, Error} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_dir, UserDir},
{user_interaction, false}]),
check_error(Error),
@@ -1091,7 +1125,7 @@ double_close(Config) when is_list(Config
{user_dir, UserDir},
{user_passwords, [{"vego", "morot"}]},
{failfun, fun ssh_test_lib:failfun/2}]),
- {ok, CM} = ssh:connect(Host, Port, [{silently_accept_hosts, true},
+ CM = ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user, "vego"},
{password, "morot"},
@@ -1108,6 +1142,8 @@ daemon_opt_fd(Config) ->
file:make_dir(UserDir),
{ok,S1} = gen_tcp:listen(0,[]),
+ ct:log("Socket S1 = ~p", [S1]),
+
{ok,Fd1} = prim_inet:getfd(S1),
{ok,Pid1} = ssh:daemon(0, [{system_dir, SystemDir},
@@ -1117,7 +1153,7 @@ daemon_opt_fd(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
{ok,{_Host1,Port1}} = inet:sockname(S1),
- {ok, C1} = ssh:connect("localhost", Port1, [{silently_accept_hosts, true},
+ C1 = ssh_test_lib:connect(Port1, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user, "vego"},
{password, "morot"},
@@ -1137,6 +1173,7 @@ multi_daemon_opt_fd(Config) ->
Test =
fun() ->
{ok,S} = gen_tcp:listen(0,[]),
+ ct:log("Socket S = ~p", [S]),
{ok,Fd} = prim_inet:getfd(S),
{ok,Pid} = ssh:daemon(0, [{system_dir, SystemDir},
@@ -1146,7 +1183,7 @@ multi_daemon_opt_fd(Config) ->
{failfun, fun ssh_test_lib:failfun/2}]),
{ok,{_Host,Port}} = inet:sockname(S),
- {ok, C} = ssh:connect("localhost", Port, [{silently_accept_hosts, true},
+ C = ssh_test_lib:connect(Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user, "vego"},
{password, "morot"},
@@ -1194,7 +1231,7 @@ packet_size(Config) ->
rec(Server, Conn, Ch, MaxSz) ->
receive
- {ssh_cm,Conn,{data,Ch,_,M}} when size(M) =< MaxSz ->
+ {ssh_cm,Conn,{data,Ch,_,M}} when byte_size(M) =< MaxSz ->
ct:log("~p: ~p",[MaxSz,M]),
rec(Server, Conn, Ch, MaxSz);
{ssh_cm,Conn,{data,Ch,_,_}} = M ->
@@ -1383,7 +1420,7 @@ login_bad_pwd_no_retry(Config, AuthMetho
{ok,Conn} ->
ssh:close(Conn),
ssh:stop_daemon(DaemonRef),
- {fail, "Connect erroneosly succeded"}
+ {fail, "Connect erroneosly succeeded"}
end
end.
@@ -1455,7 +1492,7 @@ setopts_getopts(Config) ->
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------
%% Due to timing the error message may or may not be delivered to
-%% the "tcp-application" before the socket closed message is recived
+%% the "tcp-application" before the socket closed message is received
check_error("Invalid state") -> ok;
check_error("Connection closed") -> ok;
check_error("Selection of key exchange algorithm failed"++_) -> ok;
@@ -1467,7 +1504,7 @@ basic_test(Config) ->
ServerOpts = proplists:get_value(server_opts, Config),
{Pid, Host, Port} = ssh_test_lib:daemon(ServerOpts),
- {ok, CM} = ssh:connect(Host, Port, ClientOpts),
+ CM = ssh_test_lib:connect(Host, Port, ClientOpts),
ok = ssh:close(CM),
ssh:stop_daemon(Pid).
@@ -1505,7 +1542,7 @@ new_do_shell(IO, N, [new_prompt|More]) -
new_do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
Pfx = prompt_prefix(),
- PfxSize = size(Pfx),
+ PfxSize = byte_size(Pfx),
receive
_X = <<"\r\n">> ->
ct:log("Skip newline ~p",[_X]),
@@ -1547,7 +1584,8 @@ new_do_shell(IO, N, Ops=[{Order,Arg}|Mor
ct:log("Matched echo ~ts",[RecStr]),
new_do_shell(IO, N, More);
false ->
- ct:fail("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr])
+ ct:log("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr]),
+ new_do_shell(IO, N, Ops)
end
after 30000 ->
ct:log("Message queue of ~p:~n~p",
@@ -1572,7 +1610,7 @@ prompt_prefix() ->
new_do_shell_prompt(IO, N, type, Str, More) ->
ct:log("Matched prompt ~p to trigger sending of next line to server",[N]),
IO ! {input, self(), Str++"\r\n"},
- ct:log("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
+ ct:log("Prompt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
new_do_shell(IO, N, More);
new_do_shell_prompt(IO, N, Op, Str, More) ->
ct:log("Matched prompt ~p",[N]),
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_bench_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_bench_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_bench_SUITE.erl
@@ -1,7 +1,7 @@
%%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2021. 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.
@@ -156,6 +156,7 @@ transfer_text(Config) ->
{password, proplists:get_value(pwd, Config)},
{user_dir, proplists:get_value(priv_dir, Config)},
{silently_accept_hosts, true},
+ {save_accepted_host, false},
{user_interaction, false},
{max_random_length_padding, 0}
],
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_chan_behaviours_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2018-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2018-2021. 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.
@@ -90,7 +90,6 @@ end_per_testcase(_TC, Config) ->
-define(EXPECT(What, Bind),
- Bind =
(fun() ->
receive What ->
ct:log("~p:~p ~p got ~p",[?MODULE,?LINE,self(),What]),
@@ -105,7 +104,7 @@ end_per_testcase(_TC, Config) ->
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
-%% Try start a subsystem whos name is not known by the server
+%% Try start a subsystem whose name is not known by the server
noexist_subsystem(Config) ->
C = proplists:get_value(connref, Config),
{ok, Ch} = ssh_connection:session_channel(C, infinity),
@@ -129,20 +128,20 @@ defined_subsystem(Config) ->
{ok, Ch1} = ssh_connection:session_channel(C, infinity),
success = ssh_connection:subsystem(C, Ch1, "ch1", infinity),
- IDsrv = ?EXPECT({{_Csrv,_Ch1srv}, {ssh_channel_up,_Ch1srv,_Csrv}}, {_Csrv,_Ch1srv}),
+ IDsrv = ?EXPECT({{Csrv,Ch1srv}, {ssh_channel_up,Ch1srv,Csrv}}, {Csrv,Ch1srv}),
ok = ssh_connection:close(C, Ch1),
?EXPECT({IDsrv, {terminate,normal}}, []),
?EXPECT({ssh_cm, C, {closed,Ch1}}, []), % self() is instead of a proper channel handler
ok.
-%% Try to start and stop a subsystem from a ssh_client_channel behviour
+%% Try to start and stop a subsystem from a ssh_client_channel behaviour
subsystem_client(Config) ->
C = proplists:get_value(connref, Config),
{ok,ChRef} = ssh_chan_behaviours_client:start_link(C),
- IDclt = ?EXPECT({{C,_Ch1clt}, {ssh_channel_up,_Ch1clt,C}}, {C,_Ch1clt}),
- IDsrv = ?EXPECT({{_Csrv,_Ch1srv}, {ssh_channel_up,_Ch1srv,_Csrv}}, {_Csrv,_Ch1srv}),
+ IDclt = ?EXPECT({{C,Ch1clt}, {ssh_channel_up,_C1clt,C}}, {C,Ch1clt}),
+ IDsrv = ?EXPECT({{Csrv,_h1srv}, {ssh_channel_up,Ch1srv,Csrv}}, {Csrv,Ch1srv}),
ok = ssh_chan_behaviours_client:stop(ChRef),
?EXPECT({IDclt, {terminate,normal}}, []), % From the proper channel handler
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_collect_labmachine_info_SUITE.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_collect_labmachine_info_SUITE.erl
@@ -0,0 +1,250 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2021. 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_collect_labmachine_info_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+
+-export([save_ssh_data/3]).
+
+-export([
+ suite/0,
+ all/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ ssh_info_lib/1
+ ]).
+
+
+-define(DAYS_TO_KEEP, 5).
+
+save_ssh_data(Host, Data, Config0) ->
+ case init_data_transfer(Host, Config0) of
+ Config1 when is_list(Config1) ->
+ Config =
+ case Data of
+ [[_|_]|_] ->
+ lists:foldl(fun save_data/2, Config1, Data);
+ _ ->
+ save_data(Data, Config1)
+ end,
+ end_data_transfer(Config);
+
+ Skip ->
+ Skip
+ end.
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+suite() -> [{timetrap,{seconds,40}}].
+
+all() -> [ssh_info_lib].
+
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case os:find_executable("ssh") of
+ false ->
+ {skip, "No ssh found"};
+
+ _Path ->
+ init_data_transfer(hostname(), Config)
+ end.
+
+end_per_suite(Config) ->
+ end_data_transfer(Config).
+
+%%--------------------------------------------------------------------
+ssh_info_lib(Config) ->
+ NewEntry = new_entry(),
+ ct:pal("New entry:~n~p",[NewEntry]),
+ save_data(NewEntry, Config).
+
+new_entry() ->
+ [{hostname, hostname()},
+ {type, host},
+ {date, date()},
+ {time, time()},
+ {os_type, os:type()},
+ {os_version, os:version()},
+ {full_ssh_version, ssh_info()}
+ ].
+
+ssh_info() ->
+ try os:cmd("ssh -V") of
+ Version when is_list(Version) -> Version--"\n";
+ _ -> "?"
+ catch
+ _:_ -> "??"
+ end.
+
+%%--------------------------------------------------------------------
+hostname() ->
+ case inet:gethostname() of
+ {ok,Name} -> string:to_lower(Name);
+ _ -> "undefined"
+ end.
+
+priv_dir(Config) -> proplists:get_value(priv_dir, Config).
+
+priv_file(Config, Name) -> filename:join(priv_dir(Config), Name).
+
+remove_drive_letter(FileName) ->
+ ssh_test_lib:winpath_to_linuxpath(FileName).
+
+usable_file(FileName) ->
+ case file:open(FileName, [append]) of
+ {ok,D} ->
+ ok == file:close(D);
+ _ ->
+ false
+ end.
+
+%%%----------------------------------------------------------------
+wsl_ify(Cmnd) ->
+ case os:getenv("WSLENV") of
+ false -> Cmnd;
+ _ -> "wsl " ++ Cmnd
+ end.
+
+%%%================================================================
+save_data(NewEntry, Config) ->
+ LocalFile = proplists:get_value(local_file, Config),
+
+ YoungEntries =
+ case file:consult(LocalFile) of
+ {ok, Consulted} when is_list(Consulted) ->
+ lists:filter(fun(E) -> is_young(E) end,
+ Consulted);
+ Other ->
+ ct:log("Strange result of consult:~n~p", [Other]),
+ ct:fail("Consult failed")
+ end,
+
+ {ok,D} = file:open(LocalFile, [write]),
+ lists:foreach(fun(E) ->
+ io:format(D, '~p.~n', [E])
+ end, lists:usort([NewEntry|YoungEntries])),
+ file:close(D),
+ Config.
+
+
+is_young(E) ->
+ try
+ Days = days_ago(proplists:get_value(date, E)),
+ Days >= 0 andalso Days =< ?DAYS_TO_KEEP
+ catch
+ _:_ -> false % No or illegal date property
+ end.
+
+
+days_ago(D={_,_,_})->
+ calendar:date_to_gregorian_days(date()) - calendar:date_to_gregorian_days(D).
+
+%%%----------------------------------------------------------------
+init_data_transfer(Host, Config) ->
+ case ct:get_config(collect_host_info) of
+ undefined ->
+ {skip, "No 'collect_host_info' path configured"};
+
+ Root when is_list(Root) ->
+ RemoteFile = filename:join([Root, "ssh_info", Host++".data"]),
+ init_data_transfer_cont(Host, Config, RemoteFile)
+ end.
+
+init_data_transfer_cont(Host, Config, RemoteFile) ->
+ LocalFile = priv_file(Config, Host++".sshdata"),
+
+ case usable_file(LocalFile) of
+ false -> ct:fail(no_local_file);
+ true -> ok
+ end,
+
+ TransferType =
+ case {path_type(RemoteFile), os:type()} of
+ {local, {unix,_}} ->
+ case usable_file(RemoteFile) of
+ true -> filesystem;
+ false -> ssh
+ end;
+ _ ->
+ ssh
+ end,
+
+ case TransferType of
+ filesystem ->
+ %% 'filesystem' was concluded since it was possible
+ %% to open the file in append mode
+ {ok,B} = file:read_file(RemoteFile),
+ ok = file:write_file(LocalFile, B);
+ ssh ->
+ SCP = wsl_ify("scp "++RemoteFile++" "++remove_drive_letter(LocalFile)),
+ ct:pal("Run command: \"~s\"", [SCP]),
+ Result = os:cmd(SCP),
+ ct:pal("Command result: \"~s\"",[Result])
+ end,
+
+ [{transfer_type, TransferType},
+ {local_file,LocalFile},
+ {remote_file,RemoteFile} | Config].
+
+%%%----------------------------------------------------------------
+end_data_transfer(Config) ->
+ LocalFile = proplists:get_value(local_file,Config),
+ RemoteFile = proplists:get_value(remote_file,Config),
+ case proplists:get_value(transfer_type,Config) of
+ filesystem ->
+ {ok,B} = file:read_file(LocalFile),
+ ok = file:write_file(RemoteFile, B);
+ ssh ->
+ SCP = wsl_ify("scp "++remove_drive_letter(LocalFile)++" "++RemoteFile),
+ ct:pal("Run command: \"~s\"", [SCP]),
+ Result = os:cmd(SCP),
+ ct:pal("Command result: \"~s\"",[Result])
+ end,
+ file:delete(LocalFile).
+
+path_type(Path) ->
+ case string:lexemes(Path, ":") of
+ [_] ->
+ local;
+ [Host | _] ->
+ case string:find(Host, "/") of
+ nomatch -> remote;
+ _ -> local
+ end
+ end.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_compat_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_compat_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_compat_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2023. 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.
@@ -35,7 +35,9 @@
init_per_suite/1,
end_per_suite/1,
init_per_group/2,
- end_per_group/2
+ end_per_group/2,
+ init_per_testcase/2,
+ end_per_testcase/2
]).
-export([
@@ -78,7 +80,7 @@ groups() ->
ssh_image_versions() ->
try
%% Find all useful containers in such a way that undefined command, too low
- %% priviliges, no containers and containers found give meaningful result:
+ %% privileges, no containers and containers found give meaningful result:
L0 = ["REPOSITORY"++_|_] = string:tokens(os:cmd("docker images"), "\r\n"),
[["REPOSITORY","TAG"|_]|L1] = [string:tokens(E, " ") || E<-L0],
[list_to_atom(V) || [?DOCKER_PFX,V|_] <- L1]
@@ -98,7 +100,9 @@ init_per_suite(Config) ->
{skip, "No docker"};
_ ->
ssh:start(),
+ log_image_versions(ssh_image_versions(), Config),
ct:log("Crypto info: ~p",[crypto:info_lib()]),
+ ct:log("ssh image versions: ~p",[ssh_image_versions()]),
Config
end).
@@ -148,7 +152,7 @@ init_per_group(G, Config0) ->
ct:comment("~s",[NewCmnt])
end,
AuthMethods =
- %% This should be obtained by quering the peer, but that
+ %% This should be obtained by querying the peer, but that
%% is a bit hard. It is possible with ssh_protocol_SUITE
%% techniques, but it can wait.
case Vssh of
@@ -192,6 +196,26 @@ end_per_group(G, Config) ->
ok
end.
+
+init_per_testcase(TC, Config) when TC==login_otp_is_client ;
+ TC==all_algorithms_sftp_exec_reneg_otp_is_client ->
+ case proplists:get_value(ssh_version, Config) of
+ "openssh4.4p1-openssl0.9.8c" -> {skip, "Not tested"};
+ "openssh4.5p1-openssl0.9.8m" -> {skip, "Not tested"};
+ "openssh5.0p1-openssl0.9.8za" -> {skip, "Not tested"};
+ "openssh6.2p2-openssl0.9.8c" -> {skip, "Not tested"};
+ "openssh6.3p1-openssl0.9.8zh" -> {skip, "Not tested"};
+ "openssh6.6p1-openssl1.0.2n" -> {skip, "Not tested"};
+ _ ->
+ Config
+ end;
+init_per_testcase(_, Config) ->
+ Config.
+
+
+end_per_testcase(_TC, _Config) ->
+ ok.
+
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
@@ -228,6 +252,7 @@ login_otp_is_client(Config) ->
{user,?USER},
{user_dir, Dir},
{silently_accept_hosts,true},
+ {save_accepted_host, false},
{user_interaction,false}
| Opts
])
@@ -296,6 +321,7 @@ all_algorithms_sftp_exec_reneg_otp_is_cl
{user_dir, new_dir(Config)},
{preferred_algorithms, [{Tag,[Alg]} | PrefAlgs]},
{silently_accept_hosts,true},
+ {save_accepted_host, false},
{user_interaction,false}
]) ,
test_erl_client_reneg(ConnRes, % Seems that max 10 channels may be open in sshd
@@ -310,6 +336,7 @@ all_algorithms_sftp_exec_reneg_otp_is_cl
%%--------------------------------------------------------------------
renegotiation_otp_is_server(Config) ->
PublicKeyAlgs = [A || {public_key,A} <- proplists:get_value(common_remote_client_algs, Config, [])],
+ ct:log("PublicKeyAlgs = ~p", [PublicKeyAlgs]),
UserDir = setup_remote_priv_and_local_auth_keys(hd(PublicKeyAlgs), Config),
SftpRootDir = new_dir(Config),
ct:log("Rootdir = ~p",[SftpRootDir]),
@@ -321,6 +348,7 @@ renegotiation_otp_is_server(Config) ->
{user_dir, UserDir},
{user_passwords, [{?USER,?PASSWD}]},
{failfun, fun ssh_test_lib:failfun/2},
+ {modify_algorithms, [{append, [{public_key,PublicKeyAlgs}]}]},
{connectfun,
fun(_,_,_) ->
HostConnRef = self(),
@@ -364,7 +392,7 @@ reneg_tester_loop(Parent, Ref, HostConnR
send_recv_big_with_renegotiate_otp_is_client(Config) ->
%% Connect to the remote openssh server:
{IP,Port} = ip_port(Config),
- {ok,C} = ssh:connect(IP, Port, [{user,?USER},
+ C = ssh_test_lib:connect(IP, Port, [{user,?USER},
{password,?PASSWD},
{user_dir, setup_remote_auth_keys_and_local_priv('ssh-rsa', Config)},
{silently_accept_hosts,true},
@@ -382,7 +410,7 @@ send_recv_big_with_renegotiate_otp_is_cl
Data = << <<X:32>> || X <- lists:seq(1, HalfSizeBytes div 4)>>,
%% Send the data. Must spawn a process to avoid deadlock. The client will block
- %% until all is sent through the send window. But the server will stop receiveing
+ %% until all is sent through the send window. But the server will stop receiving
%% when the servers send-window towards the client is full.
%% Since the client can't receive before the server has received all but 655k from the client
%% ssh_connection:send/4 is blocking...
@@ -473,7 +501,7 @@ loop_until(CondFun, DoFun, Acc) ->
exec_from_docker(Config, HostIP, HostPort, Command, Expects, ExtraSshArg) when is_binary(hd(Expects)),
is_list(Config) ->
{DockerIP,DockerPort} = ip_port(Config),
- {ok,C} = ssh:connect(DockerIP, DockerPort,
+ C = ssh_test_lib:connect(DockerIP, DockerPort,
[{user,?USER},
{password,?PASSWD},
{user_dir, new_dir(Config)},
@@ -634,6 +662,7 @@ setup_remote_auth_keys_and_local_priv(Ke
{password, ?PASSWD },
{auth_methods, "password"},
{silently_accept_hosts,true},
+ {save_accepted_host, false},
{preferred_algorithms, ssh_transport:supported_algorithms()},
{user_interaction,false}
]),
@@ -847,7 +876,7 @@ new_dir(Config) ->
%%--------------------------------------------------------------------
%%
-%% Find the intersection of algoritms for otp ssh and the docker ssh.
+%% Find the intersection of algorithms for otp ssh and the docker ssh.
%% Returns {ok, ServerHello, Server, ClientHello, Client} where Server are the algorithms common
%% with the docker server and analogous for Client.
%%
@@ -1069,7 +1098,7 @@ receive_hello(S, Ack) ->
receive_kexinit(_S, <<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>)
- when PacketLen < 5000, % heuristic max len to stop huge attempts if packet decodeing get out of sync
+ when PacketLen < 5000, % heuristic max len to stop huge attempts if packet decoding get out of sync
size(PayloadAndPadding) >= (PacketLen-1) % Need more bytes?
->
ct:log("Has all ~p packet bytes",[PacketLen]),
@@ -1182,7 +1211,7 @@ do_check_local_directory(ServerRootDir)
call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir, Ref) ->
{DockerIP,DockerPort} = ip_port(Config),
ct:log("Going to connect ~p:~p", [DockerIP, DockerPort]),
- {ok,C} = ssh:connect(DockerIP, DockerPort,
+ C = ssh_test_lib:connect(DockerIP, DockerPort,
[{user,?USER},
{password,?PASSWD},
{user_dir, UserDir},
@@ -1237,13 +1266,13 @@ call_sftp_in_docker(Config, ServerIP, Se
recv_log_msgs(C, Ch) ->
receive
{ssh_cm,C,{closed,Ch}} ->
- %% ct:log("Channel closed ~p",[{closed,1}]),
+ ct:log("Channel closed ~p",[{closed,1}]),
ok;
{ssh_cm,C,{data,Ch,1,Msg}} ->
ct:log("*** ERROR from docker:~n~s",[Msg]),
recv_log_msgs(C, Ch);
{ssh_cm,C,_Msg} ->
- %% ct:log("Got ~p",[_Msg]),
+ ct:log("Got ~p",[_Msg]),
recv_log_msgs(C, Ch)
after
30000 ->
@@ -1458,3 +1487,50 @@ renegotiate_test(Kex1, ConnectionRef) ->
%% ct:log("Renegotiate test passed!",[]),
ok
end.
+
+%%%----------------------------------------------------------------
+%% ImageVersions = ['dropbearv2016.72',
+%% 'openssh4.4p1-openssl0.9.8c',
+%% ...
+%% 'openssh8.8p1-openssl1.1.1l']
+
+log_image_versions(ImageVersions, Config) ->
+ case true == (catch
+ lists:member({save_ssh_data,3},
+ ssh_collect_labmachine_info_SUITE:module_info(exports)))
+ of
+ true ->
+ HostPfx = hostname()++"_docker",
+ {_Imax, Entries} = lists:foldl(fix_entry(HostPfx), {0,[]}, ImageVersions),
+ ssh_collect_labmachine_info_SUITE:save_ssh_data(HostPfx, Entries, Config);
+ false ->
+ Config
+ end.
+
+
+fix_entry(HostPfx) ->
+ fun(E, {I,Acc}) ->
+ Entry =
+ [{hostname, lists:flatten(io_lib:format("~s:~2..0w",[HostPfx,I]))},
+ {type, compat_test},
+ {date, date()},
+ {time, time()},
+ {os_type, os:type()},
+ {os_version, os:version()},
+ {full_ssh_version, fix_version(E)}
+ ],
+ {I+1, [Entry|Acc]}
+ end.
+
+fix_version(E) ->
+ case string:tokens(atom_to_list(E), "-") of
+ ["openssh"++Vs, "openssl"++Vc ] -> lists:concat(["OpenSSH_",Vs," OpenSSL ",Vc]);
+ ["openssh"++Vs, "libressl"++Vc] -> lists:concat(["OpenSSH_",Vs," LibreSSL ",Vc]);
+ _ -> atom_to_list(E)
+ end.
+
+hostname() ->
+ case inet:gethostname() of
+ {ok,Name} -> string:to_lower(Name);
+ _ -> "undefined"
+ end.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_cth.erl
===================================================================
--- /dev/null
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_cth.erl
@@ -0,0 +1,104 @@
+-module(ssh_cth).
+
+-export([id/1,
+ init/2,
+ pre_init_per_suite/3,
+ pre_end_per_suite/3,
+ pre_init_per_group/4,
+ post_init_per_group/5,
+ pre_end_per_group/4,
+ post_end_per_group/5,
+ pre_init_per_testcase/4,
+ post_init_per_testcase/5,
+ pre_end_per_testcase/4,
+ post_end_per_testcase/5
+ ]).
+
+-record(c, {
+ known_hosts_file_name = filename:join(os:getenv("HOME"), ".ssh/known_hosts"),
+ known_hosts_last_contents = <<>>,
+ suite, % suite name
+ groups = [], % Group path in reversed order
+ n % test case number
+ }).
+
+id(Opts) ->
+ proplists:get_value(filename, Opts, "/tmp/file.log").
+
+init(_Id, _Opts) ->
+ {ok, #c{n=1}}.
+
+pre_init_per_suite(Suite, Config, State0) ->
+ {_, State} = read_known_hosts_diff(State0#c{suite=Suite}),
+ {Config, State}.
+
+pre_end_per_suite(Suite, Config, State) ->
+ ct:pal("BEGIN ~p:end_per_suite(...)", [Suite]),
+ {Config, State}.
+
+
+pre_init_per_group(Suite, Group, Config, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sBEGIN ~p:init_per_group(~p,...)", [log_diff(Diff),Suite,Group]),
+ {Config, State#c{groups = (State#c.groups ++ [Group])}}.
+
+post_init_per_group(Suite, Group, _Config, Return, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sEND ~p:init_per_group(~p,...)", [log_diff(Diff),Suite,Group]),
+ {Return, State}.
+
+pre_end_per_group(Suite, Group, Config, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sBEGIN ~p:end_per_group(~p,...)", [log_diff(Diff), Suite, Group]),
+ {Config, State}.
+
+post_end_per_group(Suite, Group, _Config, Return, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sEND ~p:end_per_group(~p,...)", [log_diff(Diff),Suite,Group]),
+ {Return, State#c{groups = lists:reverse(lists:reverse(State#c.groups)--[Group])}}.
+
+
+pre_init_per_testcase(SuiteName, _TC, Config, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~s########## ~p ~p ~s ~p", [log_diff(Diff), State0#c.suite, State0#c.n, groups(Config), SuiteName]),
+ {Config, State#c{n = State#c.n + 1}}.
+
+post_init_per_testcase(SuiteName, TestcaseName, _Config, Return, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~send ~p:init_per_testcase(~p,...)", [log_diff(Diff), SuiteName, TestcaseName]),
+ {Return, State}.
+
+pre_end_per_testcase(Suite, TC, Config, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sBEGIN ~p:end_per_testcase(~p,...)", [log_diff(Diff), Suite, TC]),
+ {Config, State}.
+
+post_end_per_testcase(SuiteName, TC, _Config, Return, State0) ->
+ {Diff, State} = read_known_hosts_diff(State0),
+ ct:pal("~sEND ~p:end_per_testcase(~p,...)", [log_diff(Diff), SuiteName, TC]),
+ {Return, State}.
+
+
+
+
+groups(Config) ->
+ F = fun(X) -> io_lib:format("~w",[X]) end,
+ io_lib:format("~s", [lists:join("/", lists:map(F,get_groups(Config)))]).
+
+get_groups(Config) ->
+ P = proplists:get_value(tc_group_path, Config, []) ++
+ [proplists:get_value(tc_group_properties, Config, [])],
+ [Name || L <- P,
+ is_list(L),
+ {name,Name} <- L].
+
+
+
+read_known_hosts_diff(S = #c{known_hosts_file_name = File,
+ known_hosts_last_contents = Bin0}) ->
+ {ok, <<Bin0:(size(Bin0))/binary, Diff/binary>> = Bin} = file:read_file(File),
+ {Diff, S#c{known_hosts_last_contents = Bin}}.
+
+log_diff(<<>>) -> "";
+log_diff(Bin) -> io_lib:format("~n++++ ~p~n",[Bin]).
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_engine_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_engine_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_engine_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2021. 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.
@@ -80,6 +80,8 @@ init_per_suite(Config) ->
{skip, "Engine not supported on this OpenSSL version"};
{error, bad_engine_id} ->
{skip, "Dynamic Engine not supported"};
+ {error, notexist} ->
+ {skip, "No Dynamic Engine to test with"};
Other ->
ct:log("Engine load failed: ~p",[Other]),
{fail, "Engine load failed"}
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_key_cb.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2021. 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.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb_engine_keys.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_key_cb_engine_keys.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb_engine_keys.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2021. 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.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb_options.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_key_cb_options.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_key_cb_options.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2015-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2015-2021. 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.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_pubkey_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_pubkey_SUITE.erl
@@ -34,36 +34,63 @@
]).
-export([
+ check_dsa_disabled/1,
+ check_rsa_sha1_disabled/1,
connect_dsa_to_dsa/1,
connect_dsa_to_ecdsa/1,
connect_dsa_to_ed25519/1,
connect_dsa_to_ed448/1,
- connect_dsa_to_rsa/1,
+ connect_dsa_to_rsa_sha2/1,
connect_ecdsa_to_dsa/1,
connect_ecdsa_to_ecdsa/1,
connect_ecdsa_to_ed25519/1,
connect_ecdsa_to_ed448/1,
- connect_ecdsa_to_rsa/1,
+ connect_ecdsa_to_rsa_sha2/1,
connect_ed25519_to_dsa/1,
connect_ed25519_to_ecdsa/1,
connect_ed25519_to_ed25519/1,
connect_ed25519_to_ed448/1,
- connect_ed25519_to_rsa/1,
+ connect_ed25519_to_rsa_sha2/1,
connect_ed448_to_dsa/1,
connect_ed448_to_ecdsa/1,
connect_ed448_to_ed25519/1,
connect_ed448_to_ed448/1,
- connect_ed448_to_rsa/1,
- connect_rsa_to_dsa/1,
- connect_rsa_to_ecdsa/1,
- connect_rsa_to_ed25519/1,
- connect_rsa_to_ed448/1,
- connect_rsa_to_rsa/1,
+ connect_ed448_to_rsa_sha2/1,
+ connect_rsa_sha1_to_dsa/1,
+ connect_rsa_sha2_to_dsa/1,
+ connect_rsa_sha2_to_ecdsa/1,
+ connect_rsa_sha2_to_ed25519/1,
+ connect_rsa_sha2_to_ed448/1,
+ connect_rsa_sha2_to_rsa_sha2/1,
+
+ ssh_rsa_public_key/1,
+ ssh_dsa_public_key/1,
+ ssh_ecdsa_public_key/1,
+ ssh_rfc4716_rsa_comment/1,
+ ssh_rfc4716_dsa_comment/1,
+ ssh_rfc4716_rsa_subject/1,
+ ssh_list_public_key/1,
+ ssh_known_hosts/1,
+ ssh1_known_hosts/1,
+ ssh_auth_keys/1,
+ ssh1_auth_keys/1,
+ ssh_openssh_key_with_comment/1,
+ ssh_openssh_key_long_header/1,
+
+ ssh_hostkey_fingerprint_md5_implicit/1,
+ ssh_hostkey_fingerprint_md5/1,
+ ssh_hostkey_fingerprint_sha/1,
+ ssh_hostkey_fingerprint_sha256/1,
+ ssh_hostkey_fingerprint_sha384/1,
+ ssh_hostkey_fingerprint_sha512/1,
+ ssh_hostkey_fingerprint_list/1,
- chk_known_hosts/1
+ chk_known_hosts/1,
+ ssh_hostkey_pkcs8/1
]).
-include_lib("common_test/include/ct.hrl").
+-include_lib("public_key/include/public_key.hrl").
-include("ssh_test_lib.hrl").
%%%----------------------------------------------------------------
@@ -78,48 +105,72 @@ all() ->
[{group, old_format},
{group, new_format},
{group, option_space},
+ {group, ssh_hostkey_fingerprint},
+ {group, ssh_public_key_decode_encode},
+ {group, pkcs8},
chk_known_hosts
].
--define(tests_old, [connect_rsa_to_rsa,
- connect_rsa_to_dsa,
- connect_rsa_to_ecdsa,
- connect_dsa_to_rsa,
+-define(tests_old, [connect_rsa_sha2_to_rsa_sha2,
+ connect_rsa_sha1_to_dsa,
+ connect_rsa_sha2_to_dsa,
+ connect_rsa_sha2_to_ecdsa,
+ connect_dsa_to_rsa_sha2,
connect_dsa_to_dsa,
connect_dsa_to_ecdsa,
- connect_ecdsa_to_rsa,
+ connect_ecdsa_to_rsa_sha2,
connect_ecdsa_to_dsa,
- connect_ecdsa_to_ecdsa
- ]).
-
--define(tests_new, [
+ connect_ecdsa_to_ecdsa,
connect_dsa_to_ed25519,
- connect_dsa_to_ed448,
connect_ecdsa_to_ed25519,
+ connect_rsa_sha2_to_ed25519,
+ connect_dsa_to_ed448,
connect_ecdsa_to_ed448,
- connect_ed25519_to_dsa,
+ connect_rsa_sha2_to_ed448
+ ]).
+
+-define(tests_new, [connect_ed25519_to_dsa,
connect_ed25519_to_ecdsa,
connect_ed25519_to_ed448,
connect_ed25519_to_ed25519,
- connect_ed25519_to_rsa,
+ connect_ed25519_to_rsa_sha2,
connect_ed448_to_dsa,
connect_ed448_to_ecdsa,
connect_ed448_to_ed25519,
connect_ed448_to_ed448,
- connect_ed448_to_rsa,
- connect_rsa_to_ed25519,
- connect_rsa_to_ed448
+ connect_ed448_to_rsa_sha2
| ?tests_old % but taken from the new format directory
]).
groups() ->
[{new_format, [], ?tests_new},
- {old_format, [], ?tests_old++[{group,passphrase}]},
+ {old_format, [], [check_dsa_disabled, check_rsa_sha1_disabled | ?tests_old++[{group,passphrase}] ]},
{passphrase, [], ?tests_old},
- {option_space,[], [{group,new_format}]}
+ {option_space,[], [{group,new_format}]},
+ {pkcs8, [], [ssh_hostkey_pkcs8]},
+
+ {ssh_hostkey_fingerprint, [],
+ [ssh_hostkey_fingerprint_md5_implicit,
+ ssh_hostkey_fingerprint_md5,
+ ssh_hostkey_fingerprint_sha,
+ ssh_hostkey_fingerprint_sha256,
+ ssh_hostkey_fingerprint_sha384,
+ ssh_hostkey_fingerprint_sha512,
+ ssh_hostkey_fingerprint_list]},
+
+ {ssh_public_key_decode_encode, [],
+ [ssh_rsa_public_key, ssh_dsa_public_key, ssh_ecdsa_public_key,
+ ssh_rfc4716_rsa_comment, ssh_rfc4716_dsa_comment,
+ ssh_rfc4716_rsa_subject,
+ ssh_list_public_key,
+ ssh_known_hosts, %% ssh1_known_hosts,
+ ssh_auth_keys, %% ssh1_auth_keys,
+ ssh_openssh_key_with_comment,
+ ssh_openssh_key_long_header]}
].
+
%%%----------------------------------------------------------------
init_per_suite(Config) ->
?CHECK_CRYPTO(
@@ -144,6 +195,11 @@ init_per_group(old_format, Config) ->
[{fmt,old_format},
{key_src_dir,Dir} | Config];
+init_per_group(pkcs8, Config) ->
+ Dir = filename:join(proplists:get_value(data_dir,Config), "pkcs8"),
+ [{fmt,pkcs8},
+ {key_src_dir,Dir} | Config];
+
init_per_group(option_space, Config) ->
extend_optsL([client_opts,daemon_opts],
[{key_cb, {ssh_file, [{optimize, space}]}}],
@@ -162,6 +218,12 @@ init_per_group(passphrase, Config0) ->
{skip, "Unsupported hash"}
end;
+init_per_group(ssh_public_key_decode_encode, Config) ->
+ [{pk_data_dir,
+ filename:join([proplists:get_value(data_dir, Config),
+ "public_key"])
+ } | Config];
+
init_per_group(_, Config) ->
Config.
@@ -183,18 +245,22 @@ end_per_group(_, Config) ->
Config.
%%%----------------------------------------------------------------
-init_per_testcase(connect_rsa_to_rsa, Config0) ->
- setup_user_system_dir(rsa, rsa, Config0);
-init_per_testcase(connect_rsa_to_dsa, Config0) ->
- setup_user_system_dir(rsa, dsa, Config0);
-init_per_testcase(connect_rsa_to_ecdsa, Config0) ->
- setup_user_system_dir(rsa, ecdsa, Config0);
-init_per_testcase(connect_rsa_to_ed25519, Config0) ->
- setup_user_system_dir(rsa, ed25519, Config0);
-init_per_testcase(connect_rsa_to_ed448, Config0) ->
- setup_user_system_dir(rsa, ed448, Config0);
-init_per_testcase(connect_dsa_to_rsa, Config0) ->
- setup_user_system_dir(dsa, rsa, Config0);
+init_per_testcase(ssh_hostkey_pkcs8, Config0) ->
+ setup_user_system_dir(rsa_sha2, rsa_sha2, Config0);
+init_per_testcase(connect_rsa_sha2_to_rsa_sha2, Config0) ->
+ setup_user_system_dir(rsa_sha2, rsa_sha2, Config0);
+init_per_testcase(connect_rsa_sha1_to_dsa, Config0) ->
+ setup_user_system_dir(rsa_sha1, dsa, Config0);
+init_per_testcase(connect_rsa_sha2_to_dsa, Config0) ->
+ setup_user_system_dir(rsa_sha2, dsa, Config0);
+init_per_testcase(connect_rsa_sha2_to_ecdsa, Config0) ->
+ setup_user_system_dir(rsa_sha2, ecdsa, Config0);
+init_per_testcase(connect_rsa_sha2_to_ed25519, Config0) ->
+ setup_user_system_dir(rsa_sha2, ed25519, Config0);
+init_per_testcase(connect_rsa_sha2_to_ed448, Config0) ->
+ setup_user_system_dir(rsa_sha2, ed448, Config0);
+init_per_testcase(connect_dsa_to_rsa_sha2, Config0) ->
+ setup_user_system_dir(dsa, rsa_sha2, Config0);
init_per_testcase(connect_dsa_to_dsa, Config0) ->
setup_user_system_dir(dsa, dsa, Config0);
init_per_testcase(connect_dsa_to_ecdsa, Config0) ->
@@ -203,8 +269,8 @@ init_per_testcase(connect_dsa_to_ed25519
setup_user_system_dir(dsa, ed25519, Config0);
init_per_testcase(connect_dsa_to_ed448, Config0) ->
setup_user_system_dir(dsa, ed448, Config0);
-init_per_testcase(connect_ecdsa_to_rsa, Config0) ->
- setup_user_system_dir(ecdsa, rsa, Config0);
+init_per_testcase(connect_ecdsa_to_rsa_sha2, Config0) ->
+ setup_user_system_dir(ecdsa, rsa_sha2, Config0);
init_per_testcase(connect_ecdsa_to_dsa, Config0) ->
setup_user_system_dir(ecdsa, dsa, Config0);
init_per_testcase(connect_ecdsa_to_ecdsa, Config0) ->
@@ -213,8 +279,8 @@ init_per_testcase(connect_ecdsa_to_ed255
setup_user_system_dir(ecdsa, ed25519, Config0);
init_per_testcase(connect_ecdsa_to_ed448, Config0) ->
setup_user_system_dir(ecdsa, ed448, Config0);
-init_per_testcase(connect_ed25519_to_rsa, Config0) ->
- setup_user_system_dir(ed25519, rsa, Config0);
+init_per_testcase(connect_ed25519_to_rsa_sha2, Config0) ->
+ setup_user_system_dir(ed25519, rsa_sha2, Config0);
init_per_testcase(connect_ed25519_to_dsa, Config0) ->
setup_user_system_dir(ed25519, dsa, Config0);
init_per_testcase(connect_ed25519_to_ecdsa, Config0) ->
@@ -223,8 +289,8 @@ init_per_testcase(connect_ed25519_to_ed2
setup_user_system_dir(ed25519, ed25519, Config0);
init_per_testcase(connect_ed25519_to_ed448, Config0) ->
setup_user_system_dir(ed25519, ed448, Config0);
-init_per_testcase(connect_ed448_to_rsa, Config0) ->
- setup_user_system_dir(ed448, rsa, Config0);
+init_per_testcase(connect_ed448_to_rsa_sha2, Config0) ->
+ setup_user_system_dir(ed448, rsa_sha2, Config0);
init_per_testcase(connect_ed448_to_dsa, Config0) ->
setup_user_system_dir(ed448, dsa, Config0);
init_per_testcase(connect_ed448_to_ecdsa, Config0) ->
@@ -233,31 +299,74 @@ init_per_testcase(connect_ed448_to_ed255
setup_user_system_dir(ed448, ed25519, Config0);
init_per_testcase(connect_ed448_to_ed448, Config0) ->
setup_user_system_dir(ed448, ed448, Config0);
+
+init_per_testcase(check_dsa_disabled, Config0) ->
+ setup_default_user_system_dir(dsa, Config0);
+init_per_testcase(check_rsa_sha1_disabled, Config0) ->
+ setup_default_user_system_dir(rsa_sha1, Config0);
+
+init_per_testcase(ssh_hostkey_fingerprint_md5_implicit, Config) ->
+ init_fingerprint_testcase([md5], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_md5, Config) ->
+ init_fingerprint_testcase([md5], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_sha, Config) ->
+ init_fingerprint_testcase([sha], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_sha256, Config) ->
+ init_fingerprint_testcase([sha256], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_sha384, Config) ->
+ init_fingerprint_testcase([sha384], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_sha512, Config) ->
+ init_fingerprint_testcase([sha512], Config);
+
+init_per_testcase(ssh_hostkey_fingerprint_list , Config) ->
+ init_fingerprint_testcase([sha,md5], Config);
+
init_per_testcase(_, Config) ->
Config.
+
end_per_testcase(_, Config) ->
Config.
+%%%----
+init_fingerprint_testcase(Algs, Config0) ->
+ Hashs = proplists:get_value(hashs, crypto:supports(), []),
+ case Algs -- Hashs of
+ [] ->
+ Config = lists:keydelete(watchdog, 1, Config0),
+ Dog = ct:timetrap(?TIMEOUT),
+ [{watchdog, Dog} | Config];
+ UnsupportedAlgs ->
+ {skip,{UnsupportedAlgs,not_supported}}
+ end.
+
%%%----------------------------------------------------------------
%%% Test Cases ----------------------------------------------------
%%%----------------------------------------------------------------
-connect_rsa_to_rsa(Config) ->
+connect_rsa_sha2_to_rsa_sha2(Config) ->
try_connect(Config).
-connect_rsa_to_dsa(Config) ->
+connect_rsa_sha1_to_dsa(Config) ->
try_connect(Config).
-connect_rsa_to_ecdsa(Config) ->
+connect_rsa_sha2_to_dsa(Config) ->
+ try_connect(Config).
+
+connect_rsa_sha2_to_ecdsa(Config) ->
try_connect(Config).
-connect_rsa_to_ed25519(Config) ->
+connect_rsa_sha2_to_ed25519(Config) ->
try_connect(Config).
-connect_rsa_to_ed448(Config) ->
+connect_rsa_sha2_to_ed448(Config) ->
try_connect(Config).
-connect_dsa_to_rsa(Config) ->
+connect_dsa_to_rsa_sha2(Config) ->
try_connect(Config).
connect_dsa_to_dsa(Config) ->
@@ -272,7 +381,7 @@ connect_dsa_to_ed25519(Config) ->
connect_dsa_to_ed448(Config) ->
try_connect(Config).
-connect_ecdsa_to_rsa(Config) ->
+connect_ecdsa_to_rsa_sha2(Config) ->
try_connect(Config).
connect_ecdsa_to_dsa(Config) ->
@@ -287,7 +396,7 @@ connect_ecdsa_to_ed25519(Config) ->
connect_ecdsa_to_ed448(Config) ->
try_connect(Config).
-connect_ed25519_to_rsa(Config) ->
+connect_ed25519_to_rsa_sha2(Config) ->
try_connect(Config).
connect_ed25519_to_dsa(Config) ->
@@ -302,7 +411,7 @@ connect_ed25519_to_ed25519(Config) ->
connect_ed25519_to_ed448(Config) ->
try_connect(Config).
-connect_ed448_to_rsa(Config) ->
+connect_ed448_to_rsa_sha2(Config) ->
try_connect(Config).
connect_ed448_to_dsa(Config) ->
@@ -317,6 +426,382 @@ connect_ed448_to_ed25519(Config) ->
connect_ed448_to_ed448(Config) ->
try_connect(Config).
+%%%----------------------------------------------------------------
+check_dsa_disabled(Config) ->
+ try_connect_disabled(Config).
+
+check_rsa_sha1_disabled(Config) ->
+ try_connect_disabled(Config).
+
+
+%%%----------------------------------------------------------------
+
+%% Check of different host keys left to later
+ssh_hostkey_pkcs8(Config) ->
+ try_connect(Config).
+
+%%%----------------------------------------------------------------
+
+%% Check of different host keys left to later
+ssh_hostkey_fingerprint_md5_implicit(_Config) ->
+ Expected = "4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a",
+ Expected = ssh:hostkey_fingerprint(ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Check of different host keys left to later
+ssh_hostkey_fingerprint_md5(_Config) ->
+ Expected = "MD5:4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a",
+ Expected = ssh:hostkey_fingerprint(md5, ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Since this kind of fingerprint is not available yet on standard
+%% distros, we do like this instead. The Expected is generated with:
+%% $ openssh-7.3p1/ssh-keygen -E sha1 -lf <file>
+%% 2048 SHA1:Soammnaqg06jrm2jivMSnzQGlmk none@example.org (RSA)
+ssh_hostkey_fingerprint_sha(_Config) ->
+ Expected = "SHA1:Soammnaqg06jrm2jivMSnzQGlmk",
+ Expected = ssh:hostkey_fingerprint(sha, ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Since this kind of fingerprint is not available yet on standard
+%% distros, we do like this instead.
+ssh_hostkey_fingerprint_sha256(_Config) ->
+ Expected = "SHA256:T7F1BahkJWR7iJO8+rpzWOPbp7LZP4MlNrDExdNYOvY",
+ Expected = ssh:hostkey_fingerprint(sha256, ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Since this kind of fingerprint is not available yet on standard
+%% distros, we do like this instead.
+ssh_hostkey_fingerprint_sha384(_Config) ->
+ Expected = "SHA384:QhkLoGNI4KXdPvC//HxxSCP3uTQVADqxdajbgm+Gkx9zqz8N94HyP1JmH8C4/aEl",
+ Expected = ssh:hostkey_fingerprint(sha384, ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Since this kind of fingerprint is not available yet on standard
+%% distros, we do like this instead.
+ssh_hostkey_fingerprint_sha512(_Config) ->
+ Expected = "SHA512:ezUismvm3ADQQb6Nm0c1DwQ6ydInlJNfsnSQejFkXNmABg1Aenk9oi45CXeBOoTnlfTsGG8nFDm0smP10PBEeA",
+ Expected = ssh:hostkey_fingerprint(sha512, ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+%% Since this kind of fingerprint is not available yet on standard
+%% distros, we do like this instead.
+ssh_hostkey_fingerprint_list(_Config) ->
+ Expected = ["SHA1:Soammnaqg06jrm2jivMSnzQGlmk",
+ "MD5:4b:0b:63:de:0f:a7:3a:ab:2c:cc:2d:d1:21:37:1d:3a"],
+ Expected = ssh:hostkey_fingerprint([sha,md5], ssh_hostkey(rsa)).
+
+%%--------------------------------------------------------------------
+ssh_rsa_public_key(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+ {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_pub")),
+ [{PubKey, Attributes1}] = ssh_file:decode(RSARawSsh2, public_key),
+ [{PubKey, Attributes1}] = ssh_file:decode(RSARawSsh2, rfc4716_key),
+
+ {ok, RSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_rsa_pub")),
+ [{PubKey, Attributes2}] = ssh_file:decode(RSARawOpenSsh, public_key),
+ [{PubKey, Attributes2}] = ssh_file:decode(RSARawOpenSsh, openssh_key),
+
+ %% Can not check EncodedSSh == RSARawSsh2 and EncodedOpenSsh
+ %% = RSARawOpenSsh as line breakpoints may differ
+
+ EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
+ EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
+
+ [{PubKey, Attributes1}] =
+ ssh_file:decode(EncodedSSh, public_key),
+ [{PubKey, Attributes2}] =
+ ssh_file:decode(EncodedOpenSsh, public_key).
+
+%%--------------------------------------------------------------------
+ssh_dsa_public_key(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_pub")),
+ [{PubKey, Attributes1}] = ssh_file:decode(DSARawSsh2, public_key),
+ [{PubKey, Attributes1}] = ssh_file:decode(DSARawSsh2, rfc4716_key),
+
+ {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_pub")),
+ [{PubKey, Attributes2}] = ssh_file:decode(DSARawOpenSsh, public_key),
+ [{PubKey, Attributes2}] = ssh_file:decode(DSARawOpenSsh, openssh_key),
+
+ %% Can not check EncodedSSh == DSARawSsh2 and EncodedOpenSsh
+ %% = DSARawOpenSsh as line breakpoints may differ
+
+ EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
+ EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
+
+ [{PubKey, Attributes1}] =
+ ssh_file:decode(EncodedSSh, public_key),
+ [{PubKey, Attributes2}] =
+ ssh_file:decode(EncodedOpenSsh, public_key).
+
+%%--------------------------------------------------------------------
+ssh_ecdsa_public_key(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, ECDSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_ecdsa_pub")),
+ [{PubKey, Attributes1}] = ssh_file:decode(ECDSARawSsh2, public_key),
+ [{PubKey, Attributes1}] = ssh_file:decode(ECDSARawSsh2, rfc4716_key),
+
+ {ok, ECDSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_ecdsa_pub")),
+ [{PubKey, Attributes2}] = ssh_file:decode(ECDSARawOpenSsh, public_key),
+ [{PubKey, Attributes2}] =ssh_file:decode(ECDSARawOpenSsh, openssh_key),
+
+ %% Can not check EncodedSSh == ECDSARawSsh2 and EncodedOpenSsh
+ %% = ECDSARawOpenSsh as line breakpoints may differ
+
+ EncodedSSh = ssh_file:encode([{PubKey, Attributes1}], rfc4716_key),
+ EncodedOpenSsh = ssh_file:encode([{PubKey, Attributes2}], openssh_key),
+
+ [{PubKey, Attributes1}] =
+ ssh_file:decode(EncodedSSh, public_key),
+ [{PubKey, Attributes2}] =
+ ssh_file:decode(EncodedOpenSsh, public_key).
+
+%%--------------------------------------------------------------------
+ssh_list_public_key(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(pk_data_dir, Config),
+ {Data_ssh2, Expect_ssh2} =
+ collect_binaries_expected(DataDir, rfc4716_key,
+ ["ssh2_rsa_pub", "ssh2_rsa_comment_pub",
+ "ssh2_dsa_pub", "ssh2_dsa_comment_pub",
+ "ssh2_ecdsa_pub",
+ "ssh2_subject_pub"]),
+ {Data_openssh, Expect_openssh} =
+ collect_binaries_expected(DataDir, openssh_key,
+ ["openssh_rsa_pub", "openssh_dsa_pub", "openssh_ecdsa_pub"]),
+
+ true =
+ (chk_decode(Data_openssh, Expect_openssh, openssh_key) and
+ chk_decode(Data_ssh2, Expect_ssh2, rfc4716_key) and
+ chk_decode(Data_openssh, Expect_openssh, public_key) and
+ chk_decode(Data_ssh2, Expect_ssh2, public_key) and
+ chk_encode(Expect_openssh, openssh_key) and
+ chk_encode(Expect_ssh2, rfc4716_key)
+ ).
+
+chk_encode(Data, Type) ->
+ case ssh_file:decode(ssh_file:encode(Data,Type), Type) of
+ Data->
+ ct:log("re-encode ~p ok", [Type]),
+ true;
+ Result ->
+ ct:log("re-encode ~p FAILED~n"
+ "Got~n ~p~nExpect~n ~p~n",
+ [Type, Result, Data]),
+ false
+ end.
+
+
+chk_decode(Data, Expect, Type) ->
+ case ssh_file:decode(Data, Type) of
+ Expect ->
+ ct:log("decode ~p ok", [Type]),
+ true;
+ BadResult ->
+ ct:log("decode ~p FAILED~n"
+ "Result~n ~p~nExpect~n ~p~n"
+ "~p",
+ [Type, BadResult, Expect,
+ if
+ is_list(BadResult) ->
+ lists:foldr(fun({Key,Attrs}, Acc) ->
+ case Key of
+ #'RSAPublicKey'{} when is_list(Attrs) -> Acc;
+ {_, #'Dss-Parms'{}} when is_list(Attrs) -> Acc;
+ {#'ECPoint'{}, {namedCurve,_}} when is_list(Attrs) -> Acc;
+ _ when is_list(Attrs) -> [{bad_key,{Key,Attrs}}|Acc];
+ _ -> [{bad_attrs,{Key,Attrs}}|Acc]
+ end;
+ (Other,Acc) ->
+ [{other,Other}|Acc]
+ end, [], BadResult);
+ true ->
+ '???'
+ end]),
+ false
+ end.
+
+
+collect_binaries_expected(Dir, Type, Files) ->
+ Bins0 = [B || F <- Files,
+ {ok,B} <- [ file:read_file(filename:join(Dir,F)) ]
+ ],
+ {list_to_binary( lists:join("\n", Bins0)),
+ lists:flatten([ssh_file:decode(B,Type) || B <- Bins0])}.
+
+%%--------------------------------------------------------------------
+ssh_rfc4716_rsa_comment(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_rsa_comment_pub")),
+ [{#'RSAPublicKey'{} = PubKey, Attributes}] =
+ ssh_file:decode(RSARawSsh2, public_key),
+
+ Headers = proplists:get_value(headers, Attributes),
+
+ Value = proplists:get_value("Comment", Headers, undefined),
+ true = Value =/= undefined,
+ RSARawSsh2 = ssh_file:encode([{PubKey, Attributes}], rfc4716_key).
+
+%%--------------------------------------------------------------------
+ssh_rfc4716_dsa_comment(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, DSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_dsa_comment_pub")),
+ [{{_, #'Dss-Parms'{}} = PubKey, Attributes}] =
+ ssh_file:decode(DSARawSsh2, public_key),
+
+ Headers = proplists:get_value(headers, Attributes),
+
+ Value = proplists:get_value("Comment", Headers, undefined),
+ true = Value =/= undefined,
+
+ %% Can not check Encoded == DSARawSsh2 as line continuation breakpoints may differ
+ Encoded = ssh_file:encode([{PubKey, Attributes}], rfc4716_key),
+ [{PubKey, Attributes}] =
+ ssh_file:decode(Encoded, public_key).
+
+%%--------------------------------------------------------------------
+ssh_rfc4716_rsa_subject(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, RSARawSsh2} = file:read_file(filename:join(Datadir, "ssh2_subject_pub")),
+ [{#'RSAPublicKey'{} = PubKey, Attributes}] =
+ ssh_file:decode(RSARawSsh2, public_key),
+
+ Headers = proplists:get_value(headers, Attributes),
+
+ Value = proplists:get_value("Subject", Headers, undefined),
+ true = Value =/= undefined,
+
+ %% Can not check Encoded == RSARawSsh2 as line continuation breakpoints may differ
+ Encoded = ssh_file:encode([{PubKey, Attributes}], rfc4716_key),
+ [{PubKey, Attributes}] =
+ ssh_file:decode(Encoded, public_key).
+
+%%--------------------------------------------------------------------
+ssh_known_hosts(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "known_hosts")),
+ [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2},
+ {#'RSAPublicKey'{}, Attributes3}, {#'RSAPublicKey'{}, Attributes4}] = Decoded =
+ ssh_file:decode(SshKnownHosts, known_hosts),
+
+ Comment1 = undefined,
+ Comment2 = "foo@bar.com",
+ Comment3 = "Comment with whitespaces",
+ Comment4 = "foo@bar.com Comment with whitespaces",
+
+ Comment1 = proplists:get_value(comment, Attributes1, undefined),
+ Comment2 = proplists:get_value(comment, Attributes2),
+ Comment3 = proplists:get_value(comment, Attributes3),
+ Comment4 = proplists:get_value(comment, Attributes4),
+
+ Value1 = proplists:get_value(hostnames, Attributes1, undefined),
+ Value2 = proplists:get_value(hostnames, Attributes2, undefined),
+ true = (Value1 =/= undefined) and (Value2 =/= undefined),
+
+ Encoded = ssh_file:encode(Decoded, known_hosts),
+ Decoded = ssh_file:decode(Encoded, known_hosts).
+
+%%--------------------------------------------------------------------
+ssh1_known_hosts(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, SshKnownHosts} = file:read_file(filename:join(Datadir, "ssh1_known_hosts")),
+ [{#'RSAPublicKey'{}, Attributes1}, {#'RSAPublicKey'{}, Attributes2},{#'RSAPublicKey'{}, Attributes3}]
+ = Decoded = ssh_file:decode(SshKnownHosts, known_hosts),
+
+ Value1 = proplists:get_value(hostnames, Attributes1, undefined),
+ Value2 = proplists:get_value(hostnames, Attributes2, undefined),
+ true = (Value1 =/= undefined) and (Value2 =/= undefined),
+
+ Comment ="dhopson@VMUbuntu-DSH comment with whitespaces",
+ Comment = proplists:get_value(comment, Attributes3),
+
+ Encoded = ssh_file:encode(Decoded, known_hosts),
+ Decoded = ssh_file:decode(Encoded, known_hosts).
+
+%%--------------------------------------------------------------------
+ssh_auth_keys(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "auth_keys")),
+ [{#'RSAPublicKey'{}, Attributes1}, {{_, #'Dss-Parms'{}}, Attributes2},
+ {#'RSAPublicKey'{}, Attributes3}, {{_, #'Dss-Parms'{}}, Attributes4}
+ ] = Decoded =
+ ssh_file:decode(SshAuthKeys, auth_keys),
+
+ Value1 = proplists:get_value(options, Attributes1, undefined),
+ true = Value1 =/= undefined,
+
+ Comment1 = Comment2 = "dhopson@VMUbuntu-DSH",
+ Comment3 = Comment4 ="dhopson@VMUbuntu-DSH comment with whitespaces",
+
+ Comment1 = proplists:get_value(comment, Attributes1),
+ Comment2 = proplists:get_value(comment, Attributes2),
+ Comment3 = proplists:get_value(comment, Attributes3),
+ Comment4 = proplists:get_value(comment, Attributes4),
+
+ Encoded = ssh_file:encode(Decoded, auth_keys),
+ Decoded = ssh_file:decode(Encoded, auth_keys).
+
+%%--------------------------------------------------------------------
+ssh1_auth_keys(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, SshAuthKeys} = file:read_file(filename:join(Datadir, "ssh1_auth_keys")),
+ [{#'RSAPublicKey'{}, Attributes1},
+ {#'RSAPublicKey'{}, Attributes2}, {#'RSAPublicKey'{}, Attributes3},
+ {#'RSAPublicKey'{}, Attributes4}, {#'RSAPublicKey'{}, Attributes5}] = Decoded =
+ ssh_file:decode(SshAuthKeys, auth_keys),
+
+ Value1 = proplists:get_value(bits, Attributes2, undefined),
+ Value2 = proplists:get_value(bits, Attributes3, undefined),
+ true = (Value1 =/= undefined) and (Value2 =/= undefined),
+
+ Comment2 = Comment3 = "dhopson@VMUbuntu-DSH",
+ Comment4 = Comment5 ="dhopson@VMUbuntu-DSH comment with whitespaces",
+
+ undefined = proplists:get_value(comment, Attributes1, undefined),
+ Comment2 = proplists:get_value(comment, Attributes2),
+ Comment3 = proplists:get_value(comment, Attributes3),
+ Comment4 = proplists:get_value(comment, Attributes4),
+ Comment5 = proplists:get_value(comment, Attributes5),
+
+ Encoded = ssh_file:encode(Decoded, auth_keys),
+ Decoded = ssh_file:decode(Encoded, auth_keys).
+
+%%--------------------------------------------------------------------
+ssh_openssh_key_with_comment(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok, DSARawOpenSsh} = file:read_file(filename:join(Datadir, "openssh_dsa_with_comment_pub")),
+ [{{_, #'Dss-Parms'{}}, _}] = ssh_file:decode(DSARawOpenSsh, openssh_key).
+
+%%--------------------------------------------------------------------
+ssh_openssh_key_long_header(Config) when is_list(Config) ->
+ Datadir = proplists:get_value(pk_data_dir, Config),
+
+ {ok,RSARawOpenSsh} = file:read_file(filename:join(Datadir, "ssh_rsa_long_header_pub")),
+ [{#'RSAPublicKey'{}, _}] = Decoded = ssh_file:decode(RSARawOpenSsh, public_key),
+
+ Encoded = ssh_file:encode(Decoded, rfc4716_key),
+ Decoded = ssh_file:decode(Encoded, rfc4716_key).
+
+%%%----------------------------------------------------------------
+%%% Test case helpers
+%%%----------------------------------------------------------------
+%% Should use stored keys instead
+ssh_hostkey(rsa) ->
+ [{PKdecoded,_}] =
+ ssh_file:decode(
+ <<"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYXcYmsyJBstl4EfFYzfQJmSiUE162zvSGSoMYybShYOI6rnnyvvihfw8Aml+2gZ716F2tqG48FQ/yPZEGWNPMrCejPpJctaPWhpNdNMJ8KFXSEgr5bY2mEpa19DHmuDeXKzeJJ+X7s3fVdYc4FMk5731KIW6Huf019ZnTxbx0VKG6b1KAJBg3vpNsDxEMwQ4LFMB0JHVklOTzbxmpaeULuIxvl65A+eGeFVeo2Q+YI9UnwY1vSgmc9Azwy8Ie9Z0HpQBN5I7Uc5xnknT8V6xDhgNfXEfzsgsRdDfZLECt1WO/1gP9wkosvAGZWt5oG8pbNQWiQdFq536ck8WQD9WD none@example.org">>,
+ public_key),
+ PKdecoded.
%%%----------------------------------------------------------------
chk_known_hosts(Config) ->
@@ -367,14 +852,15 @@ chk_known_hosts(Config) ->
%%%----------------------------------------------------------------
-try_connect({skip,Reson}) ->
- {skip,Reson};
+try_connect({skip,Reason}) ->
+ {skip,Reason};
try_connect(Config) ->
SystemDir = proplists:get_value(system_dir, Config),
UserDir = proplists:get_value(user_dir, Config),
ClientOpts = proplists:get_value(client_opts, Config, []),
DaemonOpts = proplists:get_value(daemon_opts, Config, []),
+ ssh_dbg:start(fun ct:log/2), ssh_dbg:on([alg]),
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir}
| DaemonOpts]),
@@ -384,46 +870,29 @@ try_connect(Config) ->
{user_interaction, false}
| ClientOpts]),
ssh:close(C),
+ ssh_dbg:stop(),
ssh:stop_daemon(Pid).
+
+try_connect_disabled(Config) ->
+ try try_connect(Config)
+ of _ -> {fail, "non-default algorithm accepted"}
+ catch error:{badmatch,{error,"Service not available"}} -> ok
+ end.
+
%%%----------------------------------------------------------------
%%% Local ---------------------------------------------------------
%%%----------------------------------------------------------------
setup_user_system_dir(ClientAlg, ServerAlg, Config) ->
- case supported(public_keys, ClientAlg) andalso supported(public_keys, ServerAlg) of
+ case supported(public_key, ClientAlg) andalso supported(public_key, ServerAlg) of
true ->
- PrivDir = proplists:get_value(priv_dir, Config),
- KeySrcDir = proplists:get_value(key_src_dir, Config),
- Fmt = proplists:get_value(fmt, Config),
-
- System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]),
- SystemDir = filename:join(PrivDir, System),
- file:make_dir(SystemDir),
-
- User = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]),
- UserDir = filename:join(PrivDir, User),
- file:make_dir(UserDir),
-
- HostSrcFile = filename:join(KeySrcDir, file(host,ServerAlg)),
- HostDstFile = filename:join(SystemDir, file(host,ServerAlg)),
-
- UserSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)),
- UserDstFile = filename:join(UserDir, file(user,ClientAlg)),
-
- UserPubSrcFile = filename:join(KeySrcDir, file(user,ClientAlg)++".pub"),
- AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
-
try
- {ok,_} = file:copy(UserSrcFile, UserDstFile),
- {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
- {ok,_} = file:copy(HostSrcFile, HostDstFile)
+ setup_dirs(ClientAlg, ServerAlg, Config)
of
- _ ->
- ModAlgs = [{modify_algorithms,
- [{append,[{public_key,
- lists:usort([alg(ClientAlg),
- alg(ServerAlg)])}]}]}
- ],
+ {ok, {SystemDir,UserDir}} ->
+ ModAlgs = [{preferred_algorithms,
+ [{public_key, lists:usort([alg(ClientAlg), alg(ServerAlg)])}]
+ }],
[{system_dir,SystemDir},
{user_dir,UserDir}
| extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
@@ -437,38 +906,120 @@ setup_user_system_dir(ClientAlg, ServerA
{skip, unsupported_algorithm}
end.
-%%%----------------------------------------------------------------
-file(host, dsa) -> "ssh_host_dsa_key";
-file(host, ecdsa) -> "ssh_host_ecdsa_key";
-file(host, ed25519) -> "ssh_host_ed25519_key";
-file(host, ed448) -> "ssh_host_ed448_key";
-file(host, rsa) -> "ssh_host_rsa_key";
-file(user, dsa) -> "id_dsa";
-file(user, ecdsa) -> "id_ecdsa";
-file(user, ed25519) -> "id_ed25519";
-file(user, ed448) -> "id_ed448";
-file(user, rsa) -> "id_rsa".
+
+setup_default_user_system_dir(ClientAlg, Config) ->
+ ServerAlg = ecdsa,
+ case default(public_key, ClientAlg) of
+ false ->
+ case supported(public_key, ClientAlg) of
+ true ->
+ case supported(public_key, ServerAlg) of
+ true ->
+ try
+ setup_dirs(ClientAlg, ServerAlg, Config)
+ of
+ {ok, {SystemDir,UserDir}} ->
+ ModAlgs = [{modify_algorithms,
+ [{append,[{public_key,[alg(ServerAlg)]}]},
+ {rm, [{public_key,[alg(ClientAlg)|inv_algs(ClientAlg)]}]}
+ ]}],
+ [{system_dir,SystemDir},
+ {user_dir,UserDir}
+ | extend_optsL([daemon_opts,client_opts], ModAlgs, Config)]
+ catch
+ error:{badmatch,{error,enoent}}:S ->
+ ct:log("~p:~p Stack:~n~p", [?MODULE,?LINE,S]),
+ {skip, no_key_file_found}
+ end;
+ false ->
+ {skip, unsupported_server_algorithm}
+ end;
+ false ->
+ {skip, unsupported_client_algorithm}
+ end;
+ true ->
+ {fail, disabled_algorithm_present}
+ end.
+
+
+setup_dirs(ClientAlg, ServerAlg, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ KeySrcDir = proplists:get_value(key_src_dir, Config),
+ Fmt = proplists:get_value(fmt, Config),
+
+ System = lists:concat(["system_", ClientAlg, "_", ServerAlg, "_", Fmt]),
+ SystemDir = filename:join(PrivDir, System),
+ file:make_dir(SystemDir),
+
+ User = lists:concat(["user_", ClientAlg, "_", ServerAlg, "_", Fmt]),
+ UserDir = filename:join(PrivDir, User),
+ file:make_dir(UserDir),
+
+ HostSrcFile = filename:join(KeySrcDir, file(src,host,ServerAlg)),
+ HostDstFile = filename:join(SystemDir, file(dst,host,ServerAlg)),
+
+ UserSrcFile = filename:join(KeySrcDir, file(src,user,ClientAlg)),
+ UserDstFile = filename:join(UserDir, file(dst,user,ClientAlg)),
+
+ UserPubSrcFile = filename:join(KeySrcDir, file(src,user,ClientAlg)++".pub"),
+ AuthorizedKeys = filename:join(UserDir, "authorized_keys"),
+
+ ct:log("UserSrcFile = ~p~nUserDstFile = ~p", [UserSrcFile, UserDstFile]),
+ {ok,_} = file:copy(UserSrcFile, UserDstFile),
+ ct:log("UserPubSrcFile = ~p~nAuthorizedKeys = ~p", [UserPubSrcFile, AuthorizedKeys]),
+ {ok,_} = file:copy(UserPubSrcFile, AuthorizedKeys),
+ ct:log("HostSrcFile = ~p~nHostDstFile = ~p", [HostSrcFile, HostDstFile]),
+ {ok,_} = file:copy(HostSrcFile, HostDstFile),
+
+ ct:log("SystemDir = ~p~nUserDir = ~p", [SystemDir,UserDir]),
+ {ok, {SystemDir,UserDir}}.
+
+%%%----------------------------------------------------------------
+file( _, host, dsa) -> "ssh_host_dsa_key";
+file( _, host, ecdsa) -> "ssh_host_ecdsa_key";
+file( _, host, ed25519) -> "ssh_host_ed25519_key";
+file( _, host, ed448) -> "ssh_host_ed448_key";
+file( _, host, rsa_sha2)-> "ssh_host_rsa_key";
+file(src, host, rsa_sha1)-> "ssh_host_rsa_key";
+file(dst, host, rsa_sha1)-> "ssh_host_rsa_key";
+file( _, user, dsa) -> "id_dsa";
+file( _, user, ecdsa) -> "id_ecdsa";
+file( _, user, ed25519) -> "id_ed25519";
+file( _, user, ed448) -> "id_ed448";
+file( _, user, rsa_sha2)-> "id_rsa";
+file(src, user, rsa_sha1)-> "id_rsa";
+file(dst, user, rsa_sha1)-> "id_rsa".
alg(dsa) -> 'ssh-dss';
alg(ecdsa) -> 'ecdsa-sha2-nistp256';
alg(ed25519) -> 'ssh-ed25519';
alg(ed448) -> 'ssh-ed448';
-alg(rsa) -> 'ssh-rsa'.
+alg(rsa_sha2)-> 'rsa-sha2-256';
+alg(rsa_sha1)-> 'ssh-rsa'.
+inv_algs(rsa_sha1) -> algs(rsa_sha2);
+inv_algs(_) -> [].
-supported(public_keys, rsa) -> supported(public_key, 'ssh-rsa') orelse
- supported(public_key, 'rsa-sha2-256') orelse
- supported(public_key, 'rsa-sha2-521');
-supported(public_keys, dsa) -> supported(public_key, 'ssh-dss');
-supported(public_keys, ecdsa) -> supported(public_key, 'ecdsa-sha2-nistp256') orelse
- supported(public_key, 'ecdsa-sha2-nistp384') orelse
- supported(public_key, 'ecdsa-sha2-nistp521');
-supported(public_keys, ed448) -> supported(public_key, 'ssh-ed448');
-supported(public_keys, ed25519) -> supported(public_key, 'ssh-ed25519');
-supported(Type, Alg) ->
- case proplists:get_value(Type,ssh_transport:supported_algorithms()) of
- undefined ->
- lists:member(Alg, crypto:supports(Type));
- L ->
- lists:member(Alg, L)
- end.
+algs(dsa) -> ['ssh-dss'];
+algs(ecdsa) -> ['ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-521'];
+algs(ed25519) -> ['ssh-ed25519'];
+algs(ed448) -> ['ssh-ed448'];
+algs(rsa_sha2)-> ['rsa-sha2-256', 'rsa-sha2-384', 'rsa-sha2-512'];
+algs(rsa_sha1)-> ['ssh-rsa'];
+algs(A) -> [A].
+
+
+
+default(Type, Alg) -> listed(algs(Alg), ssh_transport:default_algorithms(Type)).
+
+supported(Type, Alg) -> listed(algs(Alg),
+ try
+ ssh_transport:supported_algorithms(Type)
+ catch
+ error:function_clause -> crypto:supports(Type)
+ end).
+
+listed(As, L) -> lists:any(fun(A) -> lists:member(A,L) end,
+ As).
+
+
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_relay.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_relay.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_relay.erl
@@ -313,7 +313,7 @@ listen(Parent, LSock) ->
do_listen(Parent, LSock).
do_listen(Parent, LSock) ->
- %% So annoying there is no select-like sematic for this
+ %% So annoying there is no select-like semantics for this
case gen_tcp:accept(LSock, ?ACCEPT_TMO) of
{ok, Sock} ->
Parent ! {accept, Sock},
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_sftpd_erlclient_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2017. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2021. 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.
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_test_lib.hrl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_test_lib.hrl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_test_lib.hrl
@@ -1,10 +1,18 @@
+
+%%-------------------------------------------------------------------------
+%% Default system port
+%%-------------------------------------------------------------------------
+-ifndef(SSH_DEFAULT_PORT).
+-define(SSH_DEFAULT_PORT, 22).
+-endif.
+
%%-------------------------------------------------------------------------
%% Timeout time in ms
%%-------------------------------------------------------------------------
-define(TIMEOUT, 27000).
%%-------------------------------------------------------------------------
-%% Check for usable crypt
+%% Check for usable crypto
%%-------------------------------------------------------------------------
-define(CHECK_CRYPTO(UsersInitCode),
try
@@ -24,35 +32,35 @@
%%-------------------------------------------------------------------------
-define(wait_match(Pattern, Guard, FunctionCall, Bind, Timeout, Ntries),
Bind =
- (fun() ->
+ (fun() ->
F = fun(N, F1) ->
case FunctionCall of
Pattern when Guard -> Bind;
_ when N>0 ->
- ct:log("Must sleep ~p ms at ~p:~p",[Timeout,?MODULE,?LINE]),
+ ct:log("Must sleep ~p ms at ~p:~p",
+ [Timeout,?MODULE,?LINE]),
timer:sleep(Timeout),
F1(N-1, F1);
- Other ->
- ct:fail("Unexpected ~p:~p ~p",[?MODULE,?LINE,Other])
+ Other ->
+ ct:fail("Unexpected ~p:~p ~p",
+ [?MODULE,?LINE,Other])
end
end,
F(Ntries, F)
end)()
).
-
-define(wait_match(Pattern, FunctionCall, Bind, Timeout, Ntries),
?wait_match(Pattern, true, FunctionCall, Bind, Timeout, Ntries)).
-
--define(wait_match(Pattern, FunctionCall, Timeout, Ntries), ?wait_match(Pattern, FunctionCall, ok, Timeout, Ntries)).
-
--define(wait_match(Pattern, FunctionCall, Bind), ?wait_match(Pattern, FunctionCall, Bind, 500, 10) ).
-
--define(wait_match(Pattern, FunctionCall), ?wait_match(Pattern, FunctionCall, ok) ).
+-define(wait_match(Pattern, FunctionCall, Timeout, Ntries),
+ ?wait_match(Pattern, FunctionCall, ok, Timeout, Ntries)).
+-define(wait_match(Pattern, FunctionCall, Bind),
+ ?wait_match(Pattern, FunctionCall, Bind, 500, 10)).
+-define(wait_match(Pattern, FunctionCall),
+ ?wait_match(Pattern, FunctionCall, ok)).
%%-------------------------------------------------------------------------
%% Write file into log
%%-------------------------------------------------------------------------
-
-define(ct_log_show_file(File),
(fun(File__) ->
{ok,Contents__} = file:read_file(File__),
Index: otp-OTP-23.3.4.19/lib/ssh/test/ssh_upgrade_SUITE.erl
===================================================================
--- otp-OTP-23.3.4.19.orig/lib/ssh/test/ssh_upgrade_SUITE.erl
+++ otp-OTP-23.3.4.19/lib/ssh/test/ssh_upgrade_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2014-2020. All Rights Reserved.
+%% Copyright Ericsson AB 2014-2022. 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.
@@ -98,7 +98,7 @@ minor_upgrade(Config) when is_list(Confi
%%%
%%%----------------------------------------------------------------
-%%% Initialyze system before upgrade test starts.
+%%% Initialize system before upgrade test starts.
%%% Called by ct_release_test:upgrade/4
upgrade_init(CTData, State) ->
{ok, AppUp={_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssh),