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