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