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