1 <?php
2 
3 /**
4  * Pure-PHP implementation of SSHv2.
5  *
6  * PHP version 5
7  *
8  * Here are some examples of how to use this library:
9  * <code>
10  * <?php
11  *    include 'vendor/autoload.php';
12  *
13  *    $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
14  *    if (!$ssh->login('username', 'password')) {
15  *        exit('Login Failed');
16  *    }
17  *
18  *    echo $ssh->exec('pwd');
19  *    echo $ssh->exec('ls -la');
20  * ?>
21  * </code>
22  *
23  * <code>
24  * <?php
25  *    include 'vendor/autoload.php';
26  *
27  *    $key = \phpseclib3\Crypt\PublicKeyLoader::load('...', '(optional) password');
28  *
29  *    $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
30  *    if (!$ssh->login('username', $key)) {
31  *        exit('Login Failed');
32  *    }
33  *
34  *    echo $ssh->read('username@username:~$');
35  *    $ssh->write("ls -la\n");
36  *    echo $ssh->read('username@username:~$');
37  * ?>
38  * </code>
39  *
40  * @author    Jim Wigginton <terrafrost@php.net>
41  * @copyright 2007 Jim Wigginton
42  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
43  * @link      http://phpseclib.sourceforge.net
44  */
45 
46 namespace phpseclib3\Net;
47 
48 use phpseclib3\Common\Functions\Strings;
49 use phpseclib3\Crypt\Blowfish;
50 use phpseclib3\Crypt\ChaCha20;
51 use phpseclib3\Crypt\Common\AsymmetricKey;
52 use phpseclib3\Crypt\Common\PrivateKey;
53 use phpseclib3\Crypt\Common\PublicKey;
54 use phpseclib3\Crypt\Common\SymmetricKey;
55 use phpseclib3\Crypt\DH;
56 use phpseclib3\Crypt\DSA;
57 use phpseclib3\Crypt\EC;
58 use phpseclib3\Crypt\Hash;
59 use phpseclib3\Crypt\Random;
60 use phpseclib3\Crypt\RC4;
61 use phpseclib3\Crypt\Rijndael;
62 use phpseclib3\Crypt\RSA;
63 use phpseclib3\Crypt\TripleDES; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
64 use phpseclib3\Crypt\Twofish;
65 use phpseclib3\Exception\ConnectionClosedException;
66 use phpseclib3\Exception\InsufficientSetupException;
67 use phpseclib3\Exception\InvalidPacketLengthException;
68 use phpseclib3\Exception\NoSupportedAlgorithmsException;
69 use phpseclib3\Exception\TimeoutException;
70 use phpseclib3\Exception\UnableToConnectException;
71 use phpseclib3\Exception\UnsupportedAlgorithmException;
72 use phpseclib3\Exception\UnsupportedCurveException;
73 use phpseclib3\Math\BigInteger;
74 use phpseclib3\System\SSH\Agent;
75 
76 /**
77  * Pure-PHP implementation of SSHv2.
78  *
79  * @author  Jim Wigginton <terrafrost@php.net>
80  */
81 class SSH2
82 {
83     /**#@+
84      * Compression Types
85      *
86      */
87     /**
88      * No compression
89      */
90     const NET_SSH2_COMPRESSION_NONE = 1;
91     /**
92      * zlib compression
93      */
94     const NET_SSH2_COMPRESSION_ZLIB = 2;
95     /**
96      * zlib@openssh.com
97      */
98     const NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH = 3;
99     /**#@-*/
100 
101     // Execution Bitmap Masks
102     const MASK_CONSTRUCTOR   = 0x00000001;
103     const MASK_CONNECTED     = 0x00000002;
104     const MASK_LOGIN_REQ     = 0x00000004;
105     const MASK_LOGIN         = 0x00000008;
106     const MASK_SHELL         = 0x00000010;
107     const MASK_DISCONNECT    = 0x00000020;
108 
109     /*
110      * Channel constants
111      *
112      * RFC4254 refers not to client and server channels but rather to sender and recipient channels.  we don't refer
113      * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with
114      * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a
115      * recipient channel.  at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel
116      * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snippet:
117      *     The 'recipient channel' is the channel number given in the original
118      *     open request, and 'sender channel' is the channel number allocated by
119      *     the other side.
120      *
121      * @see \phpseclib3\Net\SSH2::send_channel_packet()
122      * @see \phpseclib3\Net\SSH2::get_channel_packet()
123      */
124     const CHANNEL_EXEC          = 1; // PuTTy uses 0x100
125     const CHANNEL_SHELL         = 2;
126     const CHANNEL_SUBSYSTEM     = 3;
127     const CHANNEL_AGENT_FORWARD = 4;
128     const CHANNEL_KEEP_ALIVE    = 5;
129 
130     /**
131      * Returns the message numbers
132      *
133      * @see \phpseclib3\Net\SSH2::getLog()
134      */
135     const LOG_SIMPLE = 1;
136     /**
137      * Returns the message content
138      *
139      * @see \phpseclib3\Net\SSH2::getLog()
140      */
141     const LOG_COMPLEX = 2;
142     /**
143      * Outputs the content real-time
144      */
145     const LOG_REALTIME = 3;
146     /**
147      * Dumps the content real-time to a file
148      */
149     const LOG_REALTIME_FILE = 4;
150     /**
151      * Outputs the message numbers real-time
152      */
153     const LOG_SIMPLE_REALTIME = 5;
154     /*
155      * Dumps the message numbers real-time
156      */
157     const LOG_REALTIME_SIMPLE = 5;
158     /**
159      * Make sure that the log never gets larger than this
160      *
161      * @see \phpseclib3\Net\SSH2::getLog()
162      */
163     const LOG_MAX_SIZE = 1048576; // 1024 * 1024
164 
165     /**
166      * Returns when a string matching $expect exactly is found
167      *
168      * @see \phpseclib3\Net\SSH2::read()
169      */
170     const READ_SIMPLE = 1;
171     /**
172      * Returns when a string matching the regular expression $expect is found
173      *
174      * @see \phpseclib3\Net\SSH2::read()
175      */
176     const READ_REGEX = 2;
177     /**
178      * Returns whenever a data packet is received.
179      *
180      * Some data packets may only contain a single character so it may be necessary
181      * to call read() multiple times when using this option
182      *
183      * @see \phpseclib3\Net\SSH2::read()
184      */
185     const READ_NEXT = 3;
186 
187     /**
188      * The SSH identifier
189      *
190      * @var string
191      */
192     private $identifier;
193 
194     /**
195      * The Socket Object
196      *
197      * @var resource|closed-resource|null
198      */
199     public $fsock;
200 
201     /**
202      * Execution Bitmap
203      *
204      * The bits that are set represent functions that have been called already.  This is used to determine
205      * if a requisite function has been successfully executed.  If not, an error should be thrown.
206      *
207      * @var int
208      */
209     protected $bitmap = 0;
210 
211     /**
212      * Error information
213      *
214      * @see self::getErrors()
215      * @see self::getLastError()
216      * @var array
217      */
218     private $errors = [];
219 
220     /**
221      * Server Identifier
222      *
223      * @see self::getServerIdentification()
224      * @var string|false
225      */
226     protected $server_identifier = false;
227 
228     /**
229      * Key Exchange Algorithms
230      *
231      * @see self::getKexAlgorithims()
232      * @var array|false
233      */
234     private $kex_algorithms = false;
235 
236     /**
237      * Key Exchange Algorithm
238      *
239      * @see self::getMethodsNegotiated()
240      * @var string|false
241      */
242     private $kex_algorithm = false;
243 
244     /**
245      * Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
246      *
247      * @see self::_key_exchange()
248      * @var int
249      */
250     private $kex_dh_group_size_min = 1536;
251 
252     /**
253      * Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
254      *
255      * @see self::_key_exchange()
256      * @var int
257      */
258     private $kex_dh_group_size_preferred = 2048;
259 
260     /**
261      * Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
262      *
263      * @see self::_key_exchange()
264      * @var int
265      */
266     private $kex_dh_group_size_max = 4096;
267 
268     /**
269      * Server Host Key Algorithms
270      *
271      * @see self::getServerHostKeyAlgorithms()
272      * @var array|false
273      */
274     private $server_host_key_algorithms = false;
275 
276     /**
277      * Supported Private Key Algorithms
278      *
279      * In theory this should be the same as the Server Host Key Algorithms but, in practice,
280      * some servers (eg. Azure) will support rsa-sha2-512 as a server host key algorithm but
281      * not a private key algorithm
282      *
283      * @see self::privatekey_login()
284      * @var array|false
285      */
286     private $supported_private_key_algorithms = false;
287 
288     /**
289      * Encryption Algorithms: Client to Server
290      *
291      * @see self::getEncryptionAlgorithmsClient2Server()
292      * @var array|false
293      */
294     private $encryption_algorithms_client_to_server = false;
295 
296     /**
297      * Encryption Algorithms: Server to Client
298      *
299      * @see self::getEncryptionAlgorithmsServer2Client()
300      * @var array|false
301      */
302     private $encryption_algorithms_server_to_client = false;
303 
304     /**
305      * MAC Algorithms: Client to Server
306      *
307      * @see self::getMACAlgorithmsClient2Server()
308      * @var array|false
309      */
310     private $mac_algorithms_client_to_server = false;
311 
312     /**
313      * MAC Algorithms: Server to Client
314      *
315      * @see self::getMACAlgorithmsServer2Client()
316      * @var array|false
317      */
318     private $mac_algorithms_server_to_client = false;
319 
320     /**
321      * Compression Algorithms: Client to Server
322      *
323      * @see self::getCompressionAlgorithmsClient2Server()
324      * @var array|false
325      */
326     private $compression_algorithms_client_to_server = false;
327 
328     /**
329      * Compression Algorithms: Server to Client
330      *
331      * @see self::getCompressionAlgorithmsServer2Client()
332      * @var array|false
333      */
334     private $compression_algorithms_server_to_client = false;
335 
336     /**
337      * Languages: Server to Client
338      *
339      * @see self::getLanguagesServer2Client()
340      * @var array|false
341      */
342     private $languages_server_to_client = false;
343 
344     /**
345      * Languages: Client to Server
346      *
347      * @see self::getLanguagesClient2Server()
348      * @var array|false
349      */
350     private $languages_client_to_server = false;
351 
352     /**
353      * Preferred Algorithms
354      *
355      * @see self::setPreferredAlgorithms()
356      * @var array
357      */
358     private $preferred = [];
359 
360     /**
361      * Block Size for Server to Client Encryption
362      *
363      * "Note that the length of the concatenation of 'packet_length',
364      *  'padding_length', 'payload', and 'random padding' MUST be a multiple
365      *  of the cipher block size or 8, whichever is larger.  This constraint
366      *  MUST be enforced, even when using stream ciphers."
367      *
368      *  -- http://tools.ietf.org/html/rfc4253#section-6
369      *
370      * @see self::__construct()
371      * @see self::_send_binary_packet()
372      * @var int
373      */
374     private $encrypt_block_size = 8;
375 
376     /**
377      * Block Size for Client to Server Encryption
378      *
379      * @see self::__construct()
380      * @see self::_get_binary_packet()
381      * @var int
382      */
383     private $decrypt_block_size = 8;
384 
385     /**
386      * Server to Client Encryption Object
387      *
388      * @see self::_get_binary_packet()
389      * @var SymmetricKey|false
390      */
391     private $decrypt = false;
392 
393     /**
394      * Decryption Algorithm Name
395      *
396      * @var string|null
397      */
398     private $decryptName;
399 
400     /**
401      * Decryption Invocation Counter
402      *
403      * Used by GCM
404      *
405      * @var string|null
406      */
407     private $decryptInvocationCounter;
408 
409     /**
410      * Fixed Part of Nonce
411      *
412      * Used by GCM
413      *
414      * @var string|null
415      */
416     private $decryptFixedPart;
417 
418     /**
419      * Server to Client Length Encryption Object
420      *
421      * @see self::_get_binary_packet()
422      * @var object
423      */
424     private $lengthDecrypt = false;
425 
426     /**
427      * Client to Server Encryption Object
428      *
429      * @see self::_send_binary_packet()
430      * @var SymmetricKey|false
431      */
432     private $encrypt = false;
433 
434     /**
435      * Encryption Algorithm Name
436      *
437      * @var string|null
438      */
439     private $encryptName;
440 
441     /**
442      * Encryption Invocation Counter
443      *
444      * Used by GCM
445      *
446      * @var string|null
447      */
448     private $encryptInvocationCounter;
449 
450     /**
451      * Fixed Part of Nonce
452      *
453      * Used by GCM
454      *
455      * @var string|null
456      */
457     private $encryptFixedPart;
458 
459     /**
460      * Client to Server Length Encryption Object
461      *
462      * @see self::_send_binary_packet()
463      * @var object
464      */
465     private $lengthEncrypt = false;
466 
467     /**
468      * Client to Server HMAC Object
469      *
470      * @see self::_send_binary_packet()
471      * @var object
472      */
473     private $hmac_create = false;
474 
475     /**
476      * Client to Server HMAC Name
477      *
478      * @var string|false
479      */
480     private $hmac_create_name;
481 
482     /**
483      * Client to Server ETM
484      *
485      * @var int|false
486      */
487     private $hmac_create_etm;
488 
489     /**
490      * Server to Client HMAC Object
491      *
492      * @see self::_get_binary_packet()
493      * @var object
494      */
495     private $hmac_check = false;
496 
497     /**
498      * Server to Client HMAC Name
499      *
500      * @var string|false
501      */
502     private $hmac_check_name;
503 
504     /**
505      * Server to Client ETM
506      *
507      * @var int|false
508      */
509     private $hmac_check_etm;
510 
511     /**
512      * Size of server to client HMAC
513      *
514      * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read.
515      * For the client to server side, the HMAC object will make the HMAC as long as it needs to be.  All we need to do is
516      * append it.
517      *
518      * @see self::_get_binary_packet()
519      * @var int
520      */
521     private $hmac_size = false;
522 
523     /**
524      * Server Public Host Key
525      *
526      * @see self::getServerPublicHostKey()
527      * @var string
528      */
529     private $server_public_host_key;
530 
531     /**
532      * Session identifier
533      *
534      * "The exchange hash H from the first key exchange is additionally
535      *  used as the session identifier, which is a unique identifier for
536      *  this connection."
537      *
538      *  -- http://tools.ietf.org/html/rfc4253#section-7.2
539      *
540      * @see self::_key_exchange()
541      * @var string
542      */
543     private $session_id = false;
544 
545     /**
546      * Exchange hash
547      *
548      * The current exchange hash
549      *
550      * @see self::_key_exchange()
551      * @var string
552      */
553     private $exchange_hash = false;
554 
555     /**
556      * Message Numbers
557      *
558      * @see self::__construct()
559      * @var array
560      * @access private
561      */
562     private static $message_numbers = [];
563 
564     /**
565      * Disconnection Message 'reason codes' defined in RFC4253
566      *
567      * @see self::__construct()
568      * @var array
569      * @access private
570      */
571     private static $disconnect_reasons = [];
572 
573     /**
574      * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
575      *
576      * @see self::__construct()
577      * @var array
578      * @access private
579      */
580     private static $channel_open_failure_reasons = [];
581 
582     /**
583      * Terminal Modes
584      *
585      * @link http://tools.ietf.org/html/rfc4254#section-8
586      * @see self::__construct()
587      * @var array
588      * @access private
589      */
590     private static $terminal_modes = [];
591 
592     /**
593      * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
594      *
595      * @link http://tools.ietf.org/html/rfc4254#section-5.2
596      * @see self::__construct()
597      * @var array
598      * @access private
599      */
600     private static $channel_extended_data_type_codes = [];
601 
602     /**
603      * Send Sequence Number
604      *
605      * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
606      *
607      * @see self::_send_binary_packet()
608      * @var int
609      */
610     private $send_seq_no = 0;
611 
612     /**
613      * Get Sequence Number
614      *
615      * See 'Section 6.4.  Data Integrity' of rfc4253 for more info.
616      *
617      * @see self::_get_binary_packet()
618      * @var int
619      */
620     private $get_seq_no = 0;
621 
622     /**
623      * Server Channels
624      *
625      * Maps client channels to server channels
626      *
627      * @see self::get_channel_packet()
628      * @see self::exec()
629      * @var array
630      */
631     protected $server_channels = [];
632 
633     /**
634      * Channel Read Buffers
635      *
636      * If a client requests a packet from one channel but receives two packets from another those packets should
637      * be placed in a buffer
638      *
639      * @see self::get_channel_packet()
640      * @see self::exec()
641      * @var array
642      */
643     private $channel_buffers = [];
644 
645     /**
646      * Channel Write Buffers
647      *
648      * If a client sends a packet and receives a timeout error mid-transmission, buffer the data written so it
649      * can be de-duplicated upon resuming write
650      *
651      * @see self::send_channel_packet()
652      * @var array
653      */
654     private $channel_buffers_write = [];
655 
656     /**
657      * Channel Status
658      *
659      * Contains the type of the last sent message
660      *
661      * @see self::get_channel_packet()
662      * @var array
663      */
664     protected $channel_status = [];
665 
666     /**
667      * The identifier of the interactive channel which was opened most recently
668      *
669      * @see self::getInteractiveChannelId()
670      * @var int
671      */
672     private $channel_id_last_interactive = 0;
673 
674     /**
675      * Packet Size
676      *
677      * Maximum packet size indexed by channel
678      *
679      * @see self::send_channel_packet()
680      * @var array
681      */
682     private $packet_size_client_to_server = [];
683 
684     /**
685      * Message Number Log
686      *
687      * @see self::getLog()
688      * @var array
689      */
690     private $message_number_log = [];
691 
692     /**
693      * Message Log
694      *
695      * @see self::getLog()
696      * @var array
697      */
698     private $message_log = [];
699 
700     /**
701      * The Window Size
702      *
703      * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB)
704      *
705      * @var int
706      * @see self::send_channel_packet()
707      * @see self::exec()
708      */
709     protected $window_size = 0x7FFFFFFF;
710 
711     /**
712      * What we resize the window to
713      *
714      * When PuTTY resizes the window it doesn't add an additional 0x7FFFFFFF bytes - it adds 0x40000000 bytes.
715      * Some SFTP clients (GoAnywhere) don't support adding 0x7FFFFFFF to the window size after the fact so
716      * we'll just do what PuTTY does
717      *
718      * @var int
719      * @see self::_send_channel_packet()
720      * @see self::exec()
721      */
722     private $window_resize = 0x40000000;
723 
724     /**
725      * Window size, server to client
726      *
727      * Window size indexed by channel
728      *
729      * @see self::send_channel_packet()
730      * @var array
731      */
732     protected $window_size_server_to_client = [];
733 
734     /**
735      * Window size, client to server
736      *
737      * Window size indexed by channel
738      *
739      * @see self::get_channel_packet()
740      * @var array
741      */
742     private $window_size_client_to_server = [];
743 
744     /**
745      * Server signature
746      *
747      * Verified against $this->session_id
748      *
749      * @see self::getServerPublicHostKey()
750      * @var string
751      */
752     private $signature = '';
753 
754     /**
755      * Server signature format
756      *
757      * ssh-rsa or ssh-dss.
758      *
759      * @see self::getServerPublicHostKey()
760      * @var string
761      */
762     private $signature_format = '';
763 
764     /**
765      * Interactive Buffer
766      *
767      * @see self::read()
768      * @var string
769      */
770     private $interactiveBuffer = '';
771 
772     /**
773      * Current log size
774      *
775      * Should never exceed self::LOG_MAX_SIZE
776      *
777      * @see self::_send_binary_packet()
778      * @see self::_get_binary_packet()
779      * @var int
780      */
781     private $log_size;
782 
783     /**
784      * Timeout
785      *
786      * @see self::setTimeout()
787      */
788     protected $timeout;
789 
790     /**
791      * Current Timeout
792      *
793      * @see self::get_channel_packet()
794      */
795     protected $curTimeout;
796 
797     /**
798      * Keep Alive Interval
799      *
800      * @see self::setKeepAlive()
801      */
802     private $keepAlive;
803 
804     /**
805      * Real-time log file pointer
806      *
807      * @see self::_append_log()
808      * @var resource|closed-resource
809      */
810     private $realtime_log_file;
811 
812     /**
813      * Real-time log file size
814      *
815      * @see self::_append_log()
816      * @var int
817      */
818     private $realtime_log_size;
819 
820     /**
821      * Has the signature been validated?
822      *
823      * @see self::getServerPublicHostKey()
824      * @var bool
825      */
826     private $signature_validated = false;
827 
828     /**
829      * Real-time log file wrap boolean
830      *
831      * @see self::_append_log()
832      * @var bool
833      */
834     private $realtime_log_wrap;
835 
836     /**
837      * Flag to suppress stderr from output
838      *
839      * @see self::enableQuietMode()
840      */
841     private $quiet_mode = false;
842 
843     /**
844      * Time of last read/write network activity
845      *
846      * @var float
847      */
848     private $last_packet = null;
849 
850     /**
851      * Exit status returned from ssh if any
852      *
853      * @var int
854      */
855     private $exit_status;
856 
857     /**
858      * Flag to request a PTY when using exec()
859      *
860      * @var bool
861      * @see self::enablePTY()
862      */
863     private $request_pty = false;
864 
865     /**
866      * Contents of stdError
867      *
868      * @var string
869      */
870     private $stdErrorLog;
871 
872     /**
873      * The Last Interactive Response
874      *
875      * @see self::_keyboard_interactive_process()
876      * @var string
877      */
878     private $last_interactive_response = '';
879 
880     /**
881      * Keyboard Interactive Request / Responses
882      *
883      * @see self::_keyboard_interactive_process()
884      * @var array
885      */
886     private $keyboard_requests_responses = [];
887 
888     /**
889      * Banner Message
890      *
891      * Quoting from the RFC, "in some jurisdictions, sending a warning message before
892      * authentication may be relevant for getting legal protection."
893      *
894      * @see self::_filter()
895      * @see self::getBannerMessage()
896      * @var string
897      */
898     private $banner_message = '';
899 
900     /**
901      * Did read() timeout or return normally?
902      *
903      * @see self::isTimeout()
904      * @var bool
905      */
906     protected $is_timeout = false;
907 
908     /**
909      * Log Boundary
910      *
911      * @see self::_format_log()
912      * @var string
913      */
914     private $log_boundary = ':';
915 
916     /**
917      * Log Long Width
918      *
919      * @see self::_format_log()
920      * @var int
921      */
922     private $log_long_width = 65;
923 
924     /**
925      * Log Short Width
926      *
927      * @see self::_format_log()
928      * @var int
929      */
930     private $log_short_width = 16;
931 
932     /**
933      * Hostname
934      *
935      * @see self::__construct()
936      * @see self::_connect()
937      * @var string
938      */
939     private $host;
940 
941     /**
942      * Port Number
943      *
944      * @see self::__construct()
945      * @see self::_connect()
946      * @var int
947      */
948     private $port;
949 
950     /**
951      * Number of columns for terminal window size
952      *
953      * @see self::getWindowColumns()
954      * @see self::setWindowColumns()
955      * @see self::setWindowSize()
956      * @var int
957      */
958     private $windowColumns = 80;
959 
960     /**
961      * Number of columns for terminal window size
962      *
963      * @see self::getWindowRows()
964      * @see self::setWindowRows()
965      * @see self::setWindowSize()
966      * @var int
967      */
968     private $windowRows = 24;
969 
970     /**
971      * Crypto Engine
972      *
973      * @see self::setCryptoEngine()
974      * @see self::_key_exchange()
975      * @var int
976      */
977     private static $crypto_engine = false;
978 
979     /**
980      * A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
981      *
982      * @var Agent
983      */
984     private $agent;
985 
986     /**
987      * Connection storage to replicates ssh2 extension functionality:
988      * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples}
989      *
990      * @var array<string, SSH2|\WeakReference<SSH2>>
991      */
992     private static $connections;
993 
994     /**
995      * Send the identification string first?
996      *
997      * @var bool
998      */
999     private $send_id_string_first = true;
1000 
1001     /**
1002      * Send the key exchange initiation packet first?
1003      *
1004      * @var bool
1005      */
1006     private $send_kex_first = true;
1007 
1008     /**
1009      * Some versions of OpenSSH incorrectly calculate the key size
1010      *
1011      * @var bool
1012      */
1013     private $bad_key_size_fix = false;
1014 
1015     /**
1016      * Should we try to re-connect to re-establish keys?
1017      *
1018      * @var bool
1019      */
1020     private $login_credentials_finalized = false;
1021 
1022     /**
1023      * Binary Packet Buffer
1024      *
1025      * @var object|null
1026      */
1027     private $binary_packet_buffer = null;
1028 
1029     /**
1030      * Preferred Signature Format
1031      *
1032      * @var string|false
1033      */
1034     protected $preferred_signature_format = false;
1035 
1036     /**
1037      * Authentication Credentials
1038      *
1039      * @var array
1040      */
1041     protected $auth = [];
1042 
1043     /**
1044      * Terminal
1045      *
1046      * @var string
1047      */
1048     private $term = 'vt100';
1049 
1050     /**
1051      * The authentication methods that may productively continue authentication.
1052      *
1053      * @see https://tools.ietf.org/html/rfc4252#section-5.1
1054      * @var array|null
1055      */
1056     private $auth_methods_to_continue = null;
1057 
1058     /**
1059      * Compression method
1060      *
1061      * @var int
1062      */
1063     private $compress = self::NET_SSH2_COMPRESSION_NONE;
1064 
1065     /**
1066      * Decompression method
1067      *
1068      * @var int
1069      */
1070     private $decompress = self::NET_SSH2_COMPRESSION_NONE;
1071 
1072     /**
1073      * Compression context
1074      *
1075      * @var resource|false|null
1076      */
1077     private $compress_context;
1078 
1079     /**
1080      * Decompression context
1081      *
1082      * @var resource|object
1083      */
1084     private $decompress_context;
1085 
1086     /**
1087      * Regenerate Compression Context
1088      *
1089      * @var bool
1090      */
1091     private $regenerate_compression_context = false;
1092 
1093     /**
1094      * Regenerate Decompression Context
1095      *
1096      * @var bool
1097      */
1098     private $regenerate_decompression_context = false;
1099 
1100     /**
1101      * Smart multi-factor authentication flag
1102      *
1103      * @var bool
1104      */
1105     private $smartMFA = true;
1106 
1107     /**
1108      * How many channels are currently opened
1109      *
1110      * @var int
1111      */
1112     private $channelCount = 0;
1113 
1114     /**
1115      * Does the server support multiple channels? If not then error out
1116      * when multiple channels are attempted to be opened
1117      *
1118      * @var bool
1119      */
1120     private $errorOnMultipleChannels;
1121 
1122     /**
1123      * Bytes Transferred Since Last Key Exchange
1124      *
1125      * Includes outbound and inbound totals
1126      *
1127      * @var int
1128      */
1129     private $bytesTransferredSinceLastKEX = 0;
1130 
1131     /**
1132      * After how many transferred byte should phpseclib initiate a key re-exchange?
1133      *
1134      * @var int
1135      */
1136     private $doKeyReexchangeAfterXBytes = 1024 * 1024 * 1024;
1137 
1138     /**
1139      * Has a key re-exchange been initialized?
1140      *
1141      * @var bool
1142      * @access private
1143      */
1144     private $keyExchangeInProgress = false;
1145 
1146     /**
1147      * KEX Buffer
1148      *
1149      * If we're in the middle of a key exchange we want to buffer any additional packets we get until
1150      * the key exchange is over
1151      *
1152      * @see self::_get_binary_packet()
1153      * @see self::_key_exchange()
1154      * @see self::exec()
1155      * @var array
1156      * @access private
1157      */
1158     private $kex_buffer = [];
1159 
1160     /**
1161      * Strict KEX Flag
1162      *
1163      * If kex-strict-s-v00@openssh.com is present in the first KEX packet it need not
1164      * be present in subsequent packet
1165      *
1166      * @see self::_key_exchange()
1167      * @see self::exec()
1168      * @var array
1169      * @access private
1170      */
1171     private $strict_kex_flag = false;
1172 
1173     /**
1174      * Default Constructor.
1175      *
1176      * $host can either be a string, representing the host, or a stream resource.
1177      * If $host is a stream resource then $port doesn't do anything, altho $timeout
1178      * still will be used
1179      *
1180      * @param mixed $host
1181      * @param int $port
1182      * @param int $timeout
1183      * @see self::login()
1184      */
1185     public function __construct($host, $port = 22, $timeout = 10)
1186     {
1187         if (empty(self::$message_numbers)) {
1188             self::$message_numbers = [
1189                 1 => 'NET_SSH2_MSG_DISCONNECT',
1190                 2 => 'NET_SSH2_MSG_IGNORE',
1191                 3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
1192                 4 => 'NET_SSH2_MSG_DEBUG',
1193                 5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
1194                 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
1195                 7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
1196                 20 => 'NET_SSH2_MSG_KEXINIT',
1197                 21 => 'NET_SSH2_MSG_NEWKEYS',
1198                 30 => 'NET_SSH2_MSG_KEXDH_INIT',
1199                 31 => 'NET_SSH2_MSG_KEXDH_REPLY',
1200                 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
1201                 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
1202                 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
1203                 53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
1204 
1205                 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
1206                 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
1207                 82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
1208                 90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
1209                 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
1210                 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
1211                 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
1212                 94 => 'NET_SSH2_MSG_CHANNEL_DATA',
1213                 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
1214                 96 => 'NET_SSH2_MSG_CHANNEL_EOF',
1215                 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
1216                 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
1217                 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
1218                 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
1219             ];
1220             self::$disconnect_reasons = [
1221                 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
1222                 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
1223                 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
1224                 4 => 'NET_SSH2_DISCONNECT_RESERVED',
1225                 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
1226                 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
1227                 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
1228                 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
1229                 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
1230                 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
1231                 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
1232                 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
1233                 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
1234                 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
1235                 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
1236             ];
1237             self::$channel_open_failure_reasons = [
1238                 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
1239             ];
1240             self::$terminal_modes = [
1241                 0 => 'NET_SSH2_TTY_OP_END'
1242             ];
1243             self::$channel_extended_data_type_codes = [
1244                 1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
1245             ];
1246 
1247             self::define_array(
1248                 self::$message_numbers,
1249                 self::$disconnect_reasons,
1250                 self::$channel_open_failure_reasons,
1251                 self::$terminal_modes,
1252                 self::$channel_extended_data_type_codes,
1253                 [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
1254                 [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
1255                 [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
1256                       61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
1257                 // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
1258                 [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
1259                       31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
1260                       32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
1261                       33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
1262                       34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
1263                 // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
1264                 [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
1265                       31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
1266             );
1267         }
1268 
1269         /**
1270          * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508
1271          * @var \WeakReference<SSH2>|SSH2
1272          */
1273         self::$connections[$this->getResourceId()] = class_exists('WeakReference')
1274             ? \WeakReference::create($this)
1275             : $this;
1276 
1277         $this->timeout = $timeout;
1278 
1279         if (is_resource($host)) {
1280             $this->fsock = $host;
1281             return;
1282         }
1283 
1284         if (Strings::is_stringable($host)) {
1285             $this->host = $host;
1286             $this->port = $port;
1287         }
1288     }
1289 
1290     /**
1291      * Set Crypto Engine Mode
1292      *
1293      * Possible $engine values:
1294      * OpenSSL, mcrypt, Eval, PHP
1295      *
1296      * @param int $engine
1297      */
1298     public static function setCryptoEngine($engine)
1299     {
1300         self::$crypto_engine = $engine;
1301     }
1302 
1303     /**
1304      * Send Identification String First
1305      *
1306      * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established,
1307      * both sides MUST send an identification string". It does not say which side sends it first. In
1308      * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
1309      *
1310      */
1311     public function sendIdentificationStringFirst()
1312     {
1313         $this->send_id_string_first = true;
1314     }
1315 
1316     /**
1317      * Send Identification String Last
1318      *
1319      * https://tools.ietf.org/html/rfc4253#section-4.2 says "when the connection has been established,
1320      * both sides MUST send an identification string". It does not say which side sends it first. In
1321      * theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
1322      *
1323      */
1324     public function sendIdentificationStringLast()
1325     {
1326         $this->send_id_string_first = false;
1327     }
1328 
1329     /**
1330      * Send SSH_MSG_KEXINIT First
1331      *
1332      * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending
1333      * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
1334      * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
1335      *
1336      */
1337     public function sendKEXINITFirst()
1338     {
1339         $this->send_kex_first = true;
1340     }
1341 
1342     /**
1343      * Send SSH_MSG_KEXINIT Last
1344      *
1345      * https://tools.ietf.org/html/rfc4253#section-7.1 says "key exchange begins by each sending
1346      * sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
1347      * it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
1348      *
1349      */
1350     public function sendKEXINITLast()
1351     {
1352         $this->send_kex_first = false;
1353     }
1354 
1355     /**
1356      * stream_select wrapper
1357      *
1358      * Quoting https://stackoverflow.com/a/14262151/569976,
1359      * "The general approach to `EINTR` is to simply handle the error and retry the operation again"
1360      *
1361      * This wrapper does that loop
1362      */
1363     private static function stream_select(&$read, &$write, &$except, $seconds, $microseconds = null)
1364     {
1365         $remaining = $seconds + $microseconds / 1000000;
1366         $start = microtime(true);
1367         while (true) {
1368             $result = @stream_select($read, $write, $except, $seconds, $microseconds);
1369             if ($result !== false) {
1370                 return $result;
1371             }
1372             $elapsed = microtime(true) - $start;
1373             $seconds = (int) ($remaining - floor($elapsed));
1374             $microseconds = (int) (1000000 * ($remaining - $seconds));
1375             if ($elapsed >= $remaining) {
1376                 return false;
1377             }
1378         }
1379     }
1380 
1381     /**
1382      * Connect to an SSHv2 server
1383      *
1384      * @throws \UnexpectedValueException on receipt of unexpected packets
1385      * @throws \RuntimeException on other errors
1386      */
1387     private function connect()
1388     {
1389         if ($this->bitmap & self::MASK_CONSTRUCTOR) {
1390             return;
1391         }
1392 
1393         $this->bitmap |= self::MASK_CONSTRUCTOR;
1394 
1395         $this->curTimeout = $this->timeout;
1396 
1397         if (!is_resource($this->fsock)) {
1398             $start = microtime(true);
1399             // with stream_select a timeout of 0 means that no timeout takes place;
1400             // with fsockopen a timeout of 0 means that you instantly timeout
1401             // to resolve this incompatibility a timeout of 100,000 will be used for fsockopen if timeout is 0
1402             $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout);
1403             if (!$this->fsock) {
1404                 $host = $this->host . ':' . $this->port;
1405                 throw new UnableToConnectException(rtrim("Cannot connect to $host. Error $errno. $errstr"));
1406             }
1407             $elapsed = microtime(true) - $start;
1408 
1409             if ($this->curTimeout) {
1410                 $this->curTimeout -= $elapsed;
1411                 if ($this->curTimeout < 0) {
1412                     throw new \RuntimeException('Connection timed out whilst attempting to open socket connection');
1413                 }
1414             }
1415 
1416             if (defined('NET_SSH2_LOGGING')) {
1417                 $this->append_log('(fsockopen took ' . round($elapsed, 4) . 's)', '');
1418             }
1419         }
1420 
1421         $this->identifier = $this->generate_identifier();
1422 
1423         if ($this->send_id_string_first) {
1424             $start = microtime(true);
1425             fputs($this->fsock, $this->identifier . "\r\n");
1426             $elapsed = round(microtime(true) - $start, 4);
1427             if (defined('NET_SSH2_LOGGING')) {
1428                 $this->append_log("-> (network: $elapsed)", $this->identifier . "\r\n");
1429             }
1430         }
1431 
1432         /* According to the SSH2 specs,
1433 
1434           "The server MAY send other lines of data before sending the version
1435            string.  Each line SHOULD be terminated by a Carriage Return and Line
1436            Feed.  Such lines MUST NOT begin with "SSH-", and SHOULD be encoded
1437            in ISO-10646 UTF-8 [RFC3629] (language is not specified).  Clients
1438            MUST be able to process such lines." */
1439         $data = '';
1440         $totalElapsed = 0;
1441         while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) {
1442             $line = '';
1443             while (true) {
1444                 if ($this->curTimeout) {
1445                     if ($this->curTimeout < 0) {
1446                         throw new \RuntimeException('Connection timed out whilst receiving server identification string');
1447                     }
1448                     $read = [$this->fsock];
1449                     $write = $except = null;
1450                     $start = microtime(true);
1451                     $sec = (int) floor($this->curTimeout);
1452                     $usec = (int) (1000000 * ($this->curTimeout - $sec));
1453                     if (static::stream_select($read, $write, $except, $sec, $usec) === false) {
1454                         throw new \RuntimeException('Connection timed out whilst receiving server identification string');
1455                     }
1456                     $elapsed = microtime(true) - $start;
1457                     $totalElapsed += $elapsed;
1458                     $this->curTimeout -= $elapsed;
1459                 }
1460 
1461                 $temp = stream_get_line($this->fsock, 255, "\n");
1462                 if ($temp === false) {
1463                     throw new \RuntimeException('Error reading SSH identification string; are you sure you\'re connecting to an SSH server?');
1464                 }
1465 
1466                 $line .= $temp;
1467                 if (strlen($temp) == 255) {
1468                     continue;
1469                 }
1470 
1471                 $line .= "\n";
1472 
1473                 break;
1474             }
1475 
1476             $data .= $line;
1477         }
1478 
1479         if (defined('NET_SSH2_LOGGING')) {
1480             $this->append_log('<- (network: ' . round($totalElapsed, 4) . ')', $line);
1481         }
1482 
1483         if (feof($this->fsock)) {
1484             $this->bitmap = 0;
1485             throw new ConnectionClosedException('Connection closed by server; are you sure you\'re connected to an SSH server?');
1486         }
1487 
1488         $extra = $matches[1];
1489 
1490         $this->server_identifier = trim($data, "\r\n");
1491         if (strlen($extra)) {
1492             $this->errors[] = $data;
1493         }
1494 
1495         if (version_compare($matches[3], '1.99', '<')) {
1496             $this->bitmap = 0;
1497             throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
1498         }
1499 
1500         // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see
1501         // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info.
1502         // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses
1503         // when consolekit was incorporated.
1504         // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the
1505         // issues with how Ubuntu incorporated consolekit
1506         $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#';
1507         $match = preg_match($pattern, $this->server_identifier, $matches);
1508         $match = $match && version_compare('5.8', $matches[1], '<=');
1509         $match = $match && version_compare('6.9', $matches[1], '>=');
1510         $this->errorOnMultipleChannels = $match;
1511 
1512         if (!$this->send_id_string_first) {
1513             $start = microtime(true);
1514             fputs($this->fsock, $this->identifier . "\r\n");
1515             $elapsed = round(microtime(true) - $start, 4);
1516             if (defined('NET_SSH2_LOGGING')) {
1517                 $this->append_log("-> (network: $elapsed)", $this->identifier . "\r\n");
1518             }
1519         }
1520 
1521         $this->last_packet = microtime(true);
1522 
1523         if (!$this->send_kex_first) {
1524             $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXINIT);
1525             $this->key_exchange($response);
1526         }
1527 
1528         if ($this->send_kex_first) {
1529             $this->key_exchange();
1530         }
1531 
1532         $this->bitmap |= self::MASK_CONNECTED;
1533 
1534         return true;
1535     }
1536 
1537     /**
1538      * Generates the SSH identifier
1539      *
1540      * You should overwrite this method in your own class if you want to use another identifier
1541      *
1542      * @return string
1543      */
1544     private function generate_identifier()
1545     {
1546         $identifier = 'SSH-2.0-phpseclib_3.0';
1547 
1548         $ext = [];
1549         if (extension_loaded('sodium')) {
1550             $ext[] = 'libsodium';
1551         }
1552 
1553         if (extension_loaded('openssl')) {
1554             $ext[] = 'openssl';
1555         } elseif (extension_loaded('mcrypt')) {
1556             $ext[] = 'mcrypt';
1557         }
1558 
1559         if (extension_loaded('gmp')) {
1560             $ext[] = 'gmp';
1561         } elseif (extension_loaded('bcmath')) {
1562             $ext[] = 'bcmath';
1563         }
1564 
1565         if (!empty($ext)) {
1566             $identifier .= ' (' . implode(', ', $ext) . ')';
1567         }
1568 
1569         return $identifier;
1570     }
1571 
1572     /**
1573      * Key Exchange
1574      *
1575      * @return bool
1576      * @param string|bool $kexinit_payload_server optional
1577      * @throws \UnexpectedValueException on receipt of unexpected packets
1578      * @throws \RuntimeException on other errors
1579      * @throws NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
1580      */
1581     private function key_exchange($kexinit_payload_server = false)
1582     {
1583         $this->bytesTransferredSinceLastKEX = 0;
1584 
1585         $preferred = $this->preferred;
1586         // for the initial key exchange $send_kex is true (no key re-exchange has been started)
1587         // for phpseclib initiated key exchanges $send_kex is false
1588         $send_kex = !$this->keyExchangeInProgress;
1589         $this->keyExchangeInProgress = true;
1590 
1591         $kex_algorithms = isset($preferred['kex']) ?
1592             $preferred['kex'] :
1593             SSH2::getSupportedKEXAlgorithms();
1594         $server_host_key_algorithms = isset($preferred['hostkey']) ?
1595             $preferred['hostkey'] :
1596             SSH2::getSupportedHostKeyAlgorithms();
1597         $s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ?
1598             $preferred['server_to_client']['crypt'] :
1599             SSH2::getSupportedEncryptionAlgorithms();
1600         $c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ?
1601             $preferred['client_to_server']['crypt'] :
1602             SSH2::getSupportedEncryptionAlgorithms();
1603         $s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ?
1604             $preferred['server_to_client']['mac'] :
1605             SSH2::getSupportedMACAlgorithms();
1606         $c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ?
1607             $preferred['client_to_server']['mac'] :
1608             SSH2::getSupportedMACAlgorithms();
1609         $s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ?
1610             $preferred['server_to_client']['comp'] :
1611             SSH2::getSupportedCompressionAlgorithms();
1612         $c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ?
1613             $preferred['client_to_server']['comp'] :
1614             SSH2::getSupportedCompressionAlgorithms();
1615 
1616         $kex_algorithms = array_merge($kex_algorithms, ['ext-info-c', 'kex-strict-c-v00@openssh.com']);
1617 
1618         // some SSH servers have buggy implementations of some of the above algorithms
1619         switch (true) {
1620             case $this->server_identifier == 'SSH-2.0-SSHD':
1621             case substr($this->server_identifier, 0, 13) == 'SSH-2.0-DLINK':
1622                 if (!isset($preferred['server_to_client']['mac'])) {
1623                     $s2c_mac_algorithms = array_values(array_diff(
1624                         $s2c_mac_algorithms,
1625                         ['hmac-sha1-96', 'hmac-md5-96']
1626                     ));
1627                 }
1628                 if (!isset($preferred['client_to_server']['mac'])) {
1629                     $c2s_mac_algorithms = array_values(array_diff(
1630                         $c2s_mac_algorithms,
1631                         ['hmac-sha1-96', 'hmac-md5-96']
1632                     ));
1633                 }
1634                 break;
1635             case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_':
1636                 if (!isset($preferred['server_to_client']['crypt'])) {
1637                     $s2c_encryption_algorithms = array_values(array_diff(
1638                         $s2c_encryption_algorithms,
1639                         ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
1640                     ));
1641                 }
1642                 if (!isset($preferred['client_to_server']['crypt'])) {
1643                     $c2s_encryption_algorithms = array_values(array_diff(
1644                         $c2s_encryption_algorithms,
1645                         ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
1646                     ));
1647                 }
1648         }
1649 
1650         $client_cookie = Random::string(16);
1651 
1652         $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie);
1653         $kexinit_payload_client .= Strings::packSSH2(
1654             'L10bN',
1655             $kex_algorithms,
1656             $server_host_key_algorithms,
1657             $c2s_encryption_algorithms,
1658             $s2c_encryption_algorithms,
1659             $c2s_mac_algorithms,
1660             $s2c_mac_algorithms,
1661             $c2s_compression_algorithms,
1662             $s2c_compression_algorithms,
1663             [], // language, client to server
1664             [], // language, server to client
1665             false, // first_kex_packet_follows
1666             0 // reserved for future extension
1667         );
1668 
1669         if ($kexinit_payload_server === false && $send_kex) {
1670             $this->send_binary_packet($kexinit_payload_client);
1671 
1672             while (true) {
1673                 $kexinit_payload_server = $this->get_binary_packet();
1674                 switch (ord($kexinit_payload_server[0])) {
1675                     case NET_SSH2_MSG_KEXINIT:
1676                         break 2;
1677                     case NET_SSH2_MSG_DISCONNECT:
1678                         return $this->handleDisconnect($kexinit_payload_server);
1679                 }
1680 
1681                 $this->kex_buffer[] = $kexinit_payload_server;
1682             }
1683 
1684             $send_kex = false;
1685         }
1686 
1687         $response = $kexinit_payload_server;
1688         Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
1689         $server_cookie = Strings::shift($response, 16);
1690 
1691         list(
1692             $this->kex_algorithms,
1693             $this->server_host_key_algorithms,
1694             $this->encryption_algorithms_client_to_server,
1695             $this->encryption_algorithms_server_to_client,
1696             $this->mac_algorithms_client_to_server,
1697             $this->mac_algorithms_server_to_client,
1698             $this->compression_algorithms_client_to_server,
1699             $this->compression_algorithms_server_to_client,
1700             $this->languages_client_to_server,
1701             $this->languages_server_to_client,
1702             $first_kex_packet_follows
1703         ) = Strings::unpackSSH2('L10C', $response);
1704         if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) {
1705             if ($this->session_id === false) {
1706                 // [kex-strict-s-v00@openssh.com is] only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
1707                 // if [it is] present in subsequent SSH2_MSG_KEXINIT packets
1708                 $this->strict_kex_flag = true;
1709                 if (count($this->kex_buffer)) {
1710                     throw new \UnexpectedValueException('Possible Terrapin Attack detected');
1711                 }
1712             }
1713         }
1714 
1715         $this->supported_private_key_algorithms = $this->server_host_key_algorithms;
1716 
1717         if ($send_kex) {
1718             $this->send_binary_packet($kexinit_payload_client);
1719         }
1720 
1721         // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
1722 
1723         // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
1724         // diffie-hellman key exchange as fast as possible
1725         $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
1726         if (!$decrypt || ($decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt)) === null) {
1727             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1728             throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
1729         }
1730 
1731         $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
1732         if (!$encrypt || ($encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt)) === null) {
1733             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1734             throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
1735         }
1736 
1737         // through diffie-hellman key exchange a symmetric key is obtained
1738         $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
1739         if ($this->kex_algorithm === false) {
1740             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1741             throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
1742         }
1743 
1744         $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
1745         if ($server_host_key_algorithm === false) {
1746             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1747             throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
1748         }
1749 
1750         $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
1751         if ($mac_algorithm_out === false) {
1752             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1753             throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
1754         }
1755 
1756         $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
1757         if ($mac_algorithm_in === false) {
1758             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1759             throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
1760         }
1761 
1762         $compression_map = [
1763             'none' => self::NET_SSH2_COMPRESSION_NONE,
1764             'zlib' => self::NET_SSH2_COMPRESSION_ZLIB,
1765             'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH
1766         ];
1767 
1768         $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
1769         if ($compression_algorithm_in === false) {
1770             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1771             throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
1772         }
1773         $this->decompress = $compression_map[$compression_algorithm_in];
1774 
1775         $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
1776         if ($compression_algorithm_out === false) {
1777             $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
1778             throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
1779         }
1780         $this->compress = $compression_map[$compression_algorithm_out];
1781 
1782         switch ($this->kex_algorithm) {
1783             case 'diffie-hellman-group15-sha512':
1784             case 'diffie-hellman-group16-sha512':
1785             case 'diffie-hellman-group17-sha512':
1786             case 'diffie-hellman-group18-sha512':
1787             case 'ecdh-sha2-nistp521':
1788                 $kexHash = new Hash('sha512');
1789                 break;
1790             case 'ecdh-sha2-nistp384':
1791                 $kexHash = new Hash('sha384');
1792                 break;
1793             case 'diffie-hellman-group-exchange-sha256':
1794             case 'diffie-hellman-group14-sha256':
1795             case 'ecdh-sha2-nistp256':
1796             case 'curve25519-sha256@libssh.org':
1797             case 'curve25519-sha256':
1798                 $kexHash = new Hash('sha256');
1799                 break;
1800             default:
1801                 $kexHash = new Hash('sha1');
1802         }
1803 
1804         // Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
1805 
1806         $exchange_hash_rfc4419 = '';
1807 
1808         if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) {
1809             $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ?
1810                 'Curve25519' :
1811                 substr($this->kex_algorithm, 10);
1812             $ourPrivate = EC::createKey($curve);
1813             $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates();
1814             $clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT';
1815             $serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY';
1816         } else {
1817             if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
1818                 $dh_group_sizes_packed = pack(
1819                     'NNN',
1820                     $this->kex_dh_group_size_min,
1821                     $this->kex_dh_group_size_preferred,
1822                     $this->kex_dh_group_size_max
1823                 );
1824                 $packet = pack(
1825                     'Ca*',
1826                     NET_SSH2_MSG_KEXDH_GEX_REQUEST,
1827                     $dh_group_sizes_packed
1828                 );
1829                 $this->send_binary_packet($packet);
1830                 $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
1831 
1832                 $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXDH_GEX_GROUP);
1833                 list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response);
1834                 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
1835                 $prime = new BigInteger($primeBytes, -256);
1836                 $g = new BigInteger($gBytes, -256);
1837 
1838                 $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2(
1839                     'ss',
1840                     $primeBytes,
1841                     $gBytes
1842                 );
1843 
1844                 $params = DH::createParameters($prime, $g);
1845                 $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT';
1846                 $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY';
1847             } else {
1848                 $params = DH::createParameters($this->kex_algorithm);
1849                 $clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT';
1850                 $serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY';
1851             }
1852 
1853             $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength));
1854 
1855             $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength
1856             $ourPublic = $ourPrivate->getPublicKey()->toBigInteger();
1857             $ourPublicBytes = $ourPublic->toBytes(true);
1858         }
1859 
1860         $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes);
1861 
1862         $this->send_binary_packet($data);
1863 
1864         switch ($clientKexInitMessage) {
1865             case 'NET_SSH2_MSG_KEX_ECDH_INIT':
1866                 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
1867                 break;
1868             case 'NET_SSH2_MSG_KEXDH_GEX_INIT':
1869                 $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
1870         }
1871 
1872         $response = $this->get_binary_packet_or_close(constant($serverKexReplyMessage));
1873 
1874         list(
1875             $type,
1876             $server_public_host_key,
1877             $theirPublicBytes,
1878             $this->signature
1879         ) = Strings::unpackSSH2('Csss', $response);
1880 
1881         switch ($serverKexReplyMessage) {
1882             case 'NET_SSH2_MSG_KEX_ECDH_REPLY':
1883                 $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
1884                 break;
1885             case 'NET_SSH2_MSG_KEXDH_GEX_REPLY':
1886                 $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
1887         }
1888 
1889         $this->server_public_host_key = $server_public_host_key;
1890         list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key);
1891         if (strlen($this->signature) < 4) {
1892             throw new \LengthException('The signature needs at least four bytes');
1893         }
1894         $temp = unpack('Nlength', substr($this->signature, 0, 4));
1895         $this->signature_format = substr($this->signature, 4, $temp['length']);
1896 
1897         $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes);
1898         if (($keyBytes & "\xFF\x80") === "\x00\x00") {
1899             $keyBytes = substr($keyBytes, 1);
1900         } elseif (($keyBytes[0] & "\x80") === "\x80") {
1901             $keyBytes = "\0$keyBytes";
1902         }
1903 
1904         $this->exchange_hash = Strings::packSSH2(
1905             's5',
1906             $this->identifier,
1907             $this->server_identifier,
1908             $kexinit_payload_client,
1909             $kexinit_payload_server,
1910             $this->server_public_host_key
1911         );
1912         $this->exchange_hash .= $exchange_hash_rfc4419;
1913         $this->exchange_hash .= Strings::packSSH2(
1914             's3',
1915             $ourPublicBytes,
1916             $theirPublicBytes,
1917             $keyBytes
1918         );
1919 
1920         $this->exchange_hash = $kexHash->hash($this->exchange_hash);
1921 
1922         if ($this->session_id === false) {
1923             $this->session_id = $this->exchange_hash;
1924         }
1925 
1926         switch ($server_host_key_algorithm) {
1927             case 'rsa-sha2-256':
1928             case 'rsa-sha2-512':
1929             //case 'ssh-rsa':
1930                 $expected_key_format = 'ssh-rsa';
1931                 break;
1932             default:
1933                 $expected_key_format = $server_host_key_algorithm;
1934         }
1935         if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) {
1936             switch (true) {
1937                 case $this->signature_format == $server_host_key_algorithm:
1938                 case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512':
1939                 case $this->signature_format != 'ssh-rsa':
1940                     $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
1941                     throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')');
1942             }
1943         }
1944 
1945         $packet = pack('C', NET_SSH2_MSG_NEWKEYS);
1946         $this->send_binary_packet($packet);
1947         $this->get_binary_packet_or_close(NET_SSH2_MSG_NEWKEYS);
1948 
1949         $this->keyExchangeInProgress = false;
1950 
1951         if ($this->strict_kex_flag) {
1952             $this->get_seq_no = $this->send_seq_no = 0;
1953         }
1954 
1955         $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
1956 
1957         $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
1958         if ($this->encrypt) {
1959             if (self::$crypto_engine) {
1960                 $this->encrypt->setPreferredEngine(self::$crypto_engine);
1961             }
1962             if ($this->encrypt->getBlockLengthInBytes()) {
1963                 $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes();
1964             }
1965             $this->encrypt->disablePadding();
1966 
1967             if ($this->encrypt->usesIV()) {
1968                 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1969                 while ($this->encrypt_block_size > strlen($iv)) {
1970                     $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
1971                 }
1972                 $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
1973             }
1974 
1975             switch ($encrypt) {
1976                 case 'aes128-gcm@openssh.com':
1977                 case 'aes256-gcm@openssh.com':
1978                     $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
1979                     $this->encryptFixedPart = substr($nonce, 0, 4);
1980                     $this->encryptInvocationCounter = substr($nonce, 4, 8);
1981                     // fall-through
1982                 case 'chacha20-poly1305@openssh.com':
1983                     break;
1984                 default:
1985                     $this->encrypt->enableContinuousBuffer();
1986             }
1987 
1988             $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
1989             while ($encryptKeyLength > strlen($key)) {
1990                 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
1991             }
1992             switch ($encrypt) {
1993                 case 'chacha20-poly1305@openssh.com':
1994                     $encryptKeyLength = 32;
1995                     $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
1996                     $this->lengthEncrypt->setKey(substr($key, 32, 32));
1997             }
1998             $this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
1999             $this->encryptName = $encrypt;
2000         }
2001 
2002         $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
2003         if ($this->decrypt) {
2004             if (self::$crypto_engine) {
2005                 $this->decrypt->setPreferredEngine(self::$crypto_engine);
2006             }
2007             if ($this->decrypt->getBlockLengthInBytes()) {
2008                 $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes();
2009             }
2010             $this->decrypt->disablePadding();
2011 
2012             if ($this->decrypt->usesIV()) {
2013                 $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
2014                 while ($this->decrypt_block_size > strlen($iv)) {
2015                     $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
2016                 }
2017                 $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
2018             }
2019 
2020             switch ($decrypt) {
2021                 case 'aes128-gcm@openssh.com':
2022                 case 'aes256-gcm@openssh.com':
2023                     // see https://tools.ietf.org/html/rfc5647#section-7.1
2024                     $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
2025                     $this->decryptFixedPart = substr($nonce, 0, 4);
2026                     $this->decryptInvocationCounter = substr($nonce, 4, 8);
2027                     // fall-through
2028                 case 'chacha20-poly1305@openssh.com':
2029                     break;
2030                 default:
2031                     $this->decrypt->enableContinuousBuffer();
2032             }
2033 
2034             $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
2035             while ($decryptKeyLength > strlen($key)) {
2036                 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2037             }
2038             switch ($decrypt) {
2039                 case 'chacha20-poly1305@openssh.com':
2040                     $decryptKeyLength = 32;
2041                     $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
2042                     $this->lengthDecrypt->setKey(substr($key, 32, 32));
2043             }
2044             $this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
2045             $this->decryptName = $decrypt;
2046         }
2047 
2048         /* The "arcfour128" algorithm is the RC4 cipher, as described in
2049            [SCHNEIER], using a 128-bit key.  The first 1536 bytes of keystream
2050            generated by the cipher MUST be discarded, and the first byte of the
2051            first encrypted packet MUST be encrypted using the 1537th byte of
2052            keystream.
2053 
2054            -- http://tools.ietf.org/html/rfc4345#section-4 */
2055         if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') {
2056             $this->encrypt->encrypt(str_repeat("\0", 1536));
2057         }
2058         if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') {
2059             $this->decrypt->decrypt(str_repeat("\0", 1536));
2060         }
2061 
2062         if (!$this->encrypt->usesNonce()) {
2063             list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out);
2064         } else {
2065             $this->hmac_create = new \stdClass();
2066             $this->hmac_create_name = $mac_algorithm_out;
2067             //$mac_algorithm_out = 'none';
2068             $createKeyLength = 0;
2069         }
2070 
2071         if ($this->hmac_create instanceof Hash) {
2072             $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
2073             while ($createKeyLength > strlen($key)) {
2074                 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2075             }
2076             $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
2077             $this->hmac_create_name = $mac_algorithm_out;
2078             $this->hmac_create_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out);
2079         }
2080 
2081         if (!$this->decrypt->usesNonce()) {
2082             list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in);
2083             $this->hmac_size = $this->hmac_check->getLengthInBytes();
2084         } else {
2085             $this->hmac_check = new \stdClass();
2086             $this->hmac_check_name = $mac_algorithm_in;
2087             //$mac_algorithm_in = 'none';
2088             $checkKeyLength = 0;
2089             $this->hmac_size = 0;
2090         }
2091 
2092         if ($this->hmac_check instanceof Hash) {
2093             $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
2094             while ($checkKeyLength > strlen($key)) {
2095                 $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
2096             }
2097             $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
2098             $this->hmac_check_name = $mac_algorithm_in;
2099             $this->hmac_check_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in);
2100         }
2101 
2102         $this->regenerate_compression_context = $this->regenerate_decompression_context = true;
2103 
2104         return true;
2105     }
2106 
2107     /**
2108      * Maps an encryption algorithm name to the number of key bytes.
2109      *
2110      * @param string $algorithm Name of the encryption algorithm
2111      * @return int|null Number of bytes as an integer or null for unknown
2112      */
2113     private function encryption_algorithm_to_key_size($algorithm)
2114     {
2115         if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) {
2116             return 16;
2117         }
2118 
2119         switch ($algorithm) {
2120             case 'none':
2121                 return 0;
2122             case 'aes128-gcm@openssh.com':
2123             case 'aes128-cbc':
2124             case 'aes128-ctr':
2125             case 'arcfour':
2126             case 'arcfour128':
2127             case 'blowfish-cbc':
2128             case 'blowfish-ctr':
2129             case 'twofish128-cbc':
2130             case 'twofish128-ctr':
2131                 return 16;
2132             case '3des-cbc':
2133             case '3des-ctr':
2134             case 'aes192-cbc':
2135             case 'aes192-ctr':
2136             case 'twofish192-cbc':
2137             case 'twofish192-ctr':
2138                 return 24;
2139             case 'aes256-gcm@openssh.com':
2140             case 'aes256-cbc':
2141             case 'aes256-ctr':
2142             case 'arcfour256':
2143             case 'twofish-cbc':
2144             case 'twofish256-cbc':
2145             case 'twofish256-ctr':
2146                 return 32;
2147             case 'chacha20-poly1305@openssh.com':
2148                 return 64;
2149         }
2150         return null;
2151     }
2152 
2153     /**
2154      * Maps an encryption algorithm name to an instance of a subclass of
2155      * \phpseclib3\Crypt\Common\SymmetricKey.
2156      *
2157      * @param string $algorithm Name of the encryption algorithm
2158      * @return SymmetricKey|null
2159      */
2160     private static function encryption_algorithm_to_crypt_instance($algorithm)
2161     {
2162         switch ($algorithm) {
2163             case '3des-cbc':
2164                 return new TripleDES('cbc');
2165             case '3des-ctr':
2166                 return new TripleDES('ctr');
2167             case 'aes256-cbc':
2168             case 'aes192-cbc':
2169             case 'aes128-cbc':
2170                 return new Rijndael('cbc');
2171             case 'aes256-ctr':
2172             case 'aes192-ctr':
2173             case 'aes128-ctr':
2174                 return new Rijndael('ctr');
2175             case 'blowfish-cbc':
2176                 return new Blowfish('cbc');
2177             case 'blowfish-ctr':
2178                 return new Blowfish('ctr');
2179             case 'twofish128-cbc':
2180             case 'twofish192-cbc':
2181             case 'twofish256-cbc':
2182             case 'twofish-cbc':
2183                 return new Twofish('cbc');
2184             case 'twofish128-ctr':
2185             case 'twofish192-ctr':
2186             case 'twofish256-ctr':
2187                 return new Twofish('ctr');
2188             case 'arcfour':
2189             case 'arcfour128':
2190             case 'arcfour256':
2191                 return new RC4();
2192             case 'aes128-gcm@openssh.com':
2193             case 'aes256-gcm@openssh.com':
2194                 return new Rijndael('gcm');
2195             case 'chacha20-poly1305@openssh.com':
2196                 return new ChaCha20();
2197         }
2198         return null;
2199     }
2200 
2201     /**
2202      * Maps an encryption algorithm name to an instance of a subclass of
2203      * \phpseclib3\Crypt\Hash.
2204      *
2205      * @param string $algorithm Name of the encryption algorithm
2206      * @return array{Hash, int}|null
2207      */
2208     private static function mac_algorithm_to_hash_instance($algorithm)
2209     {
2210         switch ($algorithm) {
2211             case 'umac-64@openssh.com':
2212             case 'umac-64-etm@openssh.com':
2213                 return [new Hash('umac-64'), 16];
2214             case 'umac-128@openssh.com':
2215             case 'umac-128-etm@openssh.com':
2216                 return [new Hash('umac-128'), 16];
2217             case 'hmac-sha2-512':
2218             case 'hmac-sha2-512-etm@openssh.com':
2219                 return [new Hash('sha512'), 64];
2220             case 'hmac-sha2-256':
2221             case 'hmac-sha2-256-etm@openssh.com':
2222                 return [new Hash('sha256'), 32];
2223             case 'hmac-sha1':
2224             case 'hmac-sha1-etm@openssh.com':
2225                 return [new Hash('sha1'), 20];
2226             case 'hmac-sha1-96':
2227                 return [new Hash('sha1-96'), 20];
2228             case 'hmac-md5':
2229                 return [new Hash('md5'), 16];
2230             case 'hmac-md5-96':
2231                 return [new Hash('md5-96'), 16];
2232         }
2233     }
2234 
2235     /**
2236      * Tests whether or not proposed algorithm has a potential for issues
2237      *
2238      * @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html
2239      * @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291
2240      * @param string $algorithm Name of the encryption algorithm
2241      * @return bool
2242      */
2243     private static function bad_algorithm_candidate($algorithm)
2244     {
2245         switch ($algorithm) {
2246             case 'arcfour256':
2247             case 'aes192-ctr':
2248             case 'aes256-ctr':
2249                 return true;
2250         }
2251 
2252         return false;
2253     }
2254 
2255     /**
2256      * Login
2257      *
2258      * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array
2259      *
2260      * @param string $username
2261      * @param string|PrivateKey|array[]|Agent|null ...$args
2262      * @return bool
2263      * @see self::_login()
2264      */
2265     public function login($username, ...$args)
2266     {
2267         if (!$this->login_credentials_finalized) {
2268             $this->auth[] = func_get_args();
2269         }
2270 
2271         // try logging with 'none' as an authentication method first since that's what
2272         // PuTTY does
2273         if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) {
2274             if ($this->sublogin($username)) {
2275                 return true;
2276             }
2277             if (!count($args)) {
2278                 return false;
2279             }
2280         }
2281         return $this->sublogin($username, ...$args);
2282     }
2283 
2284     /**
2285      * Login Helper
2286      *
2287      * @param string $username
2288      * @param string|PrivateKey|array[]|Agent|null ...$args
2289      * @return bool
2290      * @see self::_login_helper()
2291      */
2292     protected function sublogin($username, ...$args)
2293     {
2294         if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
2295             $this->connect();
2296         }
2297 
2298         if (empty($args)) {
2299             return $this->login_helper($username);
2300         }
2301 
2302         foreach ($args as $arg) {
2303             switch (true) {
2304                 case $arg instanceof PublicKey:
2305                     throw new \UnexpectedValueException('A PublicKey object was passed to the login method instead of a PrivateKey object');
2306                 case $arg instanceof PrivateKey:
2307                 case $arg instanceof Agent:
2308                 case is_array($arg):
2309                 case Strings::is_stringable($arg):
2310                     break;
2311                 default:
2312                     throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string');
2313             }
2314         }
2315 
2316         while (count($args)) {
2317             if (!$this->auth_methods_to_continue || !$this->smartMFA) {
2318                 $newargs = $args;
2319                 $args = [];
2320             } else {
2321                 $newargs = [];
2322                 foreach ($this->auth_methods_to_continue as $method) {
2323                     switch ($method) {
2324                         case 'publickey':
2325                             foreach ($args as $key => $arg) {
2326                                 if ($arg instanceof PrivateKey || $arg instanceof Agent) {
2327                                     $newargs[] = $arg;
2328                                     unset($args[$key]);
2329                                     break;
2330                                 }
2331                             }
2332                             break;
2333                         case 'keyboard-interactive':
2334                             $hasArray = $hasString = false;
2335                             foreach ($args as $arg) {
2336                                 if ($hasArray || is_array($arg)) {
2337                                     $hasArray = true;
2338                                     break;
2339                                 }
2340                                 if ($hasString || Strings::is_stringable($arg)) {
2341                                     $hasString = true;
2342                                     break;
2343                                 }
2344                             }
2345                             if ($hasArray && $hasString) {
2346                                 foreach ($args as $key => $arg) {
2347                                     if (is_array($arg)) {
2348                                         $newargs[] = $arg;
2349                                         break 2;
2350                                     }
2351                                 }
2352                             }
2353                             // fall-through
2354                         case 'password':
2355                             foreach ($args as $key => $arg) {
2356                                 $newargs[] = $arg;
2357                                 unset($args[$key]);
2358                                 break;
2359                             }
2360                     }
2361                 }
2362             }
2363 
2364             if (!count($newargs)) {
2365                 return false;
2366             }
2367 
2368             foreach ($newargs as $arg) {
2369                 if ($this->login_helper($username, $arg)) {
2370                     $this->login_credentials_finalized = true;
2371                     return true;
2372                 }
2373             }
2374         }
2375         return false;
2376     }
2377 
2378     /**
2379      * Login Helper
2380      *
2381      * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
2382      *           by sending dummy SSH_MSG_IGNORE messages.}
2383      *
2384      * @param string $username
2385      * @param string|AsymmetricKey|array[]|Agent|null ...$args
2386      * @return bool
2387      * @throws \UnexpectedValueException on receipt of unexpected packets
2388      * @throws \RuntimeException on other errors
2389      */
2390     private function login_helper($username, $password = null)
2391     {
2392         if (!($this->bitmap & self::MASK_CONNECTED)) {
2393             return false;
2394         }
2395 
2396         if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
2397             $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth');
2398             $this->send_binary_packet($packet);
2399 
2400             try {
2401                 $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_SERVICE_ACCEPT);
2402             } catch (InvalidPacketLengthException $e) {
2403                 // the first opportunity to encounter the "bad key size" error
2404                 if (!$this->bad_key_size_fix && $this->decryptName != null && self::bad_algorithm_candidate($this->decryptName)) {
2405                     // bad_key_size_fix is only ever re-assigned to true here
2406                     // retry the connection with that new setting but we'll
2407                     // only try it once.
2408                     $this->bad_key_size_fix = true;
2409                     return $this->reconnect();
2410                 }
2411                 throw $e;
2412             }
2413 
2414             list($type) = Strings::unpackSSH2('C', $response);
2415             list($service) = Strings::unpackSSH2('s', $response);
2416 
2417             if ($service != 'ssh-userauth') {
2418                 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
2419                 throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
2420             }
2421             $this->bitmap |= self::MASK_LOGIN_REQ;
2422         }
2423 
2424         if (strlen($this->last_interactive_response)) {
2425             return !Strings::is_stringable($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password);
2426         }
2427 
2428         if ($password instanceof PrivateKey) {
2429             return $this->privatekey_login($username, $password);
2430         }
2431 
2432         if ($password instanceof Agent) {
2433             return $this->ssh_agent_login($username, $password);
2434         }
2435 
2436         if (is_array($password)) {
2437             if ($this->keyboard_interactive_login($username, $password)) {
2438                 $this->bitmap |= self::MASK_LOGIN;
2439                 return true;
2440             }
2441             return false;
2442         }
2443 
2444         if (!isset($password)) {
2445             $packet = Strings::packSSH2(
2446                 'Cs3',
2447                 NET_SSH2_MSG_USERAUTH_REQUEST,
2448                 $username,
2449                 'ssh-connection',
2450                 'none'
2451             );
2452 
2453             $this->send_binary_packet($packet);
2454 
2455             $response = $this->get_binary_packet_or_close();
2456 
2457             list($type) = Strings::unpackSSH2('C', $response);
2458             switch ($type) {
2459                 case NET_SSH2_MSG_USERAUTH_SUCCESS:
2460                     $this->bitmap |= self::MASK_LOGIN;
2461                     return true;
2462                 case NET_SSH2_MSG_USERAUTH_FAILURE:
2463                     list($auth_methods) = Strings::unpackSSH2('L', $response);
2464                     $this->auth_methods_to_continue = $auth_methods;
2465                     // fall-through
2466                 default:
2467                     return false;
2468             }
2469         }
2470 
2471         $packet = Strings::packSSH2(
2472             'Cs3bs',
2473             NET_SSH2_MSG_USERAUTH_REQUEST,
2474             $username,
2475             'ssh-connection',
2476             'password',
2477             false,
2478             $password
2479         );
2480 
2481         // remove the username and password from the logged packet
2482         if (!defined('NET_SSH2_LOGGING')) {
2483             $logged = null;
2484         } else {
2485             $logged = Strings::packSSH2(
2486                 'Cs3bs',
2487                 NET_SSH2_MSG_USERAUTH_REQUEST,
2488                 $username,
2489                 'ssh-connection',
2490                 'password',
2491                 false,
2492                 'password'
2493             );
2494         }
2495 
2496         $this->send_binary_packet($packet, $logged);
2497 
2498         $response = $this->get_binary_packet_or_close();
2499         list($type) = Strings::unpackSSH2('C', $response);
2500         switch ($type) {
2501             case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
2502                 $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
2503 
2504                 list($message) = Strings::unpackSSH2('s', $response);
2505                 $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message;
2506 
2507                 return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
2508             case NET_SSH2_MSG_USERAUTH_FAILURE:
2509                 // can we use keyboard-interactive authentication?  if not then either the login is bad or the server employees
2510                 // multi-factor authentication
2511                 list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response);
2512                 $this->auth_methods_to_continue = $auth_methods;
2513                 if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
2514                     if ($this->keyboard_interactive_login($username, $password)) {
2515                         $this->bitmap |= self::MASK_LOGIN;
2516                         return true;
2517                     }
2518                     return false;
2519                 }
2520                 return false;
2521             case NET_SSH2_MSG_USERAUTH_SUCCESS:
2522                 $this->bitmap |= self::MASK_LOGIN;
2523                 return true;
2524         }
2525 
2526         return false;
2527     }
2528 
2529     /**
2530      * Login via keyboard-interactive authentication
2531      *
2532      * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details.  This is not a full-featured keyboard-interactive authenticator.
2533      *
2534      * @param string $username
2535      * @param string|array $password
2536      * @return bool
2537      */
2538     private function keyboard_interactive_login($username, $password)
2539     {
2540         $packet = Strings::packSSH2(
2541             'Cs5',
2542             NET_SSH2_MSG_USERAUTH_REQUEST,
2543             $username,
2544             'ssh-connection',
2545             'keyboard-interactive',
2546             '', // language tag
2547             '' // submethods
2548         );
2549         $this->send_binary_packet($packet);
2550 
2551         return $this->keyboard_interactive_process($password);
2552     }
2553 
2554     /**
2555      * Handle the keyboard-interactive requests / responses.
2556      *
2557      * @param string|array ...$responses
2558      * @return bool
2559      * @throws \RuntimeException on connection error
2560      */
2561     private function keyboard_interactive_process(...$responses)
2562     {
2563         if (strlen($this->last_interactive_response)) {
2564             $response = $this->last_interactive_response;
2565         } else {
2566             $orig = $response = $this->get_binary_packet_or_close();
2567         }
2568 
2569         list($type) = Strings::unpackSSH2('C', $response);
2570         switch ($type) {
2571             case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
2572                 list(
2573                     , // name; may be empty
2574                     , // instruction; may be empty
2575                     , // language tag; may be empty
2576                     $num_prompts
2577                 ) = Strings::unpackSSH2('s3N', $response);
2578 
2579                 for ($i = 0; $i < count($responses); $i++) {
2580                     if (is_array($responses[$i])) {
2581                         foreach ($responses[$i] as $key => $value) {
2582                             $this->keyboard_requests_responses[$key] = $value;
2583                         }
2584                         unset($responses[$i]);
2585                     }
2586                 }
2587                 $responses = array_values($responses);
2588 
2589                 if (isset($this->keyboard_requests_responses)) {
2590                     for ($i = 0; $i < $num_prompts; $i++) {
2591                         list(
2592                             $prompt, // prompt - ie. "Password: "; must not be empty
2593                             // echo
2594                         ) = Strings::unpackSSH2('sC', $response);
2595                         foreach ($this->keyboard_requests_responses as $key => $value) {
2596                             if (substr($prompt, 0, strlen($key)) == $key) {
2597                                 $responses[] = $value;
2598                                 break;
2599                             }
2600                         }
2601                     }
2602                 }
2603 
2604                 // see http://tools.ietf.org/html/rfc4256#section-3.2
2605                 if (strlen($this->last_interactive_response)) {
2606                     $this->last_interactive_response = '';
2607                 } else {
2608                     $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
2609                 }
2610 
2611                 if (!count($responses) && $num_prompts) {
2612                     $this->last_interactive_response = $orig;
2613                     return false;
2614                 }
2615 
2616                 /*
2617                    After obtaining the requested information from the user, the client
2618                    MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message.
2619                 */
2620                 // see http://tools.ietf.org/html/rfc4256#section-3.4
2621                 $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
2622                 for ($i = 0; $i < count($responses); $i++) {
2623                     $packet .= Strings::packSSH2('s', $responses[$i]);
2624                     $logged .= Strings::packSSH2('s', 'dummy-answer');
2625                 }
2626 
2627                 $this->send_binary_packet($packet, $logged);
2628 
2629                 $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
2630 
2631                 /*
2632                    After receiving the response, the server MUST send either an
2633                    SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another
2634                    SSH_MSG_USERAUTH_INFO_REQUEST message.
2635                 */
2636                 // maybe phpseclib should force close the connection after x request / responses?  unless something like that is done
2637                 // there could be an infinite loop of request / responses.
2638                 return $this->keyboard_interactive_process();
2639             case NET_SSH2_MSG_USERAUTH_SUCCESS:
2640                 return true;
2641             case NET_SSH2_MSG_USERAUTH_FAILURE:
2642                 list($auth_methods) = Strings::unpackSSH2('L', $response);
2643                 $this->auth_methods_to_continue = $auth_methods;
2644                 return false;
2645         }
2646 
2647         return false;
2648     }
2649 
2650     /**
2651      * Login with an ssh-agent provided key
2652      *
2653      * @param string $username
2654      * @param Agent $agent
2655      * @return bool
2656      */
2657     private function ssh_agent_login($username, Agent $agent)
2658     {
2659         $this->agent = $agent;
2660         $keys = $agent->requestIdentities();
2661         $orig_algorithms = $this->supported_private_key_algorithms;
2662         foreach ($keys as $key) {
2663             if ($this->privatekey_login($username, $key)) {
2664                 return true;
2665             }
2666             $this->supported_private_key_algorithms = $orig_algorithms;
2667         }
2668 
2669         return false;
2670     }
2671 
2672     /**
2673      * Login with an RSA private key
2674      *
2675      * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
2676      *           by sending dummy SSH_MSG_IGNORE messages.}
2677      *
2678      * @param string $username
2679      * @param PrivateKey $privatekey
2680      * @return bool
2681      * @throws \RuntimeException on connection error
2682      */
2683     private function privatekey_login($username, PrivateKey $privatekey)
2684     {
2685         $publickey = $privatekey->getPublicKey();
2686 
2687         if ($publickey instanceof RSA) {
2688             $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1);
2689             $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
2690             if (isset($this->preferred['hostkey'])) {
2691                 $algos = array_intersect($algos, $this->preferred['hostkey']);
2692             }
2693             $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
2694             switch ($algo) {
2695                 case 'rsa-sha2-512':
2696                     $hash = 'sha512';
2697                     $signatureType = 'rsa-sha2-512';
2698                     break;
2699                 case 'rsa-sha2-256':
2700                     $hash = 'sha256';
2701                     $signatureType = 'rsa-sha2-256';
2702                     break;
2703                 //case 'ssh-rsa':
2704                 default:
2705                     $hash = 'sha1';
2706                     $signatureType = 'ssh-rsa';
2707             }
2708         } elseif ($publickey instanceof EC) {
2709             $privatekey = $privatekey->withSignatureFormat('SSH2');
2710             $curveName = $privatekey->getCurve();
2711             switch ($curveName) {
2712                 case 'Ed25519':
2713                     $hash = 'sha512';
2714                     $signatureType = 'ssh-ed25519';
2715                     break;
2716                 case 'secp256r1': // nistp256
2717                     $hash = 'sha256';
2718                     $signatureType = 'ecdsa-sha2-nistp256';
2719                     break;
2720                 case 'secp384r1': // nistp384
2721                     $hash = 'sha384';
2722                     $signatureType = 'ecdsa-sha2-nistp384';
2723                     break;
2724                 case 'secp521r1': // nistp521
2725                     $hash = 'sha512';
2726                     $signatureType = 'ecdsa-sha2-nistp521';
2727                     break;
2728                 default:
2729                     if (is_array($curveName)) {
2730                         throw new UnsupportedCurveException('Specified Curves are not supported by SSH2');
2731                     }
2732                     throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation');
2733             }
2734         } elseif ($publickey instanceof DSA) {
2735             $privatekey = $privatekey->withSignatureFormat('SSH2');
2736             $hash = 'sha1';
2737             $signatureType = 'ssh-dss';
2738         } else {
2739             throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key');
2740         }
2741 
2742         $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]);
2743 
2744         $part1 = Strings::packSSH2(
2745             'Csss',
2746             NET_SSH2_MSG_USERAUTH_REQUEST,
2747             $username,
2748             'ssh-connection',
2749             'publickey'
2750         );
2751         $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr);
2752 
2753         $packet = $part1 . chr(0) . $part2;
2754         $this->send_binary_packet($packet);
2755 
2756         $response = $this->get_binary_packet_or_close(
2757             NET_SSH2_MSG_USERAUTH_SUCCESS,
2758             NET_SSH2_MSG_USERAUTH_FAILURE,
2759             NET_SSH2_MSG_USERAUTH_PK_OK
2760         );
2761 
2762         list($type) = Strings::unpackSSH2('C', $response);
2763         switch ($type) {
2764             case NET_SSH2_MSG_USERAUTH_FAILURE:
2765                 list($auth_methods) = Strings::unpackSSH2('L', $response);
2766                 if (in_array('publickey', $auth_methods) && substr($signatureType, 0, 9) == 'rsa-sha2-') {
2767                     $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, ['rsa-sha2-256', 'rsa-sha2-512']);
2768                     return $this->privatekey_login($username, $privatekey);
2769                 }
2770                 $this->auth_methods_to_continue = $auth_methods;
2771                 $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE';
2772                 return false;
2773             case NET_SSH2_MSG_USERAUTH_PK_OK:
2774                 // we'll just take it on faith that the public key blob and the public key algorithm name are as
2775                 // they should be
2776                 $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
2777                 break;
2778             case NET_SSH2_MSG_USERAUTH_SUCCESS:
2779                 $this->bitmap |= self::MASK_LOGIN;
2780                 return true;
2781         }
2782 
2783         $packet = $part1 . chr(1) . $part2;
2784         $privatekey = $privatekey->withHash($hash);
2785         $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet);
2786         if ($publickey instanceof RSA) {
2787             $signature = Strings::packSSH2('ss', $signatureType, $signature);
2788         }
2789         $packet .= Strings::packSSH2('s', $signature);
2790 
2791         $this->send_binary_packet($packet);
2792 
2793         $response = $this->get_binary_packet_or_close(
2794             NET_SSH2_MSG_USERAUTH_SUCCESS,
2795             NET_SSH2_MSG_USERAUTH_FAILURE
2796         );
2797 
2798         list($type) = Strings::unpackSSH2('C', $response);
2799         switch ($type) {
2800             case NET_SSH2_MSG_USERAUTH_FAILURE:
2801                 // either the login is bad or the server employs multi-factor authentication
2802                 list($auth_methods) = Strings::unpackSSH2('L', $response);
2803                 $this->auth_methods_to_continue = $auth_methods;
2804                 return false;
2805             case NET_SSH2_MSG_USERAUTH_SUCCESS:
2806                 $this->bitmap |= self::MASK_LOGIN;
2807                 return true;
2808         }
2809     }
2810 
2811     /**
2812      * Return the currently configured timeout
2813      *
2814      * @return int
2815      */
2816     public function getTimeout()
2817     {
2818         return $this->timeout;
2819     }
2820 
2821     /**
2822      * Set Timeout
2823      *
2824      * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely.  setTimeout() makes it so it'll timeout.
2825      * Setting $timeout to false or 0 will revert to the default socket timeout.
2826      *
2827      * @param mixed $timeout
2828      */
2829     public function setTimeout($timeout)
2830     {
2831         $this->timeout = $this->curTimeout = $timeout;
2832     }
2833 
2834     /**
2835      * Set Keep Alive
2836      *
2837      * Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number.
2838      *
2839      * @param int $interval
2840      */
2841     public function setKeepAlive($interval)
2842     {
2843         $this->keepAlive = $interval;
2844     }
2845 
2846     /**
2847      * Get the output from stdError
2848      *
2849      */
2850     public function getStdError()
2851     {
2852         return $this->stdErrorLog;
2853     }
2854 
2855     /**
2856      * Execute Command
2857      *
2858      * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
2859      * In all likelihood, this is not a feature you want to be taking advantage of.
2860      *
2861      * @param string $command
2862      * @param callable $callback
2863      * @return string|bool
2864      * @psalm-return ($callback is callable ? bool : string|bool)
2865      * @throws \RuntimeException on connection error
2866      */
2867     public function exec($command, $callback = null)
2868     {
2869         $this->curTimeout = $this->timeout;
2870         $this->is_timeout = false;
2871         $this->stdErrorLog = '';
2872 
2873         if (!$this->isAuthenticated()) {
2874             return false;
2875         }
2876 
2877         //if ($this->isPTYOpen()) {
2878         //    throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
2879         //}
2880 
2881         $this->open_channel(self::CHANNEL_EXEC);
2882 
2883         if ($this->request_pty === true) {
2884             $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
2885             $packet = Strings::packSSH2(
2886                 'CNsCsN4s',
2887                 NET_SSH2_MSG_CHANNEL_REQUEST,
2888                 $this->server_channels[self::CHANNEL_EXEC],
2889                 'pty-req',
2890                 1,
2891                 $this->term,
2892                 $this->windowColumns,
2893                 $this->windowRows,
2894                 0,
2895                 0,
2896                 $terminal_modes
2897             );
2898 
2899             $this->send_binary_packet($packet);
2900 
2901             $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2902             if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2903                 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
2904                 throw new \RuntimeException('Unable to request pseudo-terminal');
2905             }
2906         }
2907 
2908         // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
2909         // down.  the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
2910         // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
2911         // then immediately terminate.  without such a request exec() will loop indefinitely.  the ping process won't end but
2912         // neither will your script.
2913 
2914         // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
2915         // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
2916         // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA.  RFC4254#section-5.2 corroborates.
2917         $packet = Strings::packSSH2(
2918             'CNsCs',
2919             NET_SSH2_MSG_CHANNEL_REQUEST,
2920             $this->server_channels[self::CHANNEL_EXEC],
2921             'exec',
2922             1,
2923             $command
2924         );
2925         $this->send_binary_packet($packet);
2926 
2927         $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
2928 
2929         if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
2930             return false;
2931         }
2932 
2933         $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
2934 
2935         if ($this->request_pty === true) {
2936             $this->channel_id_last_interactive = self::CHANNEL_EXEC;
2937             return true;
2938         }
2939 
2940         $output = '';
2941         while (true) {
2942             $temp = $this->get_channel_packet(self::CHANNEL_EXEC);
2943             switch (true) {
2944                 case $temp === true:
2945                     return is_callable($callback) ? true : $output;
2946                 case $temp === false:
2947                     return false;
2948                 default:
2949                     if (is_callable($callback)) {
2950                         if ($callback($temp) === true) {
2951                             $this->close_channel(self::CHANNEL_EXEC);
2952                             return true;
2953                         }
2954                     } else {
2955                         $output .= $temp;
2956                     }
2957             }
2958         }
2959     }
2960 
2961     /**
2962      * How many channels are currently open?
2963      *
2964      * @return int
2965      */
2966     public function getOpenChannelCount()
2967     {
2968         return $this->channelCount;
2969     }
2970 
2971     /**
2972      * Opens a channel
2973      *
2974      * @param string $channel
2975      * @param bool $skip_extended
2976      * @return bool
2977      */
2978     protected function open_channel($channel, $skip_extended = false)
2979     {
2980         if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
2981             throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
2982         }
2983 
2984         $this->channelCount++;
2985 
2986         if ($this->channelCount > 1 && $this->errorOnMultipleChannels) {
2987             throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels");
2988         }
2989 
2990         // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
2991         // be adjusted".  0x7FFFFFFF is, at 2GB, the max size.  technically, it should probably be decremented, but,
2992         // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
2993         // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
2994         $this->window_size_server_to_client[$channel] = $this->window_size;
2995         // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
2996         // uses 0x4000, that's what will be used here, as well.
2997         $packet_size = 0x4000;
2998 
2999         $packet = Strings::packSSH2(
3000             'CsN3',
3001             NET_SSH2_MSG_CHANNEL_OPEN,
3002             'session',
3003             $channel,
3004             $this->window_size_server_to_client[$channel],
3005             $packet_size
3006         );
3007 
3008         $this->send_binary_packet($packet);
3009 
3010         $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN;
3011 
3012         return $this->get_channel_packet($channel, $skip_extended);
3013     }
3014 
3015     /**
3016      * Creates an interactive shell
3017      *
3018      * Returns bool(true) if the shell was opened.
3019      * Returns bool(false) if the shell was already open.
3020      *
3021      * @see self::isShellOpen()
3022      * @see self::read()
3023      * @see self::write()
3024      * @return bool
3025      * @throws InsufficientSetupException if not authenticated
3026      * @throws \UnexpectedValueException on receipt of unexpected packets
3027      * @throws \RuntimeException on other errors
3028      */
3029     public function openShell()
3030     {
3031         if (!$this->isAuthenticated()) {
3032             throw new InsufficientSetupException('Operation disallowed prior to login()');
3033         }
3034 
3035         $this->open_channel(self::CHANNEL_SHELL);
3036 
3037         $terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
3038         $packet = Strings::packSSH2(
3039             'CNsbsN4s',
3040             NET_SSH2_MSG_CHANNEL_REQUEST,
3041             $this->server_channels[self::CHANNEL_SHELL],
3042             'pty-req',
3043             true, // want reply
3044             $this->term,
3045             $this->windowColumns,
3046             $this->windowRows,
3047             0,
3048             0,
3049             $terminal_modes
3050         );
3051 
3052         $this->send_binary_packet($packet);
3053 
3054         $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
3055 
3056         if (!$this->get_channel_packet(self::CHANNEL_SHELL)) {
3057             throw new \RuntimeException('Unable to request pty');
3058         }
3059 
3060         $packet = Strings::packSSH2(
3061             'CNsb',
3062             NET_SSH2_MSG_CHANNEL_REQUEST,
3063             $this->server_channels[self::CHANNEL_SHELL],
3064             'shell',
3065             true // want reply
3066         );
3067         $this->send_binary_packet($packet);
3068 
3069         $response = $this->get_channel_packet(self::CHANNEL_SHELL);
3070         if ($response === false) {
3071             throw new \RuntimeException('Unable to request shell');
3072         }
3073 
3074         $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
3075 
3076         $this->channel_id_last_interactive = self::CHANNEL_SHELL;
3077 
3078         $this->bitmap |= self::MASK_SHELL;
3079 
3080         return true;
3081     }
3082 
3083     /**
3084      * Return the channel to be used with read(), write(), and reset(), if none were specified
3085      * @deprecated for lack of transparency in intended channel target, to be potentially replaced
3086      *             with method which guarantees open-ness of all yielded channels and throws
3087      *             error for multiple open channels
3088      * @see self::read()
3089      * @see self::write()
3090      * @return int
3091      */
3092     private function get_interactive_channel()
3093     {
3094         switch (true) {
3095             case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
3096                 return self::CHANNEL_SUBSYSTEM;
3097             case $this->is_channel_status_data(self::CHANNEL_EXEC):
3098                 return self::CHANNEL_EXEC;
3099             default:
3100                 return self::CHANNEL_SHELL;
3101         }
3102     }
3103 
3104     /**
3105      * Indicates the DATA status on the given channel
3106      *
3107      * @param int $channel The channel number to evaluate
3108      * @return bool
3109      */
3110     private function is_channel_status_data($channel)
3111     {
3112         return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
3113     }
3114 
3115     /**
3116      * Return an available open channel
3117      *
3118      * @return int
3119      */
3120     private function get_open_channel()
3121     {
3122         $channel = self::CHANNEL_EXEC;
3123         do {
3124             if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_OPEN) {
3125                 return $channel;
3126             }
3127         } while ($channel++ < self::CHANNEL_SUBSYSTEM);
3128 
3129         return false;
3130     }
3131 
3132     /**
3133      * Request agent forwarding of remote server
3134      *
3135      * @return bool
3136      */
3137     public function requestAgentForwarding()
3138     {
3139         $request_channel = $this->get_open_channel();
3140         if ($request_channel === false) {
3141             return false;
3142         }
3143 
3144         $packet = Strings::packSSH2(
3145             'CNsC',
3146             NET_SSH2_MSG_CHANNEL_REQUEST,
3147             $this->server_channels[$request_channel],
3148             'auth-agent-req@openssh.com',
3149             1
3150         );
3151 
3152         $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
3153 
3154         $this->send_binary_packet($packet);
3155 
3156         if (!$this->get_channel_packet($request_channel)) {
3157             return false;
3158         }
3159 
3160         $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
3161 
3162         return true;
3163     }
3164 
3165     /**
3166      * Returns the output of an interactive shell
3167      *
3168      * Returns when there's a match for $expect, which can take the form of a string literal or,
3169      * if $mode == self::READ_REGEX, a regular expression.
3170      *
3171      * If not specifying a channel, an open interactive channel will be selected, or, if there are
3172      * no open channels, an interactive shell will be created. If there are multiple open
3173      * interactive channels, a legacy behavior will apply in which channel selection prioritizes
3174      * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
3175      * channels, callers are discouraged from relying on this legacy behavior and should specify
3176      * the intended channel.
3177      *
3178      * @see self::write()
3179      * @param string $expect
3180      * @param int $mode One of the self::READ_* constants
3181      * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3182      * @return string|bool|null
3183      * @throws \RuntimeException on connection error
3184      * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
3185      */
3186     public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
3187     {
3188         if (!$this->isAuthenticated()) {
3189             throw new InsufficientSetupException('Operation disallowed prior to login()');
3190         }
3191 
3192         $this->curTimeout = $this->timeout;
3193         $this->is_timeout = false;
3194 
3195         if ($channel === null) {
3196             $channel = $this->get_interactive_channel();
3197         }
3198 
3199         if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) {
3200             if ($channel != self::CHANNEL_SHELL) {
3201                 throw new InsufficientSetupException('Data is not available on channel');
3202             } elseif (!$this->openShell()) {
3203                 throw new \RuntimeException('Unable to initiate an interactive shell session');
3204             }
3205         }
3206 
3207         if ($mode == self::READ_NEXT) {
3208             return $this->get_channel_packet($channel);
3209         }
3210 
3211         $match = $expect;
3212         while (true) {
3213             if ($mode == self::READ_REGEX) {
3214                 preg_match($expect, substr($this->interactiveBuffer, -1024), $matches);
3215                 $match = isset($matches[0]) ? $matches[0] : '';
3216             }
3217             $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
3218             if ($pos !== false) {
3219                 return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
3220             }
3221             $response = $this->get_channel_packet($channel);
3222             if ($response === true) {
3223                 return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
3224             }
3225 
3226             $this->interactiveBuffer .= $response;
3227         }
3228     }
3229 
3230     /**
3231      * Inputs a command into an interactive shell.
3232      *
3233      * If not specifying a channel, an open interactive channel will be selected, or, if there are
3234      * no open channels, an interactive shell will be created. If there are multiple open
3235      * interactive channels, a legacy behavior will apply in which channel selection prioritizes
3236      * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
3237      * channels, callers are discouraged from relying on this legacy behavior and should specify
3238      * the intended channel.
3239      *
3240      * @see SSH2::read()
3241      * @param string $cmd
3242      * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3243      * @return void
3244      * @throws \RuntimeException on connection error
3245      * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
3246      * @throws TimeoutException if the write could not be completed within the requested self::setTimeout()
3247      */
3248     public function write($cmd, $channel = null)
3249     {
3250         if (!$this->isAuthenticated()) {
3251             throw new InsufficientSetupException('Operation disallowed prior to login()');
3252         }
3253 
3254         if ($channel === null) {
3255             $channel = $this->get_interactive_channel();
3256         }
3257 
3258         if (!$this->is_channel_status_data($channel)) {
3259             if ($channel != self::CHANNEL_SHELL) {
3260                 throw new InsufficientSetupException('Data is not available on channel');
3261             } elseif (!$this->openShell()) {
3262                 throw new \RuntimeException('Unable to initiate an interactive shell session');
3263             }
3264         }
3265 
3266         $this->curTimeout = $this->timeout;
3267         $this->is_timeout = false;
3268         $this->send_channel_packet($channel, $cmd);
3269     }
3270 
3271     /**
3272      * Start a subsystem.
3273      *
3274      * Right now only one subsystem at a time is supported. To support multiple subsystem's stopSubsystem() could accept
3275      * a string that contained the name of the subsystem, but at that point, only one subsystem of each type could be opened.
3276      * To support multiple subsystem's of the same name maybe it'd be best if startSubsystem() generated a new channel id and
3277      * returns that and then that that was passed into stopSubsystem() but that'll be saved for a future date and implemented
3278      * if there's sufficient demand for such a feature.
3279      *
3280      * @see self::stopSubsystem()
3281      * @param string $subsystem
3282      * @return bool
3283      */
3284     public function startSubsystem($subsystem)
3285     {
3286         $this->open_channel(self::CHANNEL_SUBSYSTEM);
3287 
3288         $packet = Strings::packSSH2(
3289             'CNsCs',
3290             NET_SSH2_MSG_CHANNEL_REQUEST,
3291             $this->server_channels[self::CHANNEL_SUBSYSTEM],
3292             'subsystem',
3293             1,
3294             $subsystem
3295         );
3296         $this->send_binary_packet($packet);
3297 
3298         $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
3299 
3300         if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
3301             return false;
3302         }
3303 
3304         $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
3305 
3306         $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
3307 
3308         return true;
3309     }
3310 
3311     /**
3312      * Stops a subsystem.
3313      *
3314      * @see self::startSubsystem()
3315      * @return bool
3316      */
3317     public function stopSubsystem()
3318     {
3319         if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
3320             $this->close_channel(self::CHANNEL_SUBSYSTEM);
3321         }
3322         return true;
3323     }
3324 
3325     /**
3326      * Closes a channel
3327      *
3328      * If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
3329      *
3330      * If not specifying a channel, an open interactive channel will be selected. If there are
3331      * multiple open interactive channels, a legacy behavior will apply in which channel selection
3332      * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
3333      * interactive channels, callers are discouraged from relying on this legacy behavior and
3334      * should specify the intended channel.
3335      *
3336      * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
3337      * @return void
3338      */
3339     public function reset($channel = null)
3340     {
3341         if ($channel === null) {
3342             $channel = $this->get_interactive_channel();
3343         }
3344         if ($this->isInteractiveChannelOpen($channel)) {
3345             $this->close_channel($channel);
3346         }
3347     }
3348 
3349     /**
3350      * Is timeout?
3351      *
3352      * Did exec() or read() return because they timed out or because they encountered the end?
3353      *
3354      */
3355     public function isTimeout()
3356     {
3357         return $this->is_timeout;
3358     }
3359 
3360     /**
3361      * Disconnect
3362      *
3363      */
3364     public function disconnect()
3365     {
3366         $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3367         if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
3368             fclose($this->realtime_log_file);
3369         }
3370         unset(self::$connections[$this->getResourceId()]);
3371     }
3372 
3373     /**
3374      * Destructor.
3375      *
3376      * Will be called, automatically, if you're supporting just PHP5.  If you're supporting PHP4, you'll need to call
3377      * disconnect().
3378      *
3379      */
3380     public function __destruct()
3381     {
3382         $this->disconnect();
3383     }
3384 
3385     /**
3386      * Is the connection still active?
3387      *
3388      * $level has 3x possible values:
3389      * 0 (default): phpseclib takes a passive approach to see if the connection is still active by calling feof()
3390      *    on the socket
3391      * 1: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_IGNORE
3392      *    packet that doesn't require a response
3393      * 2: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_CHANNEL_OPEN
3394      *    packet and imediately trying to close that channel. some routers, in particular, however, will only let you
3395      *    open one channel, so this approach could yield false positives
3396      *
3397      * @param int $level
3398      * @return bool
3399      */
3400     public function isConnected($level = 0)
3401     {
3402         if (!is_int($level) || $level < 0 || $level > 2) {
3403             throw new \InvalidArgumentException('$level must be 0, 1 or 2');
3404         }
3405 
3406         if ($level == 0) {
3407             return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
3408         }
3409         try {
3410             if ($level == 1) {
3411                 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
3412             } else {
3413                 $this->open_channel(self::CHANNEL_KEEP_ALIVE);
3414                 $this->close_channel(self::CHANNEL_KEEP_ALIVE);
3415             }
3416             return true;
3417         } catch (\Exception $e) {
3418             return false;
3419         }
3420     }
3421 
3422     /**
3423      * Have you successfully been logged in?
3424      *
3425      * @return bool
3426      */
3427     public function isAuthenticated()
3428     {
3429         return (bool) ($this->bitmap & self::MASK_LOGIN);
3430     }
3431 
3432     /**
3433      * Is the interactive shell active?
3434      *
3435      * @return bool
3436      */
3437     public function isShellOpen()
3438     {
3439         return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
3440     }
3441 
3442     /**
3443      * Is the exec pty active?
3444      *
3445      * @return bool
3446      */
3447     public function isPTYOpen()
3448     {
3449         return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
3450     }
3451 
3452     /**
3453      * Is the given interactive channel active?
3454      *
3455      * @param int $channel Channel id returned by self::getInteractiveChannelId()
3456      * @return bool
3457      */
3458     public function isInteractiveChannelOpen($channel)
3459     {
3460         return $this->isAuthenticated() && $this->is_channel_status_data($channel);
3461     }
3462 
3463     /**
3464      * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
3465      * Returns 0 if no interactive channel has been opened.
3466      *
3467      * @see self::isInteractiveChannelOpen()
3468      * @return int
3469      */
3470     public function getInteractiveChannelId()
3471     {
3472         return $this->channel_id_last_interactive;
3473     }
3474 
3475     /**
3476      * Pings a server connection, or tries to reconnect if the connection has gone down
3477      *
3478      * Inspired by http://php.net/manual/en/mysqli.ping.php
3479      *
3480      * @return bool
3481      */
3482     public function ping()
3483     {
3484         if (!$this->isAuthenticated()) {
3485             if (!empty($this->auth)) {
3486                 return $this->reconnect();
3487             }
3488             return false;
3489         }
3490 
3491         try {
3492             $this->open_channel(self::CHANNEL_KEEP_ALIVE);
3493         } catch (\RuntimeException $e) {
3494             return $this->reconnect();
3495         }
3496 
3497         $this->close_channel(self::CHANNEL_KEEP_ALIVE);
3498         return true;
3499     }
3500 
3501     /**
3502      * In situ reconnect method
3503      *
3504      * @return boolean
3505      */
3506     private function reconnect()
3507     {
3508         $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3509         $this->connect();
3510         foreach ($this->auth as $auth) {
3511             $result = $this->login(...$auth);
3512         }
3513         return $result;
3514     }
3515 
3516     /**
3517      * Resets a connection for re-use
3518      */
3519     protected function reset_connection()
3520     {
3521         if (is_resource($this->fsock) && get_resource_type($this->fsock) === 'stream') {
3522             fclose($this->fsock);
3523         }
3524         $this->fsock = null;
3525         $this->bitmap = 0;
3526         $this->binary_packet_buffer = null;
3527         $this->decrypt = $this->encrypt = false;
3528         $this->decrypt_block_size = $this->encrypt_block_size = 8;
3529         $this->hmac_check = $this->hmac_create = false;
3530         $this->hmac_size = false;
3531         $this->session_id = false;
3532         $this->last_packet = null;
3533         $this->get_seq_no = $this->send_seq_no = 0;
3534         $this->channel_status = [];
3535         $this->channel_id_last_interactive = 0;
3536         $this->channel_buffers = [];
3537         $this->channel_buffers_write = [];
3538     }
3539 
3540     /**
3541      * @return int[] second and microsecond stream timeout options based on user-requested timeout and keep-alive, or the default socket timeout by default, which mirrors PHP socket streams.
3542      */
3543     private function get_stream_timeout()
3544     {
3545         $sec = ini_get('default_socket_timeout');
3546         $usec = 0;
3547         if ($this->curTimeout > 0) {
3548             $sec = (int) floor($this->curTimeout);
3549             $usec = (int) (1000000 * ($this->curTimeout - $sec));
3550         }
3551         if ($this->keepAlive > 0) {
3552             $elapsed = microtime(true) - $this->last_packet;
3553             $timeout = max($this->keepAlive - $elapsed, 0);
3554             if (!$this->curTimeout || $timeout < $this->curTimeout) {
3555                 $sec = (int) floor($timeout);
3556                 $usec = (int) (1000000 * ($timeout - $sec));
3557             }
3558         }
3559         return [$sec, $usec];
3560     }
3561 
3562     /**
3563      * Retrieves the next packet with added timeout and type handling
3564      *
3565      * @param string $message_types Message types to enforce in response, closing if not met
3566      * @return string
3567      * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
3568      */
3569     private function get_binary_packet_or_close(...$message_types)
3570     {
3571         try {
3572             $packet = $this->get_binary_packet();
3573             if (count($message_types) > 0 && !in_array(ord($packet[0]), $message_types)) {
3574                 $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3575                 throw new ConnectionClosedException('Bad message type. Expected: #'
3576                     . implode(', #', $message_types) . '. Got: #' . ord($packet[0]));
3577             }
3578             return $packet;
3579         } catch (TimeoutException $e) {
3580             $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
3581             throw new ConnectionClosedException('Connection closed due to timeout');
3582         }
3583     }
3584 
3585     /**
3586      * Gets Binary Packets
3587      *
3588      * See '6. Binary Packet Protocol' of rfc4253 for more info.
3589      *
3590      * @see self::_send_binary_packet()
3591      * @return string
3592      * @throws TimeoutException If user requested timeout was reached while waiting for next packet
3593      * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
3594      */
3595     private function get_binary_packet()
3596     {
3597         if (!is_resource($this->fsock)) {
3598             throw new \InvalidArgumentException('fsock is not a resource.');
3599         }
3600         if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
3601             return $this->filter(array_shift($this->kex_buffer));
3602         }
3603         if ($this->binary_packet_buffer == null) {
3604             // buffer the packet to permit continued reads across timeouts
3605             $this->binary_packet_buffer = (object) [
3606                 'read_time' => 0, // the time to read the packet from the socket
3607                 'raw' => '', // the raw payload read from the socket
3608                 'plain' => '', // the packet in plain text, excluding packet_length header
3609                 'packet_length' => null, // the packet_length value pulled from the payload
3610                 'size' => $this->decrypt_block_size, // the total size of this packet to be read from the socket
3611                                                      // initialize to read single block until packet_length is available
3612             ];
3613         }
3614         $packet = $this->binary_packet_buffer;
3615         while (strlen($packet->raw) < $packet->size) {
3616             if (feof($this->fsock)) {
3617                 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3618                 throw new ConnectionClosedException('Connection closed by server');
3619             }
3620             if ($this->curTimeout < 0) {
3621                 $this->is_timeout = true;
3622                 throw new TimeoutException('Timed out waiting for server');
3623             }
3624             $this->send_keep_alive();
3625 
3626             list($sec, $usec) = $this->get_stream_timeout();
3627             stream_set_timeout($this->fsock, $sec, $usec);
3628             $start = microtime(true);
3629             $raw = stream_get_contents($this->fsock, $packet->size - strlen($packet->raw));
3630             $elapsed = microtime(true) - $start;
3631             $packet->read_time += $elapsed;
3632             if ($this->curTimeout > 0) {
3633                 $this->curTimeout -= $elapsed;
3634             }
3635             if ($raw === false) {
3636                 $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3637                 throw new ConnectionClosedException('Connection closed by server');
3638             } elseif (!strlen($raw)) {
3639                 continue;
3640             }
3641             $packet->raw .= $raw;
3642             if (!$packet->packet_length) {
3643                 $this->get_binary_packet_size($packet);
3644             }
3645         }
3646 
3647         if (strlen($packet->raw) != $packet->size) {
3648             throw new \RuntimeException('Size of packet was not expected length');
3649         }
3650         // destroy buffer as packet represents the entire payload and should be processed in full
3651         $this->binary_packet_buffer = null;
3652         // copy the raw payload, so as not to destroy original
3653         $raw = $packet->raw;
3654         if ($this->hmac_check instanceof Hash) {
3655             $hmac = Strings::pop($raw, $this->hmac_size);
3656         }
3657         $packet_length_header_size = 4;
3658         if ($this->decrypt) {
3659             switch ($this->decryptName) {
3660                 case 'aes128-gcm@openssh.com':
3661                 case 'aes256-gcm@openssh.com':
3662                     $this->decrypt->setNonce(
3663                         $this->decryptFixedPart .
3664                         $this->decryptInvocationCounter
3665                     );
3666                     Strings::increment_str($this->decryptInvocationCounter);
3667                     $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
3668                     $this->decrypt->setTag(Strings::pop($raw, $this->decrypt_block_size));
3669                     $packet->plain = $this->decrypt->decrypt($raw);
3670                     break;
3671                 case 'chacha20-poly1305@openssh.com':
3672                     // This should be impossible, but we are checking anyway to narrow the type for Psalm.
3673                     if (!($this->decrypt instanceof ChaCha20)) {
3674                         throw new \LogicException('$this->decrypt is not a ' . ChaCha20::class);
3675                     }
3676                     $this->decrypt->setNonce(pack('N2', 0, $this->get_seq_no));
3677                     $this->decrypt->setCounter(0);
3678                     // this is the same approach that's implemented in Salsa20::createPoly1305Key()
3679                     // but we don't want to use the same AEAD construction that RFC8439 describes
3680                     // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
3681                     $this->decrypt->setPoly1305Key(
3682                         $this->decrypt->encrypt(str_repeat("\0", 32))
3683                     );
3684                     $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
3685                     $this->decrypt->setCounter(1);
3686                     $this->decrypt->setTag(Strings::pop($raw, 16));
3687                     $packet->plain = $this->decrypt->decrypt($raw);
3688                     break;
3689                 default:
3690                     if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
3691                         // first block was already decrypted for contained packet_length header
3692                         Strings::shift($raw, $this->decrypt_block_size);
3693                         if (strlen($raw) > 0) {
3694                             $packet->plain .= $this->decrypt->decrypt($raw);
3695                         }
3696                     } else {
3697                         Strings::shift($raw, $packet_length_header_size);
3698                         $packet->plain = $this->decrypt->decrypt($raw);
3699                     }
3700                     break;
3701             }
3702         } else {
3703             Strings::shift($raw, $packet_length_header_size);
3704             $packet->plain = $raw;
3705         }
3706         if ($this->hmac_check instanceof Hash) {
3707             $reconstructed = !$this->hmac_check_etm ?
3708                 pack('Na*', $packet->packet_length, $packet->plain) :
3709                 substr($packet->raw, 0, -$this->hmac_size);
3710             if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
3711                 $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
3712                 if ($hmac != $this->hmac_check->hash($reconstructed)) {
3713                     $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
3714                     throw new ConnectionClosedException('Invalid UMAC');
3715                 }
3716             } else {
3717                 if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
3718                     $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
3719                     throw new ConnectionClosedException('Invalid HMAC');
3720                 }
3721             }
3722         }
3723         $padding_length = 0;
3724         $payload = $packet->plain;
3725         extract(unpack('Cpadding_length', Strings::shift($payload, 1)));
3726         if ($padding_length > 0) {
3727             Strings::pop($payload, $padding_length);
3728         }
3729 
3730         if (!$this->keyExchangeInProgress) {
3731             $this->bytesTransferredSinceLastKEX += $packet->packet_length + $padding_length + 5;
3732         }
3733 
3734         if (empty($payload)) {
3735             $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3736             throw new ConnectionClosedException('Plaintext is too short');
3737         }
3738 
3739         switch ($this->decompress) {
3740             case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
3741                 if (!$this->isAuthenticated()) {
3742                     break;
3743                 }
3744                 // fall-through
3745             case self::NET_SSH2_COMPRESSION_ZLIB:
3746                 if ($this->regenerate_decompression_context) {
3747                     $this->regenerate_decompression_context = false;
3748 
3749                     $cmf = ord($payload[0]);
3750                     $cm = $cmf & 0x0F;
3751                     if ($cm != 8) { // deflate
3752                         throw new UnsupportedAlgorithmException("Only CM = 8 ('deflate') is supported ($cm)");
3753                     }
3754                     $cinfo = ($cmf & 0xF0) >> 4;
3755                     if ($cinfo > 7) {
3756                         throw new \RuntimeException("CINFO above 7 is not allowed ($cinfo)");
3757                     }
3758                     $windowSize = 1 << ($cinfo + 8);
3759 
3760                     $flg = ord($payload[1]);
3761                     //$fcheck = $flg && 0x0F;
3762                     if ((($cmf << 8) | $flg) % 31) {
3763                         throw new \RuntimeException('fcheck failed');
3764                     }
3765                     $fdict = boolval($flg & 0x20);
3766                     $flevel = ($flg & 0xC0) >> 6;
3767 
3768                     $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]);
3769                     $payload = substr($payload, 2);
3770                 }
3771                 if ($this->decompress_context) {
3772                     $payload = inflate_add($this->decompress_context, $payload, ZLIB_PARTIAL_FLUSH);
3773                 }
3774         }
3775 
3776         $this->get_seq_no++;
3777 
3778         if (defined('NET_SSH2_LOGGING')) {
3779             $current = microtime(true);
3780             $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
3781             $message_number = '<- ' . $message_number .
3782                               ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($packet->read_time, 4) . 's)';
3783             $this->append_log($message_number, $payload);
3784         }
3785         $this->last_packet = microtime(true);
3786 
3787         if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
3788             $this->key_exchange();
3789         }
3790 
3791         // don't filter if we're in the middle of a key exchange (since _filter might send out packets)
3792         return $this->keyExchangeInProgress ? $payload : $this->filter($payload);
3793     }
3794 
3795     /**
3796      * @param object $packet The packet object being constructed, passed by reference
3797      *        The size, packet_length, and plain properties of this object may be modified in processing
3798      * @throws InvalidPacketLengthException if the packet length header is invalid
3799      */
3800     private function get_binary_packet_size(&$packet)
3801     {
3802         $packet_length_header_size = 4;
3803         if (strlen($packet->raw) < $packet_length_header_size) {
3804             return;
3805         }
3806         $packet_length = 0;
3807         $added_validation_length = 0; // indicates when the packet length header is included when validating packet length against block size
3808         if ($this->decrypt) {
3809             switch ($this->decryptName) {
3810                 case 'aes128-gcm@openssh.com':
3811                 case 'aes256-gcm@openssh.com':
3812                     extract(unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size)));
3813                     $packet->size = $packet_length_header_size + $packet_length + $this->decrypt_block_size; // expect tag
3814                     break;
3815                 case 'chacha20-poly1305@openssh.com':
3816                     $this->lengthDecrypt->setNonce(pack('N2', 0, $this->get_seq_no));
3817                     $packet_length_header = $this->lengthDecrypt->decrypt(substr($packet->raw, 0, $packet_length_header_size));
3818                     extract(unpack('Npacket_length', $packet_length_header));
3819                     $packet->size = $packet_length_header_size + $packet_length + 16; // expect tag
3820                     break;
3821                 default:
3822                     if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
3823                         if (strlen($packet->raw) < $this->decrypt_block_size) {
3824                             return;
3825                         }
3826                         $packet->plain = $this->decrypt->decrypt(substr($packet->raw, 0, $this->decrypt_block_size));
3827                         extract(unpack('Npacket_length', Strings::shift($packet->plain, $packet_length_header_size)));
3828                         $packet->size = $packet_length_header_size + $packet_length;
3829                         $added_validation_length = $packet_length_header_size;
3830                     } else {
3831                         extract(unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size)));
3832                         $packet->size = $packet_length_header_size + $packet_length;
3833                     }
3834                     break;
3835             }
3836         } else {
3837             extract(unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size)));
3838             $packet->size = $packet_length_header_size + $packet_length;
3839             $added_validation_length = $packet_length_header_size;
3840         }
3841         // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
3842         // "implementations SHOULD check that the packet length is reasonable"
3843         // PuTTY uses 0x9000 as the actual max packet size and so to shall we
3844         if (
3845             $packet_length <= 0 || $packet_length > 0x9000
3846             || ($packet_length + $added_validation_length) % $this->decrypt_block_size != 0
3847         ) {
3848             $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
3849             throw new InvalidPacketLengthException('Invalid packet length');
3850         }
3851         if ($this->hmac_check instanceof Hash) {
3852             $packet->size += $this->hmac_size;
3853         }
3854         $packet->packet_length = $packet_length;
3855     }
3856 
3857     /**
3858      * Handle Disconnect
3859      *
3860      * Because some binary packets need to be ignored...
3861      *
3862      * @see self::filter()
3863      * @see self::key_exchange()
3864      * @return boolean
3865      * @access private
3866      */
3867     private function handleDisconnect($payload)
3868     {
3869         Strings::shift($payload, 1);
3870         list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
3871         $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
3872         $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
3873         throw new ConnectionClosedException('Connection closed by server');
3874     }
3875 
3876     /**
3877      * Filter Binary Packets
3878      *
3879      * Because some binary packets need to be ignored...
3880      *
3881      * @see self::_get_binary_packet()
3882      * @param string $payload
3883      * @return string
3884      */
3885     private function filter($payload)
3886     {
3887         switch (ord($payload[0])) {
3888             case NET_SSH2_MSG_DISCONNECT:
3889                 return $this->handleDisconnect($payload);
3890             case NET_SSH2_MSG_IGNORE:
3891                 $payload = $this->get_binary_packet();
3892                 break;
3893             case NET_SSH2_MSG_DEBUG:
3894                 Strings::shift($payload, 2); // second byte is "always_display"
3895                 list($message) = Strings::unpackSSH2('s', $payload);
3896                 $this->errors[] = "SSH_MSG_DEBUG: $message";
3897                 $payload = $this->get_binary_packet();
3898                 break;
3899             case NET_SSH2_MSG_UNIMPLEMENTED:
3900                 break; // return payload
3901             case NET_SSH2_MSG_KEXINIT:
3902                 // this is here for server initiated key re-exchanges after the initial key exchange
3903                 if ($this->session_id !== false) {
3904                     if (!$this->key_exchange($payload)) {
3905                         $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
3906                         throw new ConnectionClosedException('Key exchange failed');
3907                     }
3908                     $payload = $this->get_binary_packet();
3909                 }
3910                 break;
3911             case NET_SSH2_MSG_EXT_INFO:
3912                 Strings::shift($payload, 1);
3913                 list($nr_extensions) = Strings::unpackSSH2('N', $payload);
3914                 for ($i = 0; $i < $nr_extensions; $i++) {
3915                     list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $payload);
3916                     if ($extension_name == 'server-sig-algs') {
3917                         $this->supported_private_key_algorithms = explode(',', $extension_value);
3918                     }
3919                 }
3920                 $payload = $this->get_binary_packet();
3921         }
3922 
3923         // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in
3924         if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
3925             Strings::shift($payload, 1);
3926             list($this->banner_message) = Strings::unpackSSH2('s', $payload);
3927             $payload = $this->get_binary_packet();
3928         }
3929 
3930         // only called when we've already logged in
3931         if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) {
3932             switch (ord($payload[0])) {
3933                 case NET_SSH2_MSG_CHANNEL_REQUEST:
3934                     if (strlen($payload) == 31) {
3935                         extract(unpack('cpacket_type/Nchannel/Nlength', $payload));
3936                         if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) {
3937                             if (ord(substr($payload, 9 + $length))) { // want reply
3938                                 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
3939                             }
3940                             $payload = $this->get_binary_packet();
3941                         }
3942                     }
3943                     break;
3944                 case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
3945                     Strings::shift($payload, 1);
3946                     list($request_name) = Strings::unpackSSH2('s', $payload);
3947                     $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name";
3948                     $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE));
3949                     $payload = $this->get_binary_packet();
3950                     break;
3951                 case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
3952                     Strings::shift($payload, 1);
3953                     list($data, $server_channel) = Strings::unpackSSH2('sN', $payload);
3954                     switch ($data) {
3955                         case 'auth-agent':
3956                         case 'auth-agent@openssh.com':
3957                             if (isset($this->agent)) {
3958                                 $new_channel = self::CHANNEL_AGENT_FORWARD;
3959 
3960                                 list(
3961                                     $remote_window_size,
3962                                     $remote_maximum_packet_size
3963                                 ) = Strings::unpackSSH2('NN', $payload);
3964 
3965                                 $this->packet_size_client_to_server[$new_channel] = $remote_window_size;
3966                                 $this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
3967                                 $this->window_size_client_to_server[$new_channel] = $this->window_size;
3968 
3969                                 $packet_size = 0x4000;
3970 
3971                                 $packet = pack(
3972                                     'CN4',
3973                                     NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION,
3974                                     $server_channel,
3975                                     $new_channel,
3976                                     $packet_size,
3977                                     $packet_size
3978                                 );
3979 
3980                                 $this->server_channels[$new_channel] = $server_channel;
3981                                 $this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
3982                                 $this->send_binary_packet($packet);
3983                             }
3984                             break;
3985                         default:
3986                             $packet = Strings::packSSH2(
3987                                 'CN2ss',
3988                                 NET_SSH2_MSG_CHANNEL_OPEN_FAILURE,
3989                                 $server_channel,
3990                                 NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
3991                                 '', // description
3992                                 '' // language tag
3993                             );
3994                             $this->send_binary_packet($packet);
3995                     }
3996 
3997                     $payload = $this->get_binary_packet();
3998                     break;
3999             }
4000         }
4001 
4002         return $payload;
4003     }
4004 
4005     /**
4006      * Enable Quiet Mode
4007      *
4008      * Suppress stderr from output
4009      *
4010      */
4011     public function enableQuietMode()
4012     {
4013         $this->quiet_mode = true;
4014     }
4015 
4016     /**
4017      * Disable Quiet Mode
4018      *
4019      * Show stderr in output
4020      *
4021      */
4022     public function disableQuietMode()
4023     {
4024         $this->quiet_mode = false;
4025     }
4026 
4027     /**
4028      * Returns whether Quiet Mode is enabled or not
4029      *
4030      * @see self::enableQuietMode()
4031      * @see self::disableQuietMode()
4032      * @return bool
4033      */
4034     public function isQuietModeEnabled()
4035     {
4036         return $this->quiet_mode;
4037     }
4038 
4039     /**
4040      * Enable request-pty when using exec()
4041      *
4042      */
4043     public function enablePTY()
4044     {
4045         $this->request_pty = true;
4046     }
4047 
4048     /**
4049      * Disable request-pty when using exec()
4050      *
4051      */
4052     public function disablePTY()
4053     {
4054         if ($this->isPTYOpen()) {
4055             $this->close_channel(self::CHANNEL_EXEC);
4056         }
4057         $this->request_pty = false;
4058     }
4059 
4060     /**
4061      * Returns whether request-pty is enabled or not
4062      *
4063      * @see self::enablePTY()
4064      * @see self::disablePTY()
4065      * @return bool
4066      */
4067     public function isPTYEnabled()
4068     {
4069         return $this->request_pty;
4070     }
4071 
4072     /**
4073      * Gets channel data
4074      *
4075      * Returns the data as a string. bool(true) is returned if:
4076      *
4077      * - the server closes the channel
4078      * - if the connection times out
4079      * - if a window adjust packet is received on the given negated client channel
4080      * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
4081      * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
4082      * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
4083      *
4084      * bool(false) is returned if:
4085      *
4086      * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
4087      *
4088      * @param int $client_channel Specifies the channel to return data for, and data received
4089      *        on other channels is buffered. The respective negative value of a channel is
4090      *        also supported for the case that the caller is awaiting adjustment of the data
4091      *        window, and where data received on that respective channel is also buffered.
4092      * @param bool $skip_extended
4093      * @return mixed
4094      * @throws \RuntimeException on connection error
4095      */
4096     protected function get_channel_packet($client_channel, $skip_extended = false)
4097     {
4098         if (!empty($this->channel_buffers[$client_channel])) {
4099             switch ($this->channel_status[$client_channel]) {
4100                 case NET_SSH2_MSG_CHANNEL_REQUEST:
4101                     foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
4102                         switch (ord($packet[0])) {
4103                             case NET_SSH2_MSG_CHANNEL_SUCCESS:
4104                             case NET_SSH2_MSG_CHANNEL_FAILURE:
4105                                 unset($this->channel_buffers[$client_channel][$i]);
4106                                 return substr($packet, 1);
4107                         }
4108                     }
4109                     break;
4110                 default:
4111                     return substr(array_shift($this->channel_buffers[$client_channel]), 1);
4112             }
4113         }
4114 
4115         while (true) {
4116             try {
4117                 $response = $this->get_binary_packet();
4118             } catch (TimeoutException $e) {
4119                 return true;
4120             }
4121             list($type) = Strings::unpackSSH2('C', $response);
4122             if (strlen($response) >= 4) {
4123                 list($channel) = Strings::unpackSSH2('N', $response);
4124             }
4125 
4126             // will not be setup yet on incoming channel open request
4127             if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
4128                 $this->window_size_server_to_client[$channel] -= strlen($response);
4129 
4130                 // resize the window, if appropriate
4131                 if ($this->window_size_server_to_client[$channel] < 0) {
4132                 // PuTTY does something more analogous to the following:
4133                 //if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
4134                     $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
4135                     $this->send_binary_packet($packet);
4136                     $this->window_size_server_to_client[$channel] += $this->window_resize;
4137                 }
4138 
4139                 switch ($type) {
4140                     case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
4141                         list($window_size) = Strings::unpackSSH2('N', $response);
4142                         $this->window_size_client_to_server[$channel] += $window_size;
4143                         if ($channel == -$client_channel) {
4144                             return true;
4145                         }
4146 
4147                         continue 2;
4148                     case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
4149                         /*
4150                         if ($client_channel == self::CHANNEL_EXEC) {
4151                             $this->send_channel_packet($client_channel, chr(0));
4152                         }
4153                         */
4154                         // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
4155                         list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response);
4156                         $this->stdErrorLog .= $data;
4157                         if ($skip_extended || $this->quiet_mode) {
4158                             continue 2;
4159                         }
4160                         if ($client_channel == $channel && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA) {
4161                             return $data;
4162                         }
4163                         $this->channel_buffers[$channel][] = chr($type) . $data;
4164 
4165                         continue 2;
4166                     case NET_SSH2_MSG_CHANNEL_REQUEST:
4167                         if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) {
4168                             continue 2;
4169                         }
4170                         list($value) = Strings::unpackSSH2('s', $response);
4171                         switch ($value) {
4172                             case 'exit-signal':
4173                                 list(
4174                                     , // FALSE
4175                                     $signal_name,
4176                                     , // core dumped
4177                                     $error_message
4178                                 ) = Strings::unpackSSH2('bsbs', $response);
4179 
4180                                 $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name";
4181                                 if (strlen($error_message)) {
4182                                     $this->errors[count($this->errors) - 1] .= "\r\n$error_message";
4183                                 }
4184 
4185                                 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
4186                                 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4187 
4188                                 $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
4189 
4190                                 continue 3;
4191                             case 'exit-status':
4192                                 list(, $this->exit_status) = Strings::unpackSSH2('CN', $response);
4193 
4194                                 // "The client MAY ignore these messages."
4195                                 // -- http://tools.ietf.org/html/rfc4254#section-6.10
4196 
4197                                 continue 3;
4198                             default:
4199                                 // "Some systems may not implement signals, in which case they SHOULD ignore this message."
4200                                 //  -- http://tools.ietf.org/html/rfc4254#section-6.9
4201                                 continue 3;
4202                         }
4203                 }
4204 
4205                 switch ($this->channel_status[$channel]) {
4206                     case NET_SSH2_MSG_CHANNEL_OPEN:
4207                         switch ($type) {
4208                             case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
4209                                 list(
4210                                     $this->server_channels[$channel],
4211                                     $window_size,
4212                                     $this->packet_size_client_to_server[$channel]
4213                                 ) = Strings::unpackSSH2('NNN', $response);
4214 
4215                                 if ($window_size < 0) {
4216                                     $window_size &= 0x7FFFFFFF;
4217                                     $window_size += 0x80000000;
4218                                 }
4219                                 $this->window_size_client_to_server[$channel] = $window_size;
4220                                 $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended);
4221                                 $this->on_channel_open();
4222                                 return $result;
4223                             case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
4224                                 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4225                                 throw new \RuntimeException('Unable to open channel');
4226                             default:
4227                                 if ($client_channel == $channel) {
4228                                     $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4229                                     throw new \RuntimeException('Unexpected response to open request');
4230                                 }
4231                                 return $this->get_channel_packet($client_channel, $skip_extended);
4232                         }
4233                         break;
4234                     case NET_SSH2_MSG_CHANNEL_REQUEST:
4235                         switch ($type) {
4236                             case NET_SSH2_MSG_CHANNEL_SUCCESS:
4237                                 return true;
4238                             case NET_SSH2_MSG_CHANNEL_FAILURE:
4239                                 return false;
4240                             case NET_SSH2_MSG_CHANNEL_DATA:
4241                                 list($data) = Strings::unpackSSH2('s', $response);
4242                                 $this->channel_buffers[$channel][] = chr($type) . $data;
4243                                 return $this->get_channel_packet($client_channel, $skip_extended);
4244                             default:
4245                                 $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4246                                 throw new \RuntimeException('Unable to fulfill channel request');
4247                         }
4248                     case NET_SSH2_MSG_CHANNEL_CLOSE:
4249                         if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
4250                             return true;
4251                         }
4252                         return $this->get_channel_packet($client_channel, $skip_extended);
4253                 }
4254             }
4255 
4256             // ie. $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA
4257 
4258             switch ($type) {
4259                 case NET_SSH2_MSG_CHANNEL_DATA:
4260                     /*
4261                     if ($channel == self::CHANNEL_EXEC) {
4262                         // SCP requires null packets, such as this, be sent.  further, in the case of the ssh.com SSH server
4263                         // this actually seems to make things twice as fast.  more to the point, the message right after
4264                         // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
4265                         // in OpenSSH it slows things down but only by a couple thousandths of a second.
4266                         $this->send_channel_packet($channel, chr(0));
4267                     }
4268                     */
4269                     list($data) = Strings::unpackSSH2('s', $response);
4270 
4271                     if ($channel == self::CHANNEL_AGENT_FORWARD) {
4272                         $agent_response = $this->agent->forwardData($data);
4273                         if (!is_bool($agent_response)) {
4274                             $this->send_channel_packet($channel, $agent_response);
4275                         }
4276                         break;
4277                     }
4278 
4279                     if ($client_channel == $channel) {
4280                         return $data;
4281                     }
4282                     $this->channel_buffers[$channel][] = chr($type) . $data;
4283                     break;
4284                 case NET_SSH2_MSG_CHANNEL_CLOSE:
4285                     $this->curTimeout = 5;
4286 
4287                     $this->close_channel_bitmap($channel);
4288 
4289                     if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
4290                         $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
4291                     }
4292 
4293                     $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4294                     $this->channelCount--;
4295 
4296                     if ($client_channel == $channel) {
4297                         return true;
4298                     }
4299                     // fall-through
4300                 case NET_SSH2_MSG_CHANNEL_EOF:
4301                     break;
4302                 default:
4303                     $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4304                     throw new \RuntimeException("Error reading channel data ($type)");
4305             }
4306         }
4307     }
4308 
4309     /**
4310      * Sends Binary Packets
4311      *
4312      * See '6. Binary Packet Protocol' of rfc4253 for more info.
4313      *
4314      * @param string $data
4315      * @param string $logged
4316      * @see self::_get_binary_packet()
4317      * @return void
4318      */
4319     protected function send_binary_packet($data, $logged = null)
4320     {
4321         if (!is_resource($this->fsock) || feof($this->fsock)) {
4322             $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
4323             throw new ConnectionClosedException('Connection closed prematurely');
4324         }
4325 
4326         if (!isset($logged)) {
4327             $logged = $data;
4328         }
4329 
4330         switch ($this->compress) {
4331             case self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH:
4332                 if (!$this->isAuthenticated()) {
4333                     break;
4334                 }
4335                 // fall-through
4336             case self::NET_SSH2_COMPRESSION_ZLIB:
4337                 if (!$this->regenerate_compression_context) {
4338                     $header = '';
4339                 } else {
4340                     $this->regenerate_compression_context = false;
4341                     $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]);
4342                     $header = "\x78\x9C";
4343                 }
4344                 if ($this->compress_context) {
4345                     $data = $header . deflate_add($this->compress_context, $data, ZLIB_PARTIAL_FLUSH);
4346                 }
4347         }
4348 
4349         // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
4350         $packet_length = strlen($data) + 9;
4351         if ($this->encrypt && $this->encrypt->usesNonce()) {
4352             $packet_length -= 4;
4353         }
4354         // round up to the nearest $this->encrypt_block_size
4355         $packet_length += (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
4356         // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
4357         $padding_length = $packet_length - strlen($data) - 5;
4358         switch (true) {
4359             case $this->encrypt && $this->encrypt->usesNonce():
4360             case $this->hmac_create instanceof Hash && $this->hmac_create_etm:
4361                 $padding_length += 4;
4362                 $packet_length += 4;
4363         }
4364 
4365         $padding = Random::string($padding_length);
4366 
4367         // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
4368         $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
4369 
4370         $hmac = '';
4371         if ($this->hmac_create instanceof Hash && !$this->hmac_create_etm) {
4372             if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
4373                 $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
4374                 $hmac = $this->hmac_create->hash($packet);
4375             } else {
4376                 $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
4377             }
4378         }
4379 
4380         if ($this->encrypt) {
4381             switch ($this->encryptName) {
4382                 case 'aes128-gcm@openssh.com':
4383                 case 'aes256-gcm@openssh.com':
4384                     $this->encrypt->setNonce(
4385                         $this->encryptFixedPart .
4386                         $this->encryptInvocationCounter
4387                     );
4388                     Strings::increment_str($this->encryptInvocationCounter);
4389                     $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF"));
4390                     $packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
4391                     break;
4392                 case 'chacha20-poly1305@openssh.com':
4393                     // This should be impossible, but we are checking anyway to narrow the type for Psalm.
4394                     if (!($this->encrypt instanceof ChaCha20)) {
4395                         throw new \LogicException('$this->encrypt is not a ' . ChaCha20::class);
4396                     }
4397 
4398                     $nonce = pack('N2', 0, $this->send_seq_no);
4399 
4400                     $this->encrypt->setNonce($nonce);
4401                     $this->lengthEncrypt->setNonce($nonce);
4402 
4403                     $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF");
4404 
4405                     $this->encrypt->setCounter(0);
4406                     // this is the same approach that's implemented in Salsa20::createPoly1305Key()
4407                     // but we don't want to use the same AEAD construction that RFC8439 describes
4408                     // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
4409                     $this->encrypt->setPoly1305Key(
4410                         $this->encrypt->encrypt(str_repeat("\0", 32))
4411                     );
4412                     $this->encrypt->setAAD($length);
4413                     $this->encrypt->setCounter(1);
4414                     $packet = $length . $this->encrypt->encrypt(substr($packet, 4));
4415                     break;
4416                 default:
4417                     $packet = $this->hmac_create instanceof Hash && $this->hmac_create_etm ?
4418                         ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
4419                         $this->encrypt->encrypt($packet);
4420             }
4421         }
4422 
4423         if ($this->hmac_create instanceof Hash && $this->hmac_create_etm) {
4424             if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
4425                 $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
4426                 $hmac = $this->hmac_create->hash($packet);
4427             } else {
4428                 $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
4429             }
4430         }
4431 
4432         $this->send_seq_no++;
4433 
4434         $packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
4435 
4436         if (!$this->keyExchangeInProgress) {
4437             $this->bytesTransferredSinceLastKEX += strlen($packet);
4438         }
4439 
4440         $start = microtime(true);
4441         $sent = @fputs($this->fsock, $packet);
4442         $stop = microtime(true);
4443 
4444         if (defined('NET_SSH2_LOGGING')) {
4445             $current = microtime(true);
4446             $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
4447             $message_number = '-> ' . $message_number .
4448                               ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
4449             $this->append_log($message_number, $logged);
4450         }
4451         $this->last_packet = microtime(true);
4452 
4453         if (strlen($packet) != $sent) {
4454             $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
4455             $message = $sent === false ?
4456                 'Unable to write ' . strlen($packet) . ' bytes' :
4457                 "Only $sent of " . strlen($packet) . " bytes were sent";
4458             throw new \RuntimeException($message);
4459         }
4460 
4461         if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
4462             $this->key_exchange();
4463         }
4464     }
4465 
4466     /**
4467      * Sends a keep-alive message, if keep-alive is enabled and interval is met
4468      */
4469     private function send_keep_alive()
4470     {
4471         if ($this->bitmap & self::MASK_CONNECTED) {
4472             $elapsed = microtime(true) - $this->last_packet;
4473             if ($this->keepAlive > 0 && $elapsed >= $this->keepAlive) {
4474                 $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
4475             }
4476         }
4477     }
4478 
4479     /**
4480      * Logs data packets
4481      *
4482      * Makes sure that only the last 1MB worth of packets will be logged
4483      *
4484      * @param string $message_number
4485      * @param string $message
4486      */
4487     private function append_log($message_number, $message)
4488     {
4489         $this->append_log_helper(
4490             NET_SSH2_LOGGING,
4491             $message_number,
4492             $message,
4493             $this->message_number_log,
4494             $this->message_log,
4495             $this->log_size,
4496             $this->realtime_log_file,
4497             $this->realtime_log_wrap,
4498             $this->realtime_log_size
4499         );
4500     }
4501 
4502     /**
4503      * Logs data packet helper
4504      *
4505      * @param int $constant
4506      * @param string $message_number
4507      * @param string $message
4508      * @param array &$message_number_log
4509      * @param array &$message_log
4510      * @param int &$log_size
4511      * @param resource &$realtime_log_file
4512      * @param bool &$realtime_log_wrap
4513      * @param int &$realtime_log_size
4514      */
4515     protected function append_log_helper($constant, $message_number, $message, array &$message_number_log, array &$message_log, &$log_size, &$realtime_log_file, &$realtime_log_wrap, &$realtime_log_size)
4516     {
4517         // remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
4518         if (strlen($message_number) > 2) {
4519             Strings::shift($message);
4520         }
4521 
4522         switch ($constant) {
4523             // useful for benchmarks
4524             case self::LOG_SIMPLE:
4525                 $message_number_log[] = $message_number;
4526                 break;
4527             case self::LOG_SIMPLE_REALTIME:
4528                 echo $message_number;
4529                 echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
4530                 @flush();
4531                 @ob_flush();
4532                 break;
4533             // the most useful log for SSH2
4534             case self::LOG_COMPLEX:
4535                 $message_number_log[] = $message_number;
4536                 $log_size += strlen($message);
4537                 $message_log[] = $message;
4538                 while ($log_size > self::LOG_MAX_SIZE) {
4539                     $log_size -= strlen(array_shift($message_log));
4540                     array_shift($message_number_log);
4541                 }
4542                 break;
4543             // dump the output out realtime; packets may be interspersed with non packets,
4544             // passwords won't be filtered out and select other packets may not be correctly
4545             // identified
4546             case self::LOG_REALTIME:
4547                 switch (PHP_SAPI) {
4548                     case 'cli':
4549                         $start = $stop = "\r\n";
4550                         break;
4551                     default:
4552                         $start = '<pre>';
4553                         $stop = '</pre>';
4554                 }
4555                 echo $start . $this->format_log([$message], [$message_number]) . $stop;
4556                 @flush();
4557                 @ob_flush();
4558                 break;
4559             // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
4560             // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
4561             // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
4562             // at the beginning of the file
4563             case self::LOG_REALTIME_FILE:
4564                 if (!isset($realtime_log_file)) {
4565                     // PHP doesn't seem to like using constants in fopen()
4566                     $filename = NET_SSH2_LOG_REALTIME_FILENAME;
4567                     $fp = fopen($filename, 'w');
4568                     $realtime_log_file = $fp;
4569                 }
4570                 if (!is_resource($realtime_log_file)) {
4571                     break;
4572                 }
4573                 $entry = $this->format_log([$message], [$message_number]);
4574                 if ($realtime_log_wrap) {
4575                     $temp = "<<< START >>>\r\n";
4576                     $entry .= $temp;
4577                     fseek($realtime_log_file, ftell($realtime_log_file) - strlen($temp));
4578                 }
4579                 $realtime_log_size += strlen($entry);
4580                 if ($realtime_log_size > self::LOG_MAX_SIZE) {
4581                     fseek($realtime_log_file, 0);
4582                     $realtime_log_size = strlen($entry);
4583                     $realtime_log_wrap = true;
4584                 }
4585                 fputs($realtime_log_file, $entry);
4586                 break;
4587             case self::LOG_REALTIME_SIMPLE:
4588                 echo $message_number;
4589                 echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
4590         }
4591     }
4592 
4593     /**
4594      * Sends channel data
4595      *
4596      * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
4597      *
4598      * @param int $client_channel
4599      * @param string $data
4600      * @return void
4601      */
4602     protected function send_channel_packet($client_channel, $data)
4603     {
4604         if (
4605             isset($this->channel_buffers_write[$client_channel])
4606             && strpos($data, $this->channel_buffers_write[$client_channel]) === 0
4607         ) {
4608             // if buffer holds identical initial data content, resume send from the unmatched data portion
4609             $data = substr($data, strlen($this->channel_buffers_write[$client_channel]));
4610         } else {
4611             $this->channel_buffers_write[$client_channel] = '';
4612         }
4613         while (strlen($data)) {
4614             if (!$this->window_size_client_to_server[$client_channel]) {
4615                 // using an invalid channel will let the buffers be built up for the valid channels
4616                 $this->get_channel_packet(-$client_channel);
4617                 if ($this->isTimeout()) {
4618                     throw new TimeoutException('Timed out waiting for server');
4619                 } elseif (!$this->window_size_client_to_server[$client_channel]) {
4620                     throw new \RuntimeException('Data window was not adjusted');
4621                 }
4622             }
4623 
4624             /* The maximum amount of data allowed is determined by the maximum
4625                packet size for the channel, and the current window size, whichever
4626                is smaller.
4627                  -- http://tools.ietf.org/html/rfc4254#section-5.2 */
4628             $max_size = min(
4629                 $this->packet_size_client_to_server[$client_channel],
4630                 $this->window_size_client_to_server[$client_channel]
4631             );
4632 
4633             $temp = Strings::shift($data, $max_size);
4634             $packet = Strings::packSSH2(
4635                 'CNs',
4636                 NET_SSH2_MSG_CHANNEL_DATA,
4637                 $this->server_channels[$client_channel],
4638                 $temp
4639             );
4640             $this->window_size_client_to_server[$client_channel] -= strlen($temp);
4641             $this->send_binary_packet($packet);
4642             $this->channel_buffers_write[$client_channel] .= $temp;
4643         }
4644         unset($this->channel_buffers_write[$client_channel]);
4645     }
4646 
4647     /**
4648      * Closes and flushes a channel
4649      *
4650      * \phpseclib3\Net\SSH2 doesn't properly close most channels.  For exec() channels are normally closed by the server
4651      * and for SFTP channels are presumably closed when the client disconnects.  This functions is intended
4652      * for SCP more than anything.
4653      *
4654      * @param int $client_channel
4655      * @param bool $want_reply
4656      * @return void
4657      */
4658     private function close_channel($client_channel, $want_reply = false)
4659     {
4660         // see http://tools.ietf.org/html/rfc4254#section-5.3
4661 
4662         $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
4663 
4664         if (!$want_reply) {
4665             $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4666         }
4667 
4668         $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
4669         $this->channelCount--;
4670 
4671         $this->curTimeout = 5;
4672 
4673         while (!is_bool($this->get_channel_packet($client_channel))) {
4674         }
4675 
4676         if ($want_reply) {
4677             $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
4678         }
4679 
4680         $this->close_channel_bitmap($client_channel);
4681     }
4682 
4683     /**
4684      * Maintains execution state bitmap in response to channel closure
4685      *
4686      * @param int $client_channel The channel number to maintain closure status of
4687      * @return void
4688      */
4689     private function close_channel_bitmap($client_channel)
4690     {
4691         switch ($client_channel) {
4692             case self::CHANNEL_SHELL:
4693                 // Shell status has been maintained in the bitmap for backwards
4694                 //  compatibility sake, but can be removed going forward
4695                 if ($this->bitmap & self::MASK_SHELL) {
4696                     $this->bitmap &= ~self::MASK_SHELL;
4697                 }
4698                 break;
4699         }
4700     }
4701 
4702     /**
4703      * Disconnect
4704      *
4705      * @param int $reason
4706      * @return false
4707      */
4708     protected function disconnect_helper($reason)
4709     {
4710         if ($this->bitmap & self::MASK_DISCONNECT) {
4711             // Disregard subsequent disconnect requests
4712             return false;
4713         }
4714         $this->bitmap |= self::MASK_DISCONNECT;
4715         if ($this->isConnected()) {
4716             $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
4717             try {
4718                 $this->send_binary_packet($data);
4719             } catch (\Exception $e) {
4720             }
4721         }
4722 
4723         $this->reset_connection();
4724 
4725         return false;
4726     }
4727 
4728     /**
4729      * Define Array
4730      *
4731      * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
4732      * named constants from it, using the value as the name of the constant and the index as the value of the constant.
4733      * If any of the constants that would be defined already exists, none of the constants will be defined.
4734      *
4735      * @param mixed[] ...$args
4736      * @access protected
4737      */
4738     protected static function define_array(...$args)
4739     {
4740         foreach ($args as $arg) {
4741             foreach ($arg as $key => $value) {
4742                 if (!defined($value)) {
4743                     define($value, $key);
4744                 } else {
4745                     break 2;
4746                 }
4747             }
4748         }
4749     }
4750 
4751     /**
4752      * Returns a log of the packets that have been sent and received.
4753      *
4754      * Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')
4755      *
4756      * @return array|false|string
4757      */
4758     public function getLog()
4759     {
4760         if (!defined('NET_SSH2_LOGGING')) {
4761             return false;
4762         }
4763 
4764         switch (NET_SSH2_LOGGING) {
4765             case self::LOG_SIMPLE:
4766                 return $this->message_number_log;
4767             case self::LOG_COMPLEX:
4768                 $log = $this->format_log($this->message_log, $this->message_number_log);
4769                 return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>';
4770             default:
4771                 return false;
4772         }
4773     }
4774 
4775     /**
4776      * Formats a log for printing
4777      *
4778      * @param array $message_log
4779      * @param array $message_number_log
4780      * @return string
4781      */
4782     protected function format_log(array $message_log, array $message_number_log)
4783     {
4784         $output = '';
4785         for ($i = 0; $i < count($message_log); $i++) {
4786             $output .= $message_number_log[$i];
4787             $current_log = $message_log[$i];
4788             $j = 0;
4789             if (strlen($current_log)) {
4790                 $output .= "\r\n";
4791             }
4792             do {
4793                 if (strlen($current_log)) {
4794                     $output .= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0  ';
4795                 }
4796                 $fragment = Strings::shift($current_log, $this->log_short_width);
4797                 $hex = substr(preg_replace_callback('#.#s', function ($matches) {
4798                     return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
4799                 }, $fragment), strlen($this->log_boundary));
4800                 // replace non ASCII printable characters with dots
4801                 // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
4802                 // also replace < with a . since < messes up the output on web browsers
4803                 $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
4804                 $output .= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
4805                 $j++;
4806             } while (strlen($current_log));
4807             $output .= "\r\n";
4808         }
4809 
4810         return $output;
4811     }
4812 
4813     /**
4814      * Helper function for agent->on_channel_open()
4815      *
4816      * Used when channels are created to inform agent
4817      * of said channel opening. Must be called after
4818      * channel open confirmation received
4819      *
4820      */
4821     private function on_channel_open()
4822     {
4823         if (isset($this->agent)) {
4824             $this->agent->registerChannelOpen($this);
4825         }
4826     }
4827 
4828     /**
4829      * Returns the first value of the intersection of two arrays or false if
4830      * the intersection is empty. The order is defined by the first parameter.
4831      *
4832      * @param array $array1
4833      * @param array $array2
4834      * @return mixed False if intersection is empty, else intersected value.
4835      */
4836     private static function array_intersect_first(array $array1, array $array2)
4837     {
4838         foreach ($array1 as $value) {
4839             if (in_array($value, $array2)) {
4840                 return $value;
4841             }
4842         }
4843         return false;
4844     }
4845 
4846     /**
4847      * Returns all errors / debug messages on the SSH layer
4848      *
4849      * If you are looking for messages from the SFTP layer, please see SFTP::getSFTPErrors()
4850      *
4851      * @return string[]
4852      */
4853     public function getErrors()
4854     {
4855         return $this->errors;
4856     }
4857 
4858     /**
4859      * Returns the last error received on the SSH layer
4860      *
4861      * If you are looking for messages from the SFTP layer, please see SFTP::getLastSFTPError()
4862      *
4863      * @return string
4864      */
4865     public function getLastError()
4866     {
4867         $count = count($this->errors);
4868 
4869         if ($count > 0) {
4870             return $this->errors[$count - 1];
4871         }
4872     }
4873 
4874     /**
4875      * Return the server identification.
4876      *
4877      * @return string|false
4878      */
4879     public function getServerIdentification()
4880     {
4881         $this->connect();
4882 
4883         return $this->server_identifier;
4884     }
4885 
4886     /**
4887      * Returns a list of algorithms the server supports
4888      *
4889      * @return array
4890      */
4891     public function getServerAlgorithms()
4892     {
4893         $this->connect();
4894 
4895         return [
4896             'kex' => $this->kex_algorithms,
4897             'hostkey' => $this->server_host_key_algorithms,
4898             'client_to_server' => [
4899                 'crypt' => $this->encryption_algorithms_client_to_server,
4900                 'mac' => $this->mac_algorithms_client_to_server,
4901                 'comp' => $this->compression_algorithms_client_to_server,
4902                 'lang' => $this->languages_client_to_server
4903             ],
4904             'server_to_client' => [
4905                 'crypt' => $this->encryption_algorithms_server_to_client,
4906                 'mac' => $this->mac_algorithms_server_to_client,
4907                 'comp' => $this->compression_algorithms_server_to_client,
4908                 'lang' => $this->languages_server_to_client
4909             ]
4910         ];
4911     }
4912 
4913     /**
4914      * Returns a list of KEX algorithms that phpseclib supports
4915      *
4916      * @return array
4917      */
4918     public static function getSupportedKEXAlgorithms()
4919     {
4920         $kex_algorithms = [
4921             // Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
4922             // Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
4923             // libssh repository for more information.
4924             'curve25519-sha256',
4925             'curve25519-sha256@libssh.org',
4926 
4927             'ecdh-sha2-nistp256', // RFC 5656
4928             'ecdh-sha2-nistp384', // RFC 5656
4929             'ecdh-sha2-nistp521', // RFC 5656
4930 
4931             'diffie-hellman-group-exchange-sha256',// RFC 4419
4932             'diffie-hellman-group-exchange-sha1',  // RFC 4419
4933 
4934             // Diffie-Hellman Key Agreement (DH) using integer modulo prime
4935             // groups.
4936             'diffie-hellman-group14-sha256',
4937             'diffie-hellman-group14-sha1', // REQUIRED
4938             'diffie-hellman-group15-sha512',
4939             'diffie-hellman-group16-sha512',
4940             'diffie-hellman-group17-sha512',
4941             'diffie-hellman-group18-sha512',
4942 
4943             'diffie-hellman-group1-sha1', // REQUIRED
4944         ];
4945 
4946         return $kex_algorithms;
4947     }
4948 
4949     /**
4950      * Returns a list of host key algorithms that phpseclib supports
4951      *
4952      * @return array
4953      */
4954     public static function getSupportedHostKeyAlgorithms()
4955     {
4956         return [
4957             'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
4958             'ecdsa-sha2-nistp256', // RFC 5656
4959             'ecdsa-sha2-nistp384', // RFC 5656
4960             'ecdsa-sha2-nistp521', // RFC 5656
4961             'rsa-sha2-256', // RFC 8332
4962             'rsa-sha2-512', // RFC 8332
4963             'ssh-rsa', // RECOMMENDED  sign   Raw RSA Key
4964             'ssh-dss'  // REQUIRED     sign   Raw DSS Key
4965         ];
4966     }
4967 
4968     /**
4969      * Returns a list of symmetric key algorithms that phpseclib supports
4970      *
4971      * @return array
4972      */
4973     public static function getSupportedEncryptionAlgorithms()
4974     {
4975         $algos = [
4976             // from <https://tools.ietf.org/html/rfc5647>:
4977             'aes128-gcm@openssh.com',
4978             'aes256-gcm@openssh.com',
4979 
4980             // from <http://tools.ietf.org/html/rfc4345#section-4>:
4981             'arcfour256',
4982             'arcfour128',
4983 
4984             //'arcfour',      // OPTIONAL          the ARCFOUR stream cipher with a 128-bit key
4985 
4986             // CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
4987             'aes128-ctr',     // RECOMMENDED       AES (Rijndael) in SDCTR mode, with 128-bit key
4988             'aes192-ctr',     // RECOMMENDED       AES with 192-bit key
4989             'aes256-ctr',     // RECOMMENDED       AES with 256-bit key
4990 
4991             // from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>:
4992             // one of the big benefits of chacha20-poly1305 is speed. the problem is...
4993             // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
4994             // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
4995             // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
4996             // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
4997             // (which is always gonna be super fast to compute thanks to the hash extension, which
4998             // "is bundled and compiled into PHP by default")
4999             'chacha20-poly1305@openssh.com',
5000 
5001             'twofish128-ctr', // OPTIONAL          Twofish in SDCTR mode, with 128-bit key
5002             'twofish192-ctr', // OPTIONAL          Twofish with 192-bit key
5003             'twofish256-ctr', // OPTIONAL          Twofish with 256-bit key
5004 
5005             'aes128-cbc',     // RECOMMENDED       AES with a 128-bit key
5006             'aes192-cbc',     // OPTIONAL          AES with a 192-bit key
5007             'aes256-cbc',     // OPTIONAL          AES in CBC mode, with a 256-bit key
5008 
5009             'twofish128-cbc', // OPTIONAL          Twofish with a 128-bit key
5010             'twofish192-cbc', // OPTIONAL          Twofish with a 192-bit key
5011             'twofish256-cbc',
5012             'twofish-cbc',    // OPTIONAL          alias for "twofish256-cbc"
5013                               //                   (this is being retained for historical reasons)
5014 
5015             'blowfish-ctr',   // OPTIONAL          Blowfish in SDCTR mode
5016 
5017             'blowfish-cbc',   // OPTIONAL          Blowfish in CBC mode
5018 
5019             '3des-ctr',       // RECOMMENDED       Three-key 3DES in SDCTR mode
5020 
5021             '3des-cbc',       // REQUIRED          three-key 3DES in CBC mode
5022 
5023              //'none'           // OPTIONAL          no encryption; NOT RECOMMENDED
5024         ];
5025 
5026         if (self::$crypto_engine) {
5027             $engines = [self::$crypto_engine];
5028         } else {
5029             $engines = [
5030                 'libsodium',
5031                 'OpenSSL (GCM)',
5032                 'OpenSSL',
5033                 'mcrypt',
5034                 'Eval',
5035                 'PHP'
5036             ];
5037         }
5038 
5039         $ciphers = [];
5040 
5041         foreach ($engines as $engine) {
5042             foreach ($algos as $algo) {
5043                 $obj = self::encryption_algorithm_to_crypt_instance($algo);
5044                 if ($obj instanceof Rijndael) {
5045                     $obj->setKeyLength(preg_replace('#[^\d]#', '', $algo));
5046                 }
5047                 switch ($algo) {
5048                     // Eval engines do not exist for ChaCha20 or RC4 because they would not benefit from one.
5049                     // to benefit from an Eval engine they'd need to loop a variable amount of times, they'd
5050                     // need to do table lookups (eg. sbox subsitutions). ChaCha20 doesn't do either because
5051                     // it's a so-called ARX cipher, meaning that the only operations it does are add (A), rotate (R)
5052                     // and XOR (X). RC4 does do table lookups but being a stream cipher it works differently than
5053                     // block ciphers. with RC4 you XOR the plaintext against a keystream and the keystream changes
5054                     // as you encrypt stuff. the only table lookups are made against this keystream and thus table
5055                     // lookups are kinda unavoidable. with AES and DES, however, the table lookups that are done
5056                     // are done against substitution boxes (sboxes), which are invariant.
5057 
5058                     // OpenSSL can't be used as an engine, either, because OpenSSL doesn't support continuous buffers
5059                     // as SSH2 uses and altho you can emulate a continuous buffer with block ciphers you can't do so
5060                     // with stream ciphers. As for ChaCha20...  for the ChaCha20 part OpenSSL could prob be used but
5061                     // the big slow down isn't with ChaCha20 - it's with Poly1305. SSH constructs the key for that
5062                     // differently than how OpenSSL does it (OpenSSL does it as the RFC describes, SSH doesn't).
5063 
5064                     // libsodium can't be used because it doesn't support RC4 and it doesn't construct the Poly1305
5065                     // keys in the same way that SSH does
5066 
5067                     // mcrypt could prob be used for RC4 but mcrypt hasn't been included in PHP core for yearss
5068                     case 'chacha20-poly1305@openssh.com':
5069                     case 'arcfour128':
5070                     case 'arcfour256':
5071                         if ($engine != 'PHP') {
5072                             continue 2;
5073                         }
5074                         break;
5075                     case 'aes128-gcm@openssh.com':
5076                     case 'aes256-gcm@openssh.com':
5077                         if ($engine == 'OpenSSL') {
5078                             continue 2;
5079                         }
5080                         $obj->setNonce('dummydummydu');
5081                 }
5082                 if ($obj->isValidEngine($engine)) {
5083                     $algos = array_diff($algos, [$algo]);
5084                     $ciphers[] = $algo;
5085                 }
5086             }
5087         }
5088 
5089         return $ciphers;
5090     }
5091 
5092     /**
5093      * Returns a list of MAC algorithms that phpseclib supports
5094      *
5095      * @return array
5096      */
5097     public static function getSupportedMACAlgorithms()
5098     {
5099         return [
5100             'hmac-sha2-256-etm@openssh.com',
5101             'hmac-sha2-512-etm@openssh.com',
5102             'hmac-sha1-etm@openssh.com',
5103 
5104             // from <http://www.ietf.org/rfc/rfc6668.txt>:
5105             'hmac-sha2-256',// RECOMMENDED     HMAC-SHA256 (digest length = key length = 32)
5106             'hmac-sha2-512',// OPTIONAL        HMAC-SHA512 (digest length = key length = 64)
5107 
5108             'hmac-sha1-96', // RECOMMENDED     first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
5109             'hmac-sha1',    // REQUIRED        HMAC-SHA1 (digest length = key length = 20)
5110             'hmac-md5-96',  // OPTIONAL        first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
5111             'hmac-md5',     // OPTIONAL        HMAC-MD5 (digest length = key length = 16)
5112 
5113             'umac-64-etm@openssh.com',
5114             'umac-128-etm@openssh.com',
5115 
5116             // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
5117             'umac-64@openssh.com',
5118             'umac-128@openssh.com',
5119 
5120             //'none'          // OPTIONAL        no MAC; NOT RECOMMENDED
5121         ];
5122     }
5123 
5124     /**
5125      * Returns a list of compression algorithms that phpseclib supports
5126      *
5127      * @return array
5128      */
5129     public static function getSupportedCompressionAlgorithms()
5130     {
5131         $algos = ['none']; // REQUIRED        no compression
5132         if (function_exists('deflate_init')) {
5133             $algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed
5134             $algos[] = 'zlib';
5135         }
5136         return $algos;
5137     }
5138 
5139     /**
5140      * Return list of negotiated algorithms
5141      *
5142      * Uses the same format as https://www.php.net/ssh2-methods-negotiated
5143      *
5144      * @return array
5145      */
5146     public function getAlgorithmsNegotiated()
5147     {
5148         $this->connect();
5149 
5150         $compression_map = [
5151             self::NET_SSH2_COMPRESSION_NONE => 'none',
5152             self::NET_SSH2_COMPRESSION_ZLIB => 'zlib',
5153             self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com'
5154         ];
5155 
5156         return [
5157             'kex' => $this->kex_algorithm,
5158             'hostkey' => $this->signature_format,
5159             'client_to_server' => [
5160                 'crypt' => $this->encryptName,
5161                 'mac' => $this->hmac_create_name,
5162                 'comp' => $compression_map[$this->compress],
5163             ],
5164             'server_to_client' => [
5165                 'crypt' => $this->decryptName,
5166                 'mac' => $this->hmac_check_name,
5167                 'comp' => $compression_map[$this->decompress],
5168             ]
5169         ];
5170     }
5171 
5172     /**
5173      * Force multiple channels (even if phpseclib has decided to disable them)
5174      */
5175     public function forceMultipleChannels()
5176     {
5177         $this->errorOnMultipleChannels = false;
5178     }
5179 
5180     /**
5181      * Allows you to set the terminal
5182      *
5183      * @param string $term
5184      */
5185     public function setTerminal($term)
5186     {
5187         $this->term = $term;
5188     }
5189 
5190     /**
5191      * Accepts an associative array with up to four parameters as described at
5192      * <https://www.php.net/manual/en/function.ssh2-connect.php>
5193      *
5194      * @param array $methods
5195      */
5196     public function setPreferredAlgorithms(array $methods)
5197     {
5198         $keys = ['client_to_server', 'server_to_client'];
5199 
5200         if (isset($methods['kex']) && is_string($methods['kex'])) {
5201             $methods['kex'] = explode(',', $methods['kex']);
5202         }
5203 
5204         if (isset($methods['hostkey']) && is_string($methods['hostkey'])) {
5205             $methods['hostkey'] = explode(',', $methods['hostkey']);
5206         }
5207 
5208         foreach ($keys as $key) {
5209             if (isset($methods[$key])) {
5210                 $a = &$methods[$key];
5211                 if (isset($a['crypt']) && is_string($a['crypt'])) {
5212                     $a['crypt'] = explode(',', $a['crypt']);
5213                 }
5214                 if (isset($a['comp']) && is_string($a['comp'])) {
5215                     $a['comp'] = explode(',', $a['comp']);
5216                 }
5217                 if (isset($a['mac']) && is_string($a['mac'])) {
5218                     $a['mac'] = explode(',', $a['mac']);
5219                 }
5220             }
5221         }
5222 
5223         $preferred = $methods;
5224 
5225         if (isset($preferred['kex'])) {
5226             $preferred['kex'] = array_intersect(
5227                 $preferred['kex'],
5228                 static::getSupportedKEXAlgorithms()
5229             );
5230         }
5231 
5232         if (isset($preferred['hostkey'])) {
5233             $preferred['hostkey'] = array_intersect(
5234                 $preferred['hostkey'],
5235                 static::getSupportedHostKeyAlgorithms()
5236             );
5237         }
5238 
5239         foreach ($keys as $key) {
5240             if (isset($preferred[$key])) {
5241                 $a = &$preferred[$key];
5242                 if (isset($a['crypt'])) {
5243                     $a['crypt'] = array_intersect(
5244                         $a['crypt'],
5245                         static::getSupportedEncryptionAlgorithms()
5246                     );
5247                 }
5248                 if (isset($a['comp'])) {
5249                     $a['comp'] = array_intersect(
5250                         $a['comp'],
5251                         static::getSupportedCompressionAlgorithms()
5252                     );
5253                 }
5254                 if (isset($a['mac'])) {
5255                     $a['mac'] = array_intersect(
5256                         $a['mac'],
5257                         static::getSupportedMACAlgorithms()
5258                     );
5259                 }
5260             }
5261         }
5262 
5263         $keys = [
5264             'kex',
5265             'hostkey',
5266             'client_to_server/crypt',
5267             'client_to_server/comp',
5268             'client_to_server/mac',
5269             'server_to_client/crypt',
5270             'server_to_client/comp',
5271             'server_to_client/mac',
5272         ];
5273         foreach ($keys as $key) {
5274             $p = $preferred;
5275             $m = $methods;
5276 
5277             $subkeys = explode('/', $key);
5278             foreach ($subkeys as $subkey) {
5279                 if (!isset($p[$subkey])) {
5280                     continue 2;
5281                 }
5282                 $p = $p[$subkey];
5283                 $m = $m[$subkey];
5284             }
5285 
5286             if (count($p) != count($m)) {
5287                 $diff = array_diff($m, $p);
5288                 $msg = count($diff) == 1 ?
5289                     ' is not a supported algorithm' :
5290                     ' are not supported algorithms';
5291                 throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg);
5292             }
5293         }
5294 
5295         $this->preferred = $preferred;
5296     }
5297 
5298     /**
5299      * Returns the banner message.
5300      *
5301      * Quoting from the RFC, "in some jurisdictions, sending a warning message before
5302      * authentication may be relevant for getting legal protection."
5303      *
5304      * @return string
5305      */
5306     public function getBannerMessage()
5307     {
5308         return $this->banner_message;
5309     }
5310 
5311     /**
5312      * Returns the server public host key.
5313      *
5314      * Caching this the first time you connect to a server and checking the result on subsequent connections
5315      * is recommended.  Returns false if the server signature is not signed correctly with the public host key.
5316      *
5317      * @return string|false
5318      * @throws \RuntimeException on badly formatted keys
5319      * @throws NoSupportedAlgorithmsException when the key isn't in a supported format
5320      */
5321     public function getServerPublicHostKey()
5322     {
5323         if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
5324             $this->connect();
5325         }
5326 
5327         $signature = $this->signature;
5328         $server_public_host_key = base64_encode($this->server_public_host_key);
5329 
5330         if ($this->signature_validated) {
5331             return $this->bitmap ?
5332                 $this->signature_format . ' ' . $server_public_host_key :
5333                 false;
5334         }
5335 
5336         $this->signature_validated = true;
5337 
5338         switch ($this->signature_format) {
5339             case 'ssh-ed25519':
5340             case 'ecdsa-sha2-nistp256':
5341             case 'ecdsa-sha2-nistp384':
5342             case 'ecdsa-sha2-nistp521':
5343                 $key = EC::loadFormat('OpenSSH', $server_public_host_key)
5344                     ->withSignatureFormat('SSH2');
5345                 switch ($this->signature_format) {
5346                     case 'ssh-ed25519':
5347                         $hash = 'sha512';
5348                         break;
5349                     case 'ecdsa-sha2-nistp256':
5350                         $hash = 'sha256';
5351                         break;
5352                     case 'ecdsa-sha2-nistp384':
5353                         $hash = 'sha384';
5354                         break;
5355                     case 'ecdsa-sha2-nistp521':
5356                         $hash = 'sha512';
5357                 }
5358                 $key = $key->withHash($hash);
5359                 break;
5360             case 'ssh-dss':
5361                 $key = DSA::loadFormat('OpenSSH', $server_public_host_key)
5362                     ->withSignatureFormat('SSH2')
5363                     ->withHash('sha1');
5364                 break;
5365             case 'ssh-rsa':
5366             case 'rsa-sha2-256':
5367             case 'rsa-sha2-512':
5368                 // could be ssh-rsa, rsa-sha2-256, rsa-sha2-512
5369                 // we don't check here because we already checked in key_exchange
5370                 // some signatures have the type embedded within the message and some don't
5371                 list(, $signature) = Strings::unpackSSH2('ss', $signature);
5372 
5373                 $key = RSA::loadFormat('OpenSSH', $server_public_host_key)
5374                     ->withPadding(RSA::SIGNATURE_PKCS1);
5375                 switch ($this->signature_format) {
5376                     case 'rsa-sha2-512':
5377                         $hash = 'sha512';
5378                         break;
5379                     case 'rsa-sha2-256':
5380                         $hash = 'sha256';
5381                         break;
5382                     //case 'ssh-rsa':
5383                     default:
5384                         $hash = 'sha1';
5385                 }
5386                 $key = $key->withHash($hash);
5387                 break;
5388             default:
5389                 $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
5390                 throw new NoSupportedAlgorithmsException('Unsupported signature format');
5391         }
5392 
5393         if (!$key->verify($this->exchange_hash, $signature)) {
5394             return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
5395         };
5396 
5397         return $this->signature_format . ' ' . $server_public_host_key;
5398     }
5399 
5400     /**
5401      * Returns the exit status of an SSH command or false.
5402      *
5403      * @return false|int
5404      */
5405     public function getExitStatus()
5406     {
5407         if (is_null($this->exit_status)) {
5408             return false;
5409         }
5410         return $this->exit_status;
5411     }
5412 
5413     /**
5414      * Returns the number of columns for the terminal window size.
5415      *
5416      * @return int
5417      */
5418     public function getWindowColumns()
5419     {
5420         return $this->windowColumns;
5421     }
5422 
5423     /**
5424      * Returns the number of rows for the terminal window size.
5425      *
5426      * @return int
5427      */
5428     public function getWindowRows()
5429     {
5430         return $this->windowRows;
5431     }
5432 
5433     /**
5434      * Sets the number of columns for the terminal window size.
5435      *
5436      * @param int $value
5437      */
5438     public function setWindowColumns($value)
5439     {
5440         $this->windowColumns = $value;
5441     }
5442 
5443     /**
5444      * Sets the number of rows for the terminal window size.
5445      *
5446      * @param int $value
5447      */
5448     public function setWindowRows($value)
5449     {
5450         $this->windowRows = $value;
5451     }
5452 
5453     /**
5454      * Sets the number of columns and rows for the terminal window size.
5455      *
5456      * @param int $columns
5457      * @param int $rows
5458      */
5459     public function setWindowSize($columns = 80, $rows = 24)
5460     {
5461         $this->windowColumns = $columns;
5462         $this->windowRows = $rows;
5463     }
5464 
5465     /**
5466      * To String Magic Method
5467      *
5468      * @return string
5469      */
5470     #[\ReturnTypeWillChange]
5471     public function __toString()
5472     {
5473         return $this->getResourceId();
5474     }
5475 
5476     /**
5477      * Get Resource ID
5478      *
5479      * We use {} because that symbols should not be in URL according to
5480      * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}.
5481      * It will safe us from any conflicts, because otherwise regexp will
5482      * match all alphanumeric domains.
5483      *
5484      * @return string
5485      */
5486     public function getResourceId()
5487     {
5488         return '{' . spl_object_hash($this) . '}';
5489     }
5490 
5491     /**
5492      * Return existing connection
5493      *
5494      * @param string $id
5495      *
5496      * @return bool|SSH2 will return false if no such connection
5497      */
5498     public static function getConnectionByResourceId($id)
5499     {
5500         if (isset(self::$connections[$id])) {
5501             return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id];
5502         }
5503         return false;
5504     }
5505 
5506     /**
5507      * Return all excising connections
5508      *
5509      * @return array<string, SSH2>
5510      */
5511     public static function getConnections()
5512     {
5513         if (!class_exists('WeakReference')) {
5514             /** @var array<string, SSH2> */
5515             return self::$connections;
5516         }
5517         $temp = [];
5518         foreach (self::$connections as $key => $ref) {
5519             $temp[$key] = $ref->get();
5520         }
5521         return $temp;
5522     }
5523 
5524     /*
5525      * Update packet types in log history
5526      *
5527      * @param string $old
5528      * @param string $new
5529      */
5530     private function updateLogHistory($old, $new)
5531     {
5532         if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
5533             $this->message_number_log[count($this->message_number_log) - 1] = str_replace(
5534                 $old,
5535                 $new,
5536                 $this->message_number_log[count($this->message_number_log) - 1]
5537             );
5538         }
5539     }
5540 
5541     /**
5542      * Return the list of authentication methods that may productively continue authentication.
5543      *
5544      * @see https://tools.ietf.org/html/rfc4252#section-5.1
5545      * @return array|null
5546      */
5547     public function getAuthMethodsToContinue()
5548     {
5549         return $this->auth_methods_to_continue;
5550     }
5551 
5552     /**
5553      * Enables "smart" multi-factor authentication (MFA)
5554      */
5555     public function enableSmartMFA()
5556     {
5557         $this->smartMFA = true;
5558     }
5559 
5560     /**
5561      * Disables "smart" multi-factor authentication (MFA)
5562      */
5563     public function disableSmartMFA()
5564     {
5565         $this->smartMFA = false;
5566     }
5567 
5568     /**
5569      * How many bytes until the next key re-exchange?
5570      *
5571      * @param int $bytes
5572      */
5573     public function bytesUntilKeyReexchange($bytes)
5574     {
5575         $this->doKeyReexchangeAfterXBytes = $bytes;
5576     }
5577 }
5578